feat(admin): sprint 19 admin dashboard, routing, i18n

- Add admin dashboard feature (models/gateway/facade/components/page)
- Wire admin/products routes and backoffice coming-soon placeholders
- Add lastPublishedAt to ProjectEditorFacade/state
- Add dashboard i18n keys (en/ru/hy) and docs/ADMIN.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-14 12:21:33 +04:00
parent 6aec2ebcb2
commit 325dc17911
36 changed files with 1386 additions and 5 deletions

163
docs/ADMIN.md Normal file
View File

@@ -0,0 +1,163 @@
# Marketplace Admin Dashboard - Sprint 19
## Scope
Sprint 19 adds the production Admin Dashboard and makes it the default landing
page for the admin area. It also wires the previously-unrouted `admin/products`
feature and adds route placeholders for backoffice sections that don't have a
feature built yet.
## Routing
All admin routes live under `/:lang/backoffice/**` (`app.routes.ts`), guarded
by the existing `adminAuthGuard` (`core/admin-auth/admin-auth.guard.ts`):
```text
/:lang/backoffice -> redirects to dashboard
/:lang/backoffice/dashboard -> AdminDashboardPageComponent
/:lang/backoffice/products -> AdminProductsListPageComponent
/:lang/backoffice/products/create -> AdminProductEditorPageComponent
/:lang/backoffice/products/:id/edit -> AdminProductEditorPageComponent
/:lang/backoffice/products/:id/duplicate -> AdminProductEditorPageComponent
/:lang/backoffice/categories -> BackofficeComingSoonPageComponent
/:lang/backoffice/static-pages -> BackofficeComingSoonPageComponent
/:lang/backoffice/transactions -> BackofficeComingSoonPageComponent
/:lang/backoffice/orders -> BackofficeComingSoonPageComponent
/:lang/backoffice/media -> BackofficeComingSoonPageComponent
```
`admin/products` (`features/admin/products/`) was already fully implemented
in an earlier sprint but was never wired into `app.routes.ts` and its internal
navigation hardcoded the `ru` locale segment. Both are fixed in this sprint:
routes are wired, and `admin-products-list-page.component.ts` /
`admin-product-editor-page.component.ts` now build the locale segment from
`LanguageService.currentLanguage()`.
**Dashboard as default admin page:** on successful admin Telegram QR login,
`TelegramLoginComponent` (`mode="admin"`) navigates to
`/:lang/backoffice/dashboard` (`components/telegram-login/telegram-login.component.ts`).
The `backoffice` route's empty path also redirects to `dashboard`, so any bare
`/:lang/backoffice` link lands there too.
## Architecture
```text
src/app/features/admin/dashboard/
models/ admin-dashboard.model.ts
services/ admin-dashboard-metrics.gateway.interface.ts
admin-dashboard-metrics.local.gateway.ts
admin-dashboard-metrics-gateway.token.ts
admin-dashboard-history.service.ts
facade/ admin-dashboard.facade.ts
components/ admin-dashboard-card.component.*
admin-dashboard-quick-actions.component.*
admin-dashboard-activity.component.*
admin-dashboard-health.component.*
pages/ admin-dashboard-page.component.*
src/app/features/backoffice/shared/
backoffice-coming-soon-page.component.*
```
Follows the existing container/facade/service split (ADR-006, ADR-007):
`AdminDashboardPageComponent` is the container, `AdminDashboardFacade` owns
orchestration, presentational card/quick-actions/activity/health components
take only `@Input()`s and have no HttpClient/localStorage/route access.
### Data sources (future-ready)
Cards never read `ConfigService`, `localStorage`, or an HTTP client directly -
everything routes through `AdminDashboardFacade`, which composes:
- **`ProjectEditorFacade`** (already existed) - `bootstrap`, `status`,
`lastSavedAt`, `lastPublishedAt` (new, see below), `validationIssues`,
`homepageWidgets`. Backs Marketplace Status, Project Name, Current Theme,
Languages, Last Publish, Last Draft Save, Bootstrap Version, Active Layout,
Enabled Widgets, and the System Health checks.
- **`ADMIN_DASHBOARD_METRICS_GATEWAY`** (new `InjectionToken`, same swap
pattern as `BACKOFFICE_DATA_PROVIDER`) - defaults to
`AdminDashboardMetricsLocalGateway`, which composes
`BackofficeDataService.loadCategories()/loadProducts()` (already used by
`AdminProductsLocalGateway`) into counts. Backs Categories Count and
Products Count. Swapping to a real dashboard-metrics endpoint later means
implementing `AdminDashboardMetricsGateway` and rebinding the token - the
facade and cards don't change.
- **`AdminDashboardHistoryService`** (new) - localStorage-backed activity log,
scoped per tenant, same pattern as `ProjectEditorDraftStorageService`. The
facade appends an entry whenever `lastSavedAt`/`lastPublishedAt` change
(detected via an `effect()`, primed on first read so the initial bootstrap
load doesn't get logged as an activity event). Backs Recent Activity.
### Orders / Revenue
No backend or local data model exists for orders or revenue anywhere in the
codebase (`features/backoffice/orders` is an empty placeholder folder). These
two cards render an honest **`pending-backend`** card state ("Awaiting backend
integration") rather than fabricated numbers - not a "no data" empty state,
since the gap is structural, not a temporarily-empty dataset.
### Card states
`AdminDashboardCardComponent` (`components/admin-dashboard-card.component.ts`)
renders one of: `loading` (skeleton), `empty`, `error`, `pending-backend`, or
the ready value + optional subtitle. The container computes each card's status
per data source (bootstrap not yet loaded -> `loading`; metrics gateway error
-> `error`; no supported locales -> `empty`; Orders/Revenue -> always
`pending-backend`).
### System Health
`ProjectValidator` (`features/project-editor/services/project-validator.service.ts`)
already covered 5 of the 6 required checks. This sprint added two more:
- `translationIssues()` - flags a supported non-default locale missing a
header nav label translation or a static-page `translations` entry.
- `layoutIssues()` - flags `bootstrap.layout.type` or any section's
`layout.strategy` that isn't one of the known enum values
(`PlatformLayoutType` / `SectionLayoutStrategy`). Runtime validation matters
here because bootstrap JSON isn't type-checked at load time.
Dashboard mapping (`AdminDashboardFacade.healthChecks`):
| Dashboard label | Validator code |
|---|---|
| Bootstrap valid | structural: `bootstrap !== null && schemaVersion` set |
| Configuration valid | no validation issues at all |
| Missing translations | `missing-translations` (new) |
| Invalid colors | `invalid-colors` (existing) |
| Invalid widget references | `missing-widget` (existing - a homepage widget with no `type`) |
| Invalid layouts | `invalid-layouts` (new) |
### Quick Actions
Static list in `AdminDashboardFacade` (`route` arrays relative to the lang
root); the page component prefixes the current locale
(`LanguageService.currentLanguage()`) before binding `routerLink`. Categories,
Static Pages, Transactions, Orders, and Media Library currently land on
`BackofficeComingSoonPageComponent` since those features aren't built yet -
this is a routing placeholder, not a dashboard card placeholder.
### `lastPublishedAt` (ProjectEditorFacade change)
Before this sprint, `publish()` only updated `lastSavedAt`, so "last draft
save" and "last publish" were indistinguishable after a publish. Added
`lastPublishedAt: number | null` to `ProjectEditorState` /
`ProjectEditorFacade`, set only inside `publish()`. `lastSavedAt` behavior is
unchanged (still updated by both `save()` and `publish()`).
## Known gaps / backend needs
- **Dashboard metrics endpoint.** Categories/Products counts are computed
client-side from `BackofficeDataService` (itself mock/API-switchable via
`BACKOFFICE_DATA_PROVIDER`). A dedicated `/builder/dashboard/summary`-style
endpoint would let `AdminDashboardMetricsGateway` return richer data
(real-time counts, trend deltas) without touching the facade or cards.
- **Orders/Revenue have no backend at all** (see above) - needs an order
domain and revenue aggregation before these cards can show real data.
- **Recent Activity is local-only**, scoped to the browser/tenant via
localStorage (`adminDashboard.activityHistory.v1`), same limitation as the
existing draft-save local storage. It will not show another editor's
activity until a real audit-log endpoint exists.
- **Admin authorization is still not enforced server-side** (see
`Project-Editor.md` - "Admin Authentication" section); this sprint does not
change that. Nothing new here beyond routing/dashboard.