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

View File

@@ -0,0 +1,171 @@
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateService } from '../../../../i18n/translate.service';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { LanguageService } from '../../../../services/language.service';
import { AdminDashboardFacade } from '../facade/admin-dashboard.facade';
import { AdminDashboardCardComponent } from '../components/admin-dashboard-card.component';
import { AdminDashboardQuickActionsComponent } from '../components/admin-dashboard-quick-actions.component';
import { AdminDashboardActivityComponent, AdminDashboardActivityViewEntry } from '../components/admin-dashboard-activity.component';
import { AdminDashboardHealthComponent } from '../components/admin-dashboard-health.component';
import { AdminDashboardCardStatus, AdminDashboardQuickAction } from '../models/admin-dashboard.model';
interface DashboardCardViewModel {
id: string;
titleKey: string;
status: AdminDashboardCardStatus;
value: string | null;
subtitle: string | null;
}
@Component({
selector: 'app-admin-dashboard-page',
standalone: true,
imports: [
CommonModule,
TranslatePipe,
AdminDashboardCardComponent,
AdminDashboardQuickActionsComponent,
AdminDashboardActivityComponent,
AdminDashboardHealthComponent,
],
templateUrl: './admin-dashboard-page.component.html',
styleUrls: ['./admin-dashboard-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminDashboardPageComponent {
readonly facade = inject(AdminDashboardFacade);
private readonly translate = inject(TranslateService);
private readonly languageService = inject(LanguageService);
readonly quickActions = computed<AdminDashboardQuickAction[]>(() => {
const lang = this.languageService.currentLanguage();
return this.facade.quickActions.map(action => ({
...action,
route: ['/', lang, ...action.route.filter(segment => segment !== '')],
}));
});
readonly activityEntries = computed<AdminDashboardActivityViewEntry[]>(() =>
this.facade.activityEntries().map(entry => ({
id: entry.id,
labelKey: entry.type === 'published' ? 'dashboard.activityPublished' : 'dashboard.activityDraftSaved',
timeText: new Date(entry.timestamp).toLocaleString(),
})),
);
readonly healthChecks = this.facade.healthChecks;
readonly cards = computed<DashboardCardViewModel[]>(() => {
const bootstrap = this.facade.bootstrap();
const bootstrapLoading: AdminDashboardCardStatus = bootstrap ? 'ready' : 'loading';
const metrics = this.facade.metrics();
const locales = bootstrap?.localization.supportedLocales ?? [];
return [
{
id: 'marketplace-status',
titleKey: 'dashboard.cardMarketplaceStatus',
status: bootstrapLoading,
value: bootstrap ? this.translate.t(this.facade.status() === 'published' ? 'builder.statusPublished' : 'builder.statusDraft') : null,
subtitle: null,
},
{
id: 'project-name',
titleKey: 'dashboard.cardProjectName',
status: bootstrapLoading,
value: bootstrap?.tenant.name ?? null,
subtitle: null,
},
{
id: 'current-theme',
titleKey: 'dashboard.cardCurrentTheme',
status: bootstrapLoading,
value: bootstrap?.theme.themeId ?? null,
subtitle: null,
},
{
id: 'languages',
titleKey: 'dashboard.cardLanguages',
status: bootstrapLoading === 'loading' ? 'loading' : (locales.length === 0 ? 'empty' : 'ready'),
value: locales.length ? locales.join(', ').toUpperCase() : null,
subtitle: null,
},
{
id: 'categories-count',
titleKey: 'dashboard.cardCategoriesCount',
status: metrics.status,
value: metrics.value ? String(metrics.value.categoriesCount) : null,
subtitle: null,
},
{
id: 'products-count',
titleKey: 'dashboard.cardProductsCount',
status: metrics.status,
value: metrics.value ? String(metrics.value.productsCount) : null,
subtitle: null,
},
{
id: 'orders',
titleKey: 'dashboard.cardOrders',
status: 'pending-backend',
value: null,
subtitle: null,
},
{
id: 'revenue',
titleKey: 'dashboard.cardRevenue',
status: 'pending-backend',
value: null,
subtitle: null,
},
{
id: 'last-publish',
titleKey: 'dashboard.cardLastPublish',
status: bootstrapLoading,
value: bootstrap ? this.formatTimestamp(this.facade.lastPublishedAt()) : null,
subtitle: null,
},
{
id: 'last-draft-save',
titleKey: 'dashboard.cardLastDraftSave',
status: bootstrapLoading,
value: bootstrap ? this.formatTimestamp(this.facade.lastSavedAt()) : null,
subtitle: null,
},
{
id: 'bootstrap-version',
titleKey: 'dashboard.cardBootstrapVersion',
status: bootstrapLoading,
value: bootstrap?.schemaVersion ?? null,
subtitle: null,
},
{
id: 'active-layout',
titleKey: 'dashboard.cardActiveLayout',
status: bootstrapLoading,
value: bootstrap ? (bootstrap.layout?.type ?? 'default') : null,
subtitle: null,
},
{
id: 'enabled-widgets',
titleKey: 'dashboard.cardEnabledWidgets',
status: bootstrapLoading,
value: bootstrap ? String(this.facade.enabledWidgetsCount()) : null,
subtitle: null,
},
];
});
constructor() {
this.facade.ensureLoaded();
}
cardTitle(titleKey: string): string {
return this.translate.t(titleKey);
}
private formatTimestamp(timestamp: number | null): string {
return timestamp ? new Date(timestamp).toLocaleString() : this.translate.t('dashboard.never');
}
}