2026-07-14 12:21:33 +04:00
|
|
|
import { Injectable, computed, effect, inject, signal } from '@angular/core';
|
|
|
|
|
import { take } from 'rxjs/operators';
|
|
|
|
|
import { ProjectEditorFacade } from '../../../project-editor/facade/project-editor.facade';
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
import { EditorSchemaService } from '../../../project-editor/schema/editor-schema.service';
|
|
|
|
|
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
|
|
|
|
|
import { environment } from '../../../../../environments/environment';
|
2026-07-14 12:21:33 +04:00
|
|
|
import { ADMIN_DASHBOARD_METRICS_GATEWAY } from '../services/admin-dashboard-metrics-gateway.token';
|
|
|
|
|
import { AdminDashboardHistoryService } from '../services/admin-dashboard-history.service';
|
|
|
|
|
import {
|
|
|
|
|
AdminDashboardCardState,
|
|
|
|
|
AdminDashboardHealthCheck,
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
AdminDashboardHomeHealthCheck,
|
2026-07-14 12:21:33 +04:00
|
|
|
AdminDashboardMetrics,
|
|
|
|
|
AdminDashboardQuickAction,
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
AdminDashboardShortcut,
|
2026-07-14 12:21:33 +04:00
|
|
|
} from '../models/admin-dashboard.model';
|
|
|
|
|
|
|
|
|
|
const QUICK_ACTIONS: AdminDashboardQuickAction[] = [
|
2026-07-20 02:13:41 +04:00
|
|
|
{ id: 'add-product', icon: 'plusCircle', labelKey: 'dashboard.actionAddProduct', descriptionKey: 'dashboard.actionAddProductDescription', route: ['backoffice', 'products', 'create'] },
|
|
|
|
|
{ id: 'create-category', icon: 'network', labelKey: 'dashboard.actionCreateCategory', descriptionKey: 'dashboard.actionCreateCategoryDescription', route: ['backoffice', 'categories', 'create'] },
|
|
|
|
|
{ id: 'edit-project', icon: 'layoutGrid', labelKey: 'dashboard.actionEditProject', descriptionKey: 'dashboard.actionEditProjectDescription', route: ['edit'] },
|
|
|
|
|
{ id: 'edit-homepage', icon: 'home', labelKey: 'dashboard.actionEditHomepage', descriptionKey: 'dashboard.actionEditHomepageDescription', route: ['edit', 'homepage'] },
|
|
|
|
|
{ id: 'media-library', icon: 'images', labelKey: 'dashboard.actionMediaLibrary', descriptionKey: 'dashboard.actionMediaLibraryDescription', route: ['backoffice', 'media'] },
|
|
|
|
|
{ id: 'orders', icon: 'cart', labelKey: 'dashboard.actionOrders', descriptionKey: 'dashboard.actionOrdersDescription', route: ['backoffice', 'orders'] },
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const SHORTCUTS: AdminDashboardShortcut[] = [
|
2026-07-20 02:13:41 +04:00
|
|
|
{ id: 'products', icon: 'package', labelKey: 'dashboard.actionProducts', route: ['backoffice', 'products'] },
|
|
|
|
|
{ id: 'categories', icon: 'tags', labelKey: 'dashboard.actionCategories', route: ['backoffice', 'categories'] },
|
|
|
|
|
{ id: 'marketplace-builder', icon: 'network', labelKey: 'dashboard.shortcutMarketplaceBuilder', route: ['edit'] },
|
|
|
|
|
{ id: 'static-pages', icon: 'edit', labelKey: 'dashboard.actionStaticPages', route: ['backoffice', 'static-pages'] },
|
|
|
|
|
{ id: 'orders', icon: 'cart', labelKey: 'dashboard.actionOrders', route: ['backoffice', 'orders'] },
|
|
|
|
|
{ id: 'users', icon: 'users', labelKey: 'dashboard.actionUsers', route: ['backoffice', 'users'] },
|
|
|
|
|
{ id: 'settings', icon: 'settings', labelKey: 'dashboard.shortcutSettings', route: [], comingSoon: true },
|
|
|
|
|
{ id: 'media-library', icon: 'images', labelKey: 'dashboard.actionMediaLibrary', route: ['backoffice', 'media'] },
|
|
|
|
|
{ id: 'content', icon: 'alignLeft', labelKey: 'dashboard.shortcutContent', route: ['edit', 'static-pages'] },
|
2026-07-14 12:21:33 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
* Composes ProjectEditorFacade (bootstrap/status/save-publish timestamps/validation/dirty
|
|
|
|
|
* state) with dashboard-only metrics/history/auth so the page component stays presentational.
|
|
|
|
|
* Cards read through here, never directly from ConfigService or localStorage - swapping local
|
|
|
|
|
* sources for real backend endpoints only touches this facade and the gateways it calls, per
|
|
|
|
|
* ADR-006/007.
|
2026-07-14 12:21:33 +04:00
|
|
|
*/
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
|
|
|
export class AdminDashboardFacade {
|
|
|
|
|
private readonly projectEditor = inject(ProjectEditorFacade);
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
private readonly schema = inject(EditorSchemaService);
|
|
|
|
|
private readonly adminAuth = inject(AdminAuthService);
|
2026-07-14 12:21:33 +04:00
|
|
|
private readonly metricsGateway = inject(ADMIN_DASHBOARD_METRICS_GATEWAY);
|
|
|
|
|
private readonly history = inject(AdminDashboardHistoryService);
|
|
|
|
|
|
|
|
|
|
private readonly metricsState = signal<AdminDashboardCardState<AdminDashboardMetrics>>({ status: 'loading', value: null });
|
|
|
|
|
private readonly activityTick = signal(0);
|
|
|
|
|
|
|
|
|
|
readonly bootstrap = this.projectEditor.bootstrap;
|
|
|
|
|
readonly status = this.projectEditor.status;
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
readonly dirty = this.projectEditor.dirty;
|
2026-07-14 12:21:33 +04:00
|
|
|
readonly lastSavedAt = this.projectEditor.lastSavedAt;
|
|
|
|
|
readonly lastPublishedAt = this.projectEditor.lastPublishedAt;
|
|
|
|
|
readonly validationIssues = this.projectEditor.validationIssues;
|
|
|
|
|
readonly homepageWidgets = this.projectEditor.homepageWidgets;
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
readonly modifiedFields = this.projectEditor.modifiedFields;
|
2026-07-14 12:21:33 +04:00
|
|
|
readonly metrics = this.metricsState.asReadonly();
|
|
|
|
|
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
private static readonly MAX_MODIFIED_FIELD_CHIPS = 6;
|
|
|
|
|
|
|
|
|
|
/** Human field labels (schema labelKey) for the fields the draft actually changed, capped so the widget stays scannable. */
|
|
|
|
|
readonly modifiedFieldLabelKeys = computed<string[]>(() => {
|
|
|
|
|
const keys = [...this.modifiedFields()];
|
|
|
|
|
const fields = this.schema.all();
|
|
|
|
|
return keys
|
|
|
|
|
.slice(0, AdminDashboardFacade.MAX_MODIFIED_FIELD_CHIPS)
|
|
|
|
|
.map(key => fields.find(field => field.key === key)?.labelKey ?? key);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
readonly modifiedFieldsOverflowCount = computed(() =>
|
|
|
|
|
Math.max(0, this.modifiedFields().size - AdminDashboardFacade.MAX_MODIFIED_FIELD_CHIPS),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
readonly currentUserName = this.adminAuth.displayName;
|
|
|
|
|
readonly isProductionEnvironment = environment.production;
|
|
|
|
|
|
2026-07-14 12:21:33 +04:00
|
|
|
readonly quickActions: AdminDashboardQuickAction[] = QUICK_ACTIONS;
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
readonly shortcuts: AdminDashboardShortcut[] = SHORTCUTS;
|
2026-07-14 12:21:33 +04:00
|
|
|
|
|
|
|
|
readonly enabledWidgetsCount = computed(() => this.homepageWidgets().length);
|
|
|
|
|
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
readonly staticPagesUnpublishedCount = computed(() => {
|
|
|
|
|
const staticPages = this.bootstrap()?.staticPages;
|
|
|
|
|
if (!staticPages) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
const pages = Array.isArray(staticPages) ? staticPages : Object.values(staticPages);
|
|
|
|
|
return pages.filter(page => (page as { status?: string }).status !== 'published').length;
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-14 12:21:33 +04:00
|
|
|
readonly activityEntries = computed(() => {
|
|
|
|
|
this.activityTick();
|
|
|
|
|
const tenantId = this.bootstrap()?.tenant.id;
|
|
|
|
|
return tenantId ? this.history.list(tenantId) : [];
|
|
|
|
|
});
|
|
|
|
|
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
/** Unchanged shape/logic - AdminMonitoringPageComponent also consumes this signal. */
|
2026-07-14 12:21:33 +04:00
|
|
|
readonly healthChecks = computed<AdminDashboardHealthCheck[]>(() => {
|
|
|
|
|
const current = this.bootstrap();
|
|
|
|
|
const issues = new Set(this.validationIssues().map(issue => issue.code));
|
|
|
|
|
return [
|
|
|
|
|
{ code: 'bootstrap-valid', labelKey: 'dashboard.healthBootstrapValid', healthy: !!current?.schemaVersion },
|
|
|
|
|
{ code: 'configuration-valid', labelKey: 'dashboard.healthConfigurationValid', healthy: issues.size === 0 },
|
|
|
|
|
{ code: 'missing-translations', labelKey: 'dashboard.healthMissingTranslations', healthy: !issues.has('missing-translations') },
|
|
|
|
|
{ code: 'invalid-colors', labelKey: 'dashboard.healthInvalidColors', healthy: !issues.has('invalid-colors') },
|
|
|
|
|
{ code: 'invalid-widget-references', labelKey: 'dashboard.healthInvalidWidgetReferences', healthy: !issues.has('missing-widget') },
|
|
|
|
|
{ code: 'invalid-layouts', labelKey: 'dashboard.healthInvalidLayouts', healthy: !issues.has('invalid-layouts') },
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
readonly homeHealthChecks = computed<AdminDashboardHomeHealthCheck[]>(() => {
|
|
|
|
|
const current = this.bootstrap();
|
|
|
|
|
const bootstrapLoading = !current;
|
|
|
|
|
const issues = new Set(this.validationIssues().map(issue => issue.code));
|
|
|
|
|
const metrics = this.metrics();
|
|
|
|
|
const staticPagesUnpublished = this.staticPagesUnpublishedCount();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{ code: 'configuration-valid', labelKey: 'dashboard.healthConfigurationValid', status: bootstrapLoading ? 'loading' : issues.size === 0 ? 'healthy' : 'unhealthy' },
|
|
|
|
|
{ code: 'products-count', labelKey: 'dashboard.healthProductsCount', status: metrics.status === 'loading' ? 'loading' : metrics.status === 'error' ? 'unhealthy' : 'healthy', displayValue: metrics.value ? String(metrics.value.productsCount) : null },
|
|
|
|
|
{ code: 'categories-count', labelKey: 'dashboard.healthCategoriesCount', status: metrics.status === 'loading' ? 'loading' : metrics.status === 'error' ? 'unhealthy' : 'healthy', displayValue: metrics.value ? String(metrics.value.categoriesCount) : null },
|
|
|
|
|
{ code: 'missing-translations', labelKey: 'dashboard.healthMissingTranslations', status: bootstrapLoading ? 'loading' : issues.has('missing-translations') ? 'unhealthy' : 'healthy' },
|
|
|
|
|
{ code: 'draft-exists', labelKey: 'dashboard.healthDraftExists', status: bootstrapLoading ? 'loading' : this.dirty() ? 'attention' : 'healthy' },
|
|
|
|
|
{ code: 'images-without-alt', labelKey: 'dashboard.healthImagesWithoutAlt', status: 'unknown' },
|
|
|
|
|
{ code: 'static-pages-unpublished', labelKey: 'dashboard.healthStaticPagesUnpublished', status: bootstrapLoading ? 'loading' : staticPagesUnpublished > 0 ? 'attention' : 'healthy', displayValue: bootstrapLoading ? null : String(staticPagesUnpublished) },
|
|
|
|
|
{ code: 'homepage-configured', labelKey: 'dashboard.healthHomepageConfigured', status: bootstrapLoading ? 'loading' : this.enabledWidgetsCount() > 0 ? 'healthy' : 'attention' },
|
|
|
|
|
{ code: 'theme-configured', labelKey: 'dashboard.healthThemeConfigured', status: bootstrapLoading ? 'loading' : current?.theme?.themeId ? 'healthy' : 'attention' },
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-14 12:21:33 +04:00
|
|
|
private lastRecordedSavedAt: number | null = null;
|
|
|
|
|
private lastRecordedPublishedAt: number | null = null;
|
|
|
|
|
private historyPrimed = false;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
effect(() => {
|
|
|
|
|
const tenantId = this.bootstrap()?.tenant.id;
|
|
|
|
|
const savedAt = this.lastSavedAt();
|
|
|
|
|
const publishedAt = this.lastPublishedAt();
|
|
|
|
|
if (!tenantId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.historyPrimed) {
|
|
|
|
|
this.historyPrimed = true;
|
|
|
|
|
this.lastRecordedSavedAt = savedAt;
|
|
|
|
|
this.lastRecordedPublishedAt = publishedAt;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let recorded = false;
|
|
|
|
|
if (savedAt !== null && savedAt !== this.lastRecordedSavedAt) {
|
|
|
|
|
this.lastRecordedSavedAt = savedAt;
|
|
|
|
|
this.history.record(tenantId, 'draft-saved', savedAt);
|
|
|
|
|
recorded = true;
|
|
|
|
|
}
|
|
|
|
|
if (publishedAt !== null && publishedAt !== this.lastRecordedPublishedAt) {
|
|
|
|
|
this.lastRecordedPublishedAt = publishedAt;
|
|
|
|
|
this.history.record(tenantId, 'published', publishedAt);
|
|
|
|
|
recorded = true;
|
|
|
|
|
}
|
|
|
|
|
if (recorded) {
|
|
|
|
|
this.activityTick.update(tick => tick + 1);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureLoaded(): void {
|
|
|
|
|
if (!this.bootstrap()) {
|
|
|
|
|
this.projectEditor.loadBootstrap();
|
|
|
|
|
}
|
|
|
|
|
this.loadMetrics();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadMetrics(): void {
|
|
|
|
|
this.metricsState.set({ status: 'loading', value: null });
|
|
|
|
|
this.metricsGateway.loadMetrics().pipe(take(1)).subscribe({
|
|
|
|
|
next: metrics => this.metricsState.set({
|
|
|
|
|
status: metrics.categoriesCount === 0 && metrics.productsCount === 0 ? 'empty' : 'ready',
|
|
|
|
|
value: metrics,
|
|
|
|
|
}),
|
|
|
|
|
error: () => this.metricsState.set({ status: 'error', value: null }),
|
|
|
|
|
});
|
|
|
|
|
}
|
feat(admin): redesign dashboard into professional SaaS homepage
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 08:59:29 +04:00
|
|
|
|
|
|
|
|
publishDraft(): void {
|
|
|
|
|
this.projectEditor.publish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
discardDraft(): void {
|
|
|
|
|
this.projectEditor.resetDraft();
|
|
|
|
|
}
|
2026-07-14 12:21:33 +04:00
|
|
|
}
|