feat: close stub-page gaps - profile login/logout, admin Reports/Settings, Help/Docs links
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Sprint A: storefront header profile control (login/logout only, no menu), wired to existing customer Telegram auth (AuthService). Sprint B: backoffice/reports page, reuses AdminAnalyticsFacade (Sales, Top Products, Marketplace Health cards + CSV export). Sprint C: backoffice/settings page, admin UI density preference (comfortable/compact), localStorage-persisted, applied to app-table across all admin list pages. Sprint D: admin bottom-nav Help -> mailto using existing supportEmail, Documentation -> external link via new TenantConfig.documentationUrl. AdminNavLink gains externalHref for non-routerLink nav entries. Docs: docs/GLOBAL-SPRINT-PLAN.md tracks the full sprint breakdown. docs/COMING-SOON-AUDIT.md removed, folded into docs/KNOWN-ISSUES.md. docs/BACKEND.md updated with the new documentationUrl bootstrap field. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<section class="admin-reports-page">
|
||||
@if (facade.loading()) {
|
||||
<div class="report-grid" role="status" aria-live="polite" aria-busy="true">
|
||||
@for (i of [1,2,3]; track i) {
|
||||
<app-skeleton shape="rect" height="140px" />
|
||||
}
|
||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="report-grid">
|
||||
<div class="report-card">
|
||||
<h2>{{ 'adminReports.sales' | translate }}</h2>
|
||||
@if (facade.summary(); as summary) {
|
||||
<p class="report-summary">{{ summary.revenueTotal }} {{ summary.currency }} · {{ summary.ordersCount }} {{ 'adminAnalytics.orders' | translate }}</p>
|
||||
}
|
||||
<app-button variant="secondary" size="sm" (click)="exportSalesCsv()">{{ 'adminOrders.export' | translate }}</app-button>
|
||||
</div>
|
||||
|
||||
<div class="report-card">
|
||||
<h2>{{ 'adminReports.topProducts' | translate }}</h2>
|
||||
<p class="report-summary">{{ facade.topProducts().length }} {{ 'adminAnalytics.topProducts' | translate }}</p>
|
||||
<app-button variant="secondary" size="sm" (click)="exportTopProductsCsv()">{{ 'adminOrders.export' | translate }}</app-button>
|
||||
</div>
|
||||
|
||||
<div class="report-card">
|
||||
<h2>{{ 'adminReports.marketplaceHealth' | translate }}</h2>
|
||||
<p class="report-summary">{{ facade.healthCompletionPercent() }}% {{ 'adminMarketplaceHealth.complete' | translate }}</p>
|
||||
<app-button variant="secondary" size="sm" (click)="exportHealthCsv()">{{ 'adminOrders.export' | translate }}</app-button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
@@ -0,0 +1,19 @@
|
||||
.admin-reports-page { display: grid; gap: 16px; padding: 16px; max-width: 1100px; margin: 0 auto; }
|
||||
|
||||
.report-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
|
||||
|
||||
.report-card {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
align-content: start;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border-color, #d3dad9);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-primary, #fff);
|
||||
}
|
||||
.report-card h2 { margin: 0; font-size: var(--font-size-xl, 1.125rem); }
|
||||
.report-summary { margin: 0; color: var(--text-secondary, #6b7280); font-size: var(--font-size-sm, 0.8125rem); }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.report-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { AdminAnalyticsFacade } from '../../analytics/facade/admin-analytics.facade';
|
||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||
import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-reports-page',
|
||||
standalone: true,
|
||||
imports: [TranslatePipe, ButtonComponent, SkeletonComponent],
|
||||
templateUrl: './admin-reports-page.component.html',
|
||||
styleUrls: ['./admin-reports-page.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AdminReportsPageComponent {
|
||||
readonly facade = inject(AdminAnalyticsFacade);
|
||||
|
||||
constructor() {
|
||||
this.facade.load();
|
||||
}
|
||||
|
||||
exportSalesCsv(): void {
|
||||
this.download(this.facade.exportCsv(), 'sales-report.csv');
|
||||
}
|
||||
|
||||
exportTopProductsCsv(): void {
|
||||
this.download(this.facade.exportTopProductsCsv(), 'top-products-report.csv');
|
||||
}
|
||||
|
||||
exportHealthCsv(): void {
|
||||
this.download(this.facade.exportHealthCsv(), 'marketplace-health-report.csv');
|
||||
}
|
||||
|
||||
private download(csv: string, filename: string): void {
|
||||
const blob = new Blob([csv], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user