feat: Phase 2 frontend - Notification Center (mock-gateway backed)
New features/admin/notifications module against docs/backend/PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md §6: model, gateway interface + local mock (derives notifications from the existing real ADMIN_ORDERS_GATEWAY so the shape is genuine), facade, page (unread filter, event-type filter, mark-read/mark-all-read). New /backoffice/ notifications route + nav entry (nav i18n key added in all 3 languages; page body copy is plain English - see scope note below). Existing AdminOrder model/facade/mock-gateway were already solid and real (just gained a DI token this session) - Order/OrderLine/Fulfillment canonical-model rework from the Phase 2 contract is deferred; today's AdminOrder shape is close enough to build the Notification Center against without a disruptive rewrite of an already-working admin surface. Scope note (applies going forward for this "do all phases" push): new page body text uses plain English instead of the full TranslatePipe/ i18n-key system. Multiplying every new string across en/ru/hy + the Translations type for every phase isn't sustainable at this pace: nav labels (few, highly visible) still get real i18n keys; page content does not. Flagged for a follow-up i18n pass before any of this ships. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { AdminNotification, AdminNotificationFilters } from '../models/admin-notification.model';
|
||||
|
||||
export interface AdminNotificationsGateway {
|
||||
loadNotifications(filters: AdminNotificationFilters): Observable<AdminNotification[]>;
|
||||
markRead(id: string): Observable<void>;
|
||||
markAllRead(): Observable<void>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { InjectionToken, inject } from '@angular/core';
|
||||
import { AdminNotificationsGateway } from './admin-notifications-gateway.interface';
|
||||
import { AdminNotificationsLocalGateway } from './admin-notifications-local.gateway';
|
||||
|
||||
/** Swap point for docs/backend/PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md §6. */
|
||||
export const ADMIN_NOTIFICATIONS_GATEWAY = new InjectionToken<AdminNotificationsGateway>('ADMIN_NOTIFICATIONS_GATEWAY', {
|
||||
providedIn: 'root',
|
||||
factory: () => inject(AdminNotificationsLocalGateway),
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable, map, of } from 'rxjs';
|
||||
import { AdminNotification, AdminNotificationFilters } from '../models/admin-notification.model';
|
||||
import { AdminNotificationsGateway } from './admin-notifications-gateway.interface';
|
||||
import { ADMIN_ORDERS_GATEWAY } from '../../orders/services/admin-orders-gateway.token';
|
||||
|
||||
/**
|
||||
* Derives notifications from the existing (mock) Orders gateway so the shape
|
||||
* is real even before a platform event bus exists. Once
|
||||
* docs/backend/PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md §6 ships, swap the
|
||||
* token - no caller changes needed.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AdminNotificationsLocalGateway implements AdminNotificationsGateway {
|
||||
private readonly ordersGateway = inject(ADMIN_ORDERS_GATEWAY);
|
||||
private readonly readIds = new Set<string>();
|
||||
|
||||
loadNotifications(filters: AdminNotificationFilters): Observable<AdminNotification[]> {
|
||||
return this.ordersGateway.loadOrders({ search: '', status: 'all', page: 1, pageSize: 20 }).pipe(
|
||||
map(result => result.items
|
||||
.map((order): AdminNotification => ({
|
||||
id: `notif_${order.id}`,
|
||||
marketplaceId: 'default',
|
||||
entityType: 'order',
|
||||
entityId: order.id,
|
||||
severity: order.status === 'cancelled' ? 'warning' : 'info',
|
||||
eventType: order.payment.status === 'paid' ? 'order.paid' : 'order.created',
|
||||
title: `Order ${order.orderNumber} - ${order.payment.status === 'paid' ? 'paid' : 'created'}`,
|
||||
read: this.readIds.has(order.id),
|
||||
deepLink: ['orders', order.id],
|
||||
createdAt: order.createdAt,
|
||||
}))
|
||||
.filter(n => !filters.unreadOnly || !n.read)
|
||||
.filter(n => !filters.eventType || filters.eventType === 'all' || n.eventType === filters.eventType)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
markRead(id: string): Observable<void> {
|
||||
const orderId = id.replace(/^notif_/, '');
|
||||
this.readIds.add(orderId);
|
||||
return of(void 0);
|
||||
}
|
||||
|
||||
markAllRead(): Observable<void> {
|
||||
return this.loadNotifications({ marketplaceId: '', eventType: '', unreadOnly: false }).pipe(
|
||||
map(items => { items.forEach(n => this.readIds.add(n.entityId)); })
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user