Replace every PrimeIcons pi-* class (static and data-driven) in the admin dashboard area with app-icon: dashboard-card, dashboard-shortcut- card, dashboard-status-row, dashboard-timeline, and the icon data in admin-dashboard.facade.ts / admin-dashboard-page.component.ts. Icon fields on AdminDashboardQuickAction/Shortcut/DashboardTimelineEntry are now typed AppIconName instead of string, so a typo or unmapped icon name is a compile error instead of a silently blank icon. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
124 lines
4.9 KiB
TypeScript
124 lines
4.9 KiB
TypeScript
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
|
|
import { RouterLink } from '@angular/router';
|
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|
import { TranslateService } from '../../../../i18n/translate.service';
|
|
import { LanguageService } from '../../../../services/language.service';
|
|
import { AdminDashboardFacade } from '../facade/admin-dashboard.facade';
|
|
import { DashboardSectionComponent } from '../components/dashboard-section.component';
|
|
import { DashboardCardComponent } from '../components/dashboard-card.component';
|
|
import { DashboardMetricComponent } from '../components/dashboard-metric.component';
|
|
import { DashboardStatusRowComponent } from '../components/dashboard-status-row.component';
|
|
import { DashboardShortcutCardComponent } from '../components/dashboard-shortcut-card.component';
|
|
import { DashboardTimelineComponent, DashboardTimelineEntry, DashboardTimelineStatus } from '../components/dashboard-timeline.component';
|
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
|
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
|
|
import { AdminDashboardQuickAction, AdminDashboardShortcut } from '../models/admin-dashboard.model';
|
|
import { AppIconName } from '../../../../shared/ui/icon/icon-registry';
|
|
|
|
interface LinkedAction {
|
|
icon: AppIconName;
|
|
labelKey: string;
|
|
descriptionKey?: string;
|
|
route: string[];
|
|
comingSoon: boolean;
|
|
}
|
|
|
|
const ACTIVITY_ICON: Record<string, AppIconName> = {
|
|
'draft-saved': 'save',
|
|
published: 'upload',
|
|
};
|
|
|
|
const ACTIVITY_LABEL_KEY: Record<string, string> = {
|
|
'draft-saved': 'dashboard.activityDraftSaved',
|
|
published: 'dashboard.activityPublished',
|
|
};
|
|
|
|
const DOCUMENTATION_LINKS: Array<{ id: string; icon: AppIconName; labelKey: string }> = [
|
|
{ id: 'documentation', icon: 'book', labelKey: 'dashboard.docDocumentation' },
|
|
{ id: 'shortcuts', icon: 'zap', labelKey: 'dashboard.docKeyboardShortcuts' },
|
|
{ id: 'report-issue', icon: 'flag', labelKey: 'dashboard.docReportIssue' },
|
|
{ id: 'release-notes', icon: 'megaphone', labelKey: 'dashboard.docReleaseNotes' },
|
|
];
|
|
|
|
@Component({
|
|
selector: 'app-admin-dashboard-page',
|
|
standalone: true,
|
|
imports: [
|
|
RouterLink,
|
|
TranslatePipe,
|
|
DashboardSectionComponent,
|
|
DashboardCardComponent,
|
|
DashboardMetricComponent,
|
|
DashboardStatusRowComponent,
|
|
DashboardShortcutCardComponent,
|
|
DashboardTimelineComponent,
|
|
ButtonComponent,
|
|
IconComponent,
|
|
],
|
|
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 documentationLinks = DOCUMENTATION_LINKS;
|
|
|
|
private toLinkedAction(action: AdminDashboardQuickAction | AdminDashboardShortcut): LinkedAction {
|
|
const lang = this.languageService.currentLanguage();
|
|
const comingSoon = 'comingSoon' in action ? !!action.comingSoon : action.route.length === 0;
|
|
return {
|
|
icon: action.icon,
|
|
labelKey: action.labelKey,
|
|
descriptionKey: 'descriptionKey' in action ? action.descriptionKey : undefined,
|
|
route: comingSoon ? [] : ['/', lang, ...action.route],
|
|
comingSoon,
|
|
};
|
|
}
|
|
|
|
readonly quickActions = computed<LinkedAction[]>(() =>
|
|
this.facade.quickActions.map(action => this.toLinkedAction(action)),
|
|
);
|
|
|
|
readonly shortcuts = computed<LinkedAction[]>(() =>
|
|
this.facade.shortcuts.map(action => this.toLinkedAction(action)),
|
|
);
|
|
|
|
readonly previewRoute = computed(() => ['/', this.languageService.currentLanguage()]);
|
|
|
|
readonly tenantName = computed(() => this.facade.bootstrap()?.tenant.name ?? null);
|
|
readonly bootstrapLoading = computed(() => !this.facade.bootstrap());
|
|
|
|
readonly lastPublishedText = computed(() => this.formatTimestamp(this.facade.lastPublishedAt()));
|
|
readonly lastSavedText = computed(() => this.formatTimestamp(this.facade.lastSavedAt()));
|
|
|
|
readonly activityStatus = computed<DashboardTimelineStatus>(() => (this.bootstrapLoading() ? 'loading' : 'ready'));
|
|
|
|
readonly activityTimelineEntries = computed<DashboardTimelineEntry[]>(() =>
|
|
this.facade.activityEntries().map(entry => ({
|
|
id: entry.id,
|
|
labelKey: ACTIVITY_LABEL_KEY[entry.type] ?? entry.type,
|
|
timeText: new Date(entry.timestamp).toLocaleString(this.languageService.currentLanguage()),
|
|
icon: ACTIVITY_ICON[entry.type] ?? 'circle',
|
|
})),
|
|
);
|
|
|
|
constructor() {
|
|
this.facade.ensureLoaded();
|
|
}
|
|
|
|
private formatTimestamp(timestamp: number | null): string | null {
|
|
return timestamp ? new Date(timestamp).toLocaleString(this.languageService.currentLanguage()) : null;
|
|
}
|
|
|
|
publishDraft(): void {
|
|
this.facade.publishDraft();
|
|
}
|
|
|
|
discardDraft(): void {
|
|
this.facade.discardDraft();
|
|
}
|
|
}
|