diff --git a/src/app/features/admin/shell/admin-layout.component.html b/src/app/features/admin/shell/admin-layout.component.html
index 9396f75..c2229e7 100644
--- a/src/app/features/admin/shell/admin-layout.component.html
+++ b/src/app/features/admin/shell/admin-layout.component.html
@@ -148,10 +148,28 @@
(click)="toggleNotifications()"
>
+ @if (unreadCount() > 0) {
+ {{ unreadCount() }}
+ }
@if (notificationsOpen()) {
-
{{ 'adminShell.topbar.notificationsEmpty' | translate }}
+ @if (recentOrders().length === 0) {
+
{{ 'adminShell.topbar.notificationsEmpty' | translate }}
+ } @else {
+ @for (order of recentOrders(); track order.id) {
+
+ }
+ }
}
diff --git a/src/app/features/admin/shell/admin-layout.component.scss b/src/app/features/admin/shell/admin-layout.component.scss
index 0cb80fd..1bd2613 100644
--- a/src/app/features/admin/shell/admin-layout.component.scss
+++ b/src/app/features/admin/shell/admin-layout.component.scss
@@ -347,6 +347,21 @@
position: relative;
}
+.admin-layout__notifications-badge {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ min-width: 16px;
+ height: 16px;
+ padding: 0 4px;
+ border-radius: 999px;
+ background: var(--color-danger, #ef4444);
+ color: #fff;
+ font-size: 10px;
+ line-height: 16px;
+ text-align: center;
+}
+
.admin-layout__notifications-panel {
position: absolute;
right: 0;
@@ -366,6 +381,27 @@
}
}
+.admin-layout__notification-item {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ width: 100%;
+ padding: 8px 10px;
+ border: none;
+ background: none;
+ text-align: left;
+ cursor: pointer;
+ border-radius: var(--radius-sm, 4px);
+}
+
+.admin-layout__notification-item:hover {
+ background: var(--bg-secondary, #f4f6f5);
+}
+
+.admin-layout__notification-order { font-weight: var(--font-weight-medium, 500); }
+.admin-layout__notification-customer { color: var(--text-secondary, #6b7280); font-size: var(--font-size-sm, 0.8125rem); }
+.admin-layout__notification-amount { font-size: var(--font-size-sm, 0.8125rem); }
+
.admin-layout__account {
display: flex;
align-items: center;
diff --git a/src/app/features/admin/shell/admin-layout.component.spec.ts b/src/app/features/admin/shell/admin-layout.component.spec.ts
new file mode 100644
index 0000000..3adb993
--- /dev/null
+++ b/src/app/features/admin/shell/admin-layout.component.spec.ts
@@ -0,0 +1,76 @@
+import { TestBed } from '@angular/core/testing';
+import { provideRouter } from '@angular/router';
+import { signal } from '@angular/core';
+import { AdminLayoutComponent } from './admin-layout.component';
+import { AdminOrderWatcherService } from './services/admin-order-watcher.service';
+import { AdminOrder } from '../orders/models/admin-order.model';
+
+function makeOrder(id: string, orderNumber: string): AdminOrder {
+ return {
+ id,
+ orderNumber,
+ status: 'pending',
+ customer: { name: 'Test Customer', email: 't@example.com', phone: '' },
+ payment: { method: 'card', status: 'paid', amount: 500, currency: 'RUB' },
+ shipping: { address: '', method: '', trackingNumber: '' },
+ items: [],
+ total: 500,
+ currency: 'RUB',
+ notes: '',
+ internalNotes: '',
+ timeline: [],
+ archived: false,
+ createdAt: '2026-08-15T10:00:00.000Z',
+ updatedAt: '2026-08-15T10:00:00.000Z',
+ };
+}
+
+describe('AdminLayoutComponent notifications bell', () => {
+ let watcherStub: {
+ recentOrders: ReturnType>;
+ unreadCount: ReturnType>;
+ start: jasmine.Spy;
+ markAllSeen: jasmine.Spy;
+ };
+
+ beforeEach(() => {
+ watcherStub = {
+ recentOrders: signal([makeOrder('o1', '1001')]),
+ unreadCount: signal(1),
+ start: jasmine.createSpy('start'),
+ markAllSeen: jasmine.createSpy('markAllSeen'),
+ };
+
+ TestBed.configureTestingModule({
+ imports: [AdminLayoutComponent],
+ providers: [
+ provideRouter([]),
+ { provide: AdminOrderWatcherService, useValue: watcherStub },
+ ],
+ });
+ });
+
+ it('starts the watcher once on construction', () => {
+ TestBed.createComponent(AdminLayoutComponent);
+ expect(watcherStub.start).toHaveBeenCalledTimes(1);
+ });
+
+ it('exposes unreadCount and recentOrders from the watcher', () => {
+ const fixture = TestBed.createComponent(AdminLayoutComponent);
+ const component = fixture.componentInstance;
+ expect(component.unreadCount()).toBe(1);
+ expect(component.recentOrders().map(o => o.id)).toEqual(['o1']);
+ });
+
+ it('marks orders seen when the notifications panel opens', () => {
+ const fixture = TestBed.createComponent(AdminLayoutComponent);
+ const component = fixture.componentInstance;
+ component.toggleNotifications();
+ expect(component.notificationsOpen()).toBe(true);
+ expect(watcherStub.markAllSeen).toHaveBeenCalledTimes(1);
+
+ component.toggleNotifications();
+ expect(component.notificationsOpen()).toBe(false);
+ expect(watcherStub.markAllSeen).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/app/features/admin/shell/admin-layout.component.ts b/src/app/features/admin/shell/admin-layout.component.ts
index db12d00..db783e8 100644
--- a/src/app/features/admin/shell/admin-layout.component.ts
+++ b/src/app/features/admin/shell/admin-layout.component.ts
@@ -12,6 +12,7 @@ import { IconComponent } from '../../../shared/ui/icon/icon.component';
import { AdminPreferencesService } from '../settings/services/admin-preferences.service';
import { UiRuntimeFacade } from '../../../facades/runtime/ui-runtime.facade';
import { ConfigService } from '../../../core/config/config.service';
+import { AdminOrderWatcherService } from './services/admin-order-watcher.service';
@Component({
selector: 'app-admin-layout',
@@ -31,6 +32,10 @@ export class AdminLayoutComponent {
private readonly preferences = inject(AdminPreferencesService);
private readonly uiRuntime = inject(UiRuntimeFacade);
private readonly configService = inject(ConfigService);
+ private readonly orderWatcher = inject(AdminOrderWatcherService);
+
+ readonly unreadCount = this.orderWatcher.unreadCount;
+ readonly recentOrders = this.orderWatcher.recentOrders;
readonly density = this.preferences.density;
@@ -87,6 +92,7 @@ export class AdminLayoutComponent {
constructor() {
this.readRouteData();
+ this.orderWatcher.start();
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd),
@@ -130,6 +136,9 @@ export class AdminLayoutComponent {
toggleNotifications(): void {
this.notificationsOpen.update(open => !open);
+ if (this.notificationsOpen()) {
+ this.orderWatcher.markAllSeen();
+ }
}
adminLinkFor(entry: Extract): string[] {
@@ -140,6 +149,11 @@ export class AdminLayoutComponent {
return ['/', lang, 'backoffice', ...(entry.path ?? [])];
}
+ goToOrder(orderId: string): void {
+ void this.router.navigate([this.currentLang(), 'backoffice', 'orders', orderId]);
+ this.notificationsOpen.set(false);
+ }
+
breadcrumbLink(entry: AdminBreadcrumbEntry): string[] {
return ['/', this.currentLang(), 'backoffice', ...(entry.path ?? [])];
}