feat: close stub-page gaps - profile login/logout, admin Reports/Settings, Help/Docs links
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:
sdarbinyan
2026-08-05 17:48:50 +04:00
parent 65c6d6f5d1
commit 48bcffa22c
26 changed files with 398 additions and 16 deletions

View File

@@ -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 }} &middot; {{ 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>

View File

@@ -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; }
}

View File

@@ -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);
}
}