fix(backoffice): wording quality pass across orders, moderation, transactions, users, customers

- Replaced hardcoded English audit/timeline text (order status changes,
  review moderation events, user role/status changes) with proper
  adminXxx.timelineEvent.*/adminUsers.audit.* i18n keys, so Recent
  Activity/Timeline/Audit panels no longer mix English into ru/hy UI.
- Translated raw internal codes rendered directly to users: transaction
  payment method ('card'/'qr'/'cash_on_delivery' -> adminTransactions.methodValue.*)
  and user roles/permissions ('products.manage' etc -> adminUsers.roleValue.*/
  adminUsers.permission.*), replacing developer-facing enum leakage with
  real copy.
- Fixed wrong-noun list-footer counts: Orders/Transactions/Moderation
  list pages all reused adminProducts.items ("N товаров"/"N products")
  regardless of what was actually listed; each now has its own itemsCount
  key ("N заказов", "N транзакций", "N отзывов").
- Fixed customer detail page's "Back" button reusing adminOrders.back
  ("Back to orders") instead of a customers-specific label.
- Added translation keys to en/ru/hy + translations.ts interface for all
  of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-25 21:20:37 +04:00
parent 72846b44b1
commit b909a195f7
28 changed files with 311 additions and 42 deletions

View File

@@ -1,7 +1,7 @@
@if (facade.selected(); as customer) {
<main class="customer-detail">
<header class="toolbar">
<app-button variant="secondary" size="sm" (click)="back()">{{ 'adminOrders.back' | translate }}</app-button>
<app-button variant="secondary" size="sm" (click)="back()">{{ 'adminCustomers.back' | translate }}</app-button>
<h1>{{ customer.name }}</h1>
@if (customer.orderCount > 1) {
<app-badge variant="success">{{ 'adminCustomers.returning' | translate }}</app-badge>

View File

@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { AdminCustomersFacade } from '../facade/admin-customers.facade';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { TranslateService } from '../../../../i18n/translate.service';
import { LanguageService } from '../../../../services/language.service';
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
import { BadgeComponent } from '../../../../shared/ui/badge/badge.component';
@@ -21,12 +22,20 @@ export class AdminCustomerDetailPageComponent {
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly languageService = inject(LanguageService);
private readonly translate = inject(TranslateService);
readonly activity = computed<OrderTimelineEntry[]>(() => {
const customer = this.facade.selected();
if (!customer) return [];
return customer.orders
.flatMap(order => order.timeline.map(entry => ({ status: entry.status, timestamp: entry.timestamp, note: entry.note, orderNumber: order.orderNumber })))
.flatMap(order => order.timeline.map(entry => ({
status: entry.status,
timestamp: entry.timestamp,
note: this.translate.t('adminOrders.timelineEvent.' + entry.eventKey, {
status: this.translate.t('adminOrders.status.' + entry.status),
}),
orderNumber: order.orderNumber,
})))
.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
});

View File

@@ -16,7 +16,7 @@
} @else {
<ul>
@for (entry of stats().recentActivity; track entry.timestamp + entry.reviewId) {
<li>{{ entry.timestamp | date:'short' }} — {{ entry.productName }} — {{ entry.action }}</li>
<li>{{ entry.timestamp | date:'short' }} — {{ entry.productName }} — {{ activityText(entry) }}</li>
}
</ul>
}

View File

@@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core';
import { DatePipe } from '@angular/common';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
import { TranslateService } from '../../../../../i18n/translate.service';
import { CardComponent } from '../../../../../shared/ui/card/card.component';
import { DashboardMetricComponent } from '../../../dashboard/components/dashboard-metric.component';
import { AdminModerationDashboardStats } from '../../facade/admin-moderation.facade';
@@ -14,5 +15,12 @@ import { AdminModerationDashboardStats } from '../../facade/admin-moderation.fac
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModerationDashboardComponent {
private readonly translate = inject(TranslateService);
readonly stats = input.required<AdminModerationDashboardStats>();
activityText(entry: AdminModerationDashboardStats['recentActivity'][number]): string {
const status = entry.status ? this.translate.t('adminModeration.status.' + entry.status) : '';
return this.translate.t('adminModeration.timelineEvent.' + entry.eventKey, { status });
}
}

View File

@@ -28,7 +28,7 @@ export interface AdminModerationDashboardStats {
reported: number;
spam: number;
averageRating: number | null;
recentActivity: { reviewId: string; productName: string; action: string; timestamp: string }[];
recentActivity: { reviewId: string; productName: string; eventKey: string; status?: AdminReviewStatus; timestamp: string }[];
moderationHealthPercent: number;
}
@@ -197,7 +197,7 @@ export class AdminModerationFacade {
const averageRating = rated.length > 0 ? Math.round((rated.reduce((sum, review) => sum + review.rating, 0) / rated.length) * 10) / 10 : null;
const recentActivity = reviews
.flatMap(review => review.timeline.map(entry => ({ reviewId: review.id, productName: review.productName, action: entry.action, timestamp: entry.timestamp })))
.flatMap(review => review.timeline.map(entry => ({ reviewId: review.id, productName: review.productName, eventKey: entry.eventKey, status: entry.status, timestamp: entry.timestamp })))
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
.slice(0, 8);

View File

@@ -1,8 +1,11 @@
export type AdminReviewStatus = 'pending' | 'approved' | 'rejected' | 'spam';
export type AdminReviewTimelineEventKey = 'submitted' | 'statusChanged' | 'restored' | 'hidden';
export type AdminReviewTimelineActor = 'admin' | 'customer';
export interface AdminReviewTimelineEntry {
action: string;
actor: string;
eventKey: AdminReviewTimelineEventKey;
actor: AdminReviewTimelineActor;
status?: AdminReviewStatus;
note: string;
timestamp: string;
}

View File

@@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { AdminModerationFacade } from '../facade/admin-moderation.facade';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { TranslateService } from '../../../../i18n/translate.service';
import { LanguageService } from '../../../../services/language.service';
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
import { BadgeComponent } from '../../../../shared/ui/badge/badge.component';
@@ -22,6 +23,7 @@ export class AdminReviewDetailPageComponent {
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly languageService = inject(LanguageService);
private readonly translate = inject(TranslateService);
readonly noteDraft = signal('');
@@ -34,7 +36,14 @@ export class AdminReviewDetailPageComponent {
timelineEntries(): OrderTimelineEntry[] {
const review = this.facade.selected();
return review ? review.timeline.map(entry => ({ timestamp: entry.timestamp, note: `${entry.action}${entry.note ? ' — ' + entry.note : ''} (${entry.actor})` })) : [];
if (!review) return [];
return review.timeline.map(entry => {
const status = entry.status ? this.translate.t('adminModeration.status.' + entry.status) : '';
const eventText = this.translate.t('adminModeration.timelineEvent.' + entry.eventKey, { status });
const actor = this.translate.t('adminModeration.actor.' + entry.actor);
const note = entry.note ? `${eventText}${entry.note}` : eventText;
return { timestamp: entry.timestamp, note: `${note} (${actor})` };
});
}
back(): void {

View File

@@ -127,7 +127,7 @@
@if (facade.reviews().length > 0) {
<div class="pager">
<span>{{ facade.total() }} {{ 'adminProducts.items' | translate }}</span>
<span>{{ facade.total() }} {{ 'adminModeration.itemsCount' | translate }}</span>
<app-pagination [currentPage]="facade.filters().page" [totalPages]="totalPages()" (pageChange)="facade.updateFilters({ page: $event })" />
</div>
}

View File

@@ -3,7 +3,7 @@ import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { BackofficeDataService } from '../../../../core/backoffice/backoffice-data.service';
import { ProductCardConfig } from '../../../../shared/models/ui';
import { AdminReview, AdminReviewListFilters, AdminReviewsListResult, AdminReviewStatus } from '../models/admin-review.model';
import { AdminReview, AdminReviewListFilters, AdminReviewsListResult, AdminReviewStatus, AdminReviewTimelineEventKey } from '../models/admin-review.model';
import { AdminReport, AdminReportStatus } from '../models/admin-report.model';
import { AdminModerationGateway } from './admin-moderation-gateway.interface';
@@ -61,16 +61,17 @@ export class AdminModerationLocalGateway implements AdminModerationGateway {
...review,
status,
updatedAt: new Date().toISOString(),
timeline: [...review.timeline, { action: `Status set to ${status}`, actor: 'Admin', note, timestamp: new Date().toISOString() }],
timeline: [...review.timeline, { eventKey: 'statusChanged' as const, status, actor: 'admin' as const, note, timestamp: new Date().toISOString() }],
}));
}
setReviewVisible(id: string, visible: boolean): Observable<AdminReview | null> {
const eventKey: AdminReviewTimelineEventKey = visible ? 'restored' : 'hidden';
return this.mutate(id, review => ({
...review,
visible,
updatedAt: new Date().toISOString(),
timeline: [...review.timeline, { action: visible ? 'Restored' : 'Hidden', actor: 'Admin', note: '', timestamp: new Date().toISOString() }],
timeline: [...review.timeline, { eventKey, actor: 'admin' as const, note: '', timestamp: new Date().toISOString() }],
}));
}
@@ -158,7 +159,7 @@ export class AdminModerationLocalGateway implements AdminModerationGateway {
featured: false,
reportCount,
moderatorNotes: '',
timeline: [{ action: 'Submitted', actor: 'Customer', note: '', timestamp: createdAt }],
timeline: [{ eventKey: 'submitted' as const, actor: 'customer' as const, note: '', timestamp: createdAt }],
createdAt,
updatedAt: createdAt,
};

View File

@@ -5,6 +5,7 @@ import { TranslatePipe } from '../../../../../i18n/translate.pipe';
export interface OrderTimelineEntry {
status?: string;
timestamp: string;
/** Pre-resolved, already-translated display text for this event. */
note: string;
orderNumber?: string;
}

View File

@@ -25,7 +25,7 @@
} @else {
<ul>
@for (entry of stats().recentActivity; track entry.timestamp + entry.orderId) {
<li>{{ entry.timestamp | date:'short' }} — {{ entry.orderNumber }} — {{ entry.note }}</li>
<li>{{ entry.timestamp | date:'short' }} — {{ entry.orderNumber }} — {{ activityText(entry) }}</li>
}
</ul>
}

View File

@@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core';
import { DatePipe } from '@angular/common';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
import { TranslateService } from '../../../../../i18n/translate.service';
import { CardComponent } from '../../../../../shared/ui/card/card.component';
import { BadgeComponent } from '../../../../../shared/ui/badge/badge.component';
import { DashboardMetricComponent } from '../../../dashboard/components/dashboard-metric.component';
@@ -15,5 +16,13 @@ import { AdminOrdersDashboardStats } from '../../facade/admin-orders.facade';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrdersDashboardComponent {
private readonly translate = inject(TranslateService);
readonly stats = input.required<AdminOrdersDashboardStats>();
activityText(entry: AdminOrdersDashboardStats['recentActivity'][number]): string {
return this.translate.t('adminOrders.timelineEvent.' + entry.eventKey, {
status: this.translate.t('adminOrders.status.' + entry.status),
});
}
}

View File

@@ -20,7 +20,7 @@ export interface AdminOrdersDashboardStats {
returningCustomers: number;
averageOrder: number;
currency: string;
recentActivity: { orderNumber: string; orderId: string; note: string; status: AdminOrderStatus; timestamp: string }[];
recentActivity: { orderNumber: string; orderId: string; eventKey: string; status: AdminOrderStatus; timestamp: string }[];
alerts: { labelKey: string; count: number }[];
}
@@ -99,12 +99,12 @@ export class AdminOrdersFacade {
this.gateway.loadOrder(id).pipe(take(1)).subscribe({ next: order => this.selected.set(order) });
}
setStatus(id: string, status: AdminOrderStatus, note: string): void {
this.gateway.updateStatus(id, status, note).pipe(take(1)).subscribe({ next: order => this.selected.set(order) });
setStatus(id: string, status: AdminOrderStatus): void {
this.gateway.updateStatus(id, status).pipe(take(1)).subscribe({ next: order => this.selected.set(order) });
}
cancelOrder(id: string): void {
this.setStatus(id, 'cancelled', 'Cancelled by admin');
this.setStatus(id, 'cancelled');
}
requestRefund(id: string): void {
@@ -118,7 +118,7 @@ export class AdminOrdersFacade {
applyBulkStatus(status: AdminOrderStatus): void {
const ids = [...this.selectedIds()];
ids.forEach(id => this.gateway.updateStatus(id, status, `Bulk status change to ${status}`).pipe(take(1)).subscribe());
ids.forEach(id => this.gateway.updateStatus(id, status).pipe(take(1)).subscribe());
this.clearSelection();
this.loadList();
this.loadDashboardStats();
@@ -185,7 +185,7 @@ export class AdminOrdersFacade {
const averageOrder = orders.length > 0 ? Math.round(total / orders.length) : 0;
const recentActivity = orders
.flatMap(order => order.timeline.map(entry => ({ orderNumber: order.orderNumber, orderId: order.id, note: entry.note, status: entry.status, timestamp: entry.timestamp })))
.flatMap(order => order.timeline.map(entry => ({ orderNumber: order.orderNumber, orderId: order.id, eventKey: entry.eventKey, status: entry.status, timestamp: entry.timestamp })))
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
.slice(0, 8);

View File

@@ -27,10 +27,12 @@ export interface AdminOrderItem {
price: number;
}
export type AdminOrderTimelineEventKey = 'created' | 'statusChanged' | 'refundRequested';
export interface AdminOrderTimelineEntry {
status: AdminOrderStatus;
timestamp: string;
note: string;
eventKey: AdminOrderTimelineEventKey;
}
export interface AdminOrder {

View File

@@ -68,7 +68,7 @@
<section class="card">
<h3>{{ 'adminOrders.timeline' | translate }}</h3>
<app-order-timeline [entries]="order.timeline" />
<app-order-timeline [entries]="timelineEntries()" />
</section>
<section class="grid two no-print">

View File

@@ -9,7 +9,7 @@ import { TranslateService } from '../../../../i18n/translate.service';
import { LanguageService } from '../../../../services/language.service';
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
import { BadgeComponent } from '../../../../shared/ui/badge/badge.component';
import { OrderTimelineComponent } from '../components/order-timeline/order-timeline.component';
import { OrderTimelineComponent, OrderTimelineEntry } from '../components/order-timeline/order-timeline.component';
const WORKFLOW_STEPS: AdminOrderStatus[] = ['pending', 'processing', 'shipped', 'delivered'];
const TERMINAL_STATUSES: AdminOrderStatus[] = ['cancelled', 'refunded'];
@@ -45,6 +45,18 @@ export class AdminOrderDetailPageComponent {
return WORKFLOW_STEPS.indexOf(order.status);
});
readonly timelineEntries = computed<OrderTimelineEntry[]>(() => {
const order = this.facade.selected();
if (!order) return [];
return order.timeline.map(entry => ({
status: entry.status,
timestamp: entry.timestamp,
note: this.translate.t('adminOrders.timelineEvent.' + entry.eventKey, {
status: this.translate.t('adminOrders.status.' + entry.status),
}),
}));
});
constructor() {
const id = this.route.snapshot.paramMap.get('id');
if (id) {
@@ -61,7 +73,7 @@ export class AdminOrderDetailPageComponent {
}
setStatus(id: string, status: AdminOrderStatus): void {
this.facade.setStatus(id, status, `Status changed to ${status}`);
this.facade.setStatus(id, status);
}
cancel(id: string): void {

View File

@@ -89,7 +89,7 @@
</div>
<div class="pager">
<span>{{ facade.total() }} {{ 'adminProducts.items' | translate }}</span>
<span>{{ facade.total() }} {{ 'adminOrders.itemsCount' | translate }}</span>
<app-pagination [currentPage]="facade.filters().page" [totalPages]="totalPages()" (pageChange)="facade.updateFilters({ page: $event })" />
</div>
}

View File

@@ -4,7 +4,7 @@ import { AdminOrder, AdminOrderListFilters, AdminOrdersListResult, AdminOrderSta
export interface AdminOrdersGateway {
loadOrders(filters: AdminOrderListFilters): Observable<AdminOrdersListResult>;
loadOrder(id: string): Observable<AdminOrder | null>;
updateStatus(id: string, status: AdminOrderStatus, note: string): Observable<AdminOrder | null>;
updateStatus(id: string, status: AdminOrderStatus): Observable<AdminOrder | null>;
requestRefund(id: string): Observable<AdminOrder | null>;
addNote(id: string, note: string, internal: boolean): Observable<AdminOrder | null>;
archiveOrder(id: string): Observable<AdminOrder | null>;

View File

@@ -31,12 +31,12 @@ export class AdminOrdersLocalGateway implements AdminOrdersGateway {
return of(this.ensureData().find(order => order.id === id) ?? null).pipe(delay(50));
}
updateStatus(id: string, status: AdminOrderStatus, note: string): Observable<AdminOrder | null> {
updateStatus(id: string, status: AdminOrderStatus): Observable<AdminOrder | null> {
return this.mutate(id, order => ({
...order,
status,
updatedAt: new Date().toISOString(),
timeline: [...order.timeline, { status, timestamp: new Date().toISOString(), note }],
timeline: [...order.timeline, { status, timestamp: new Date().toISOString(), eventKey: 'statusChanged' as const }],
}));
}
@@ -45,7 +45,7 @@ export class AdminOrdersLocalGateway implements AdminOrdersGateway {
...order,
payment: { ...order.payment, status: 'refund_requested' },
updatedAt: new Date().toISOString(),
timeline: [...order.timeline, { status: order.status, timestamp: new Date().toISOString(), note: 'Refund requested' }],
timeline: [...order.timeline, { status: order.status, timestamp: new Date().toISOString(), eventKey: 'refundRequested' as const }],
}));
}
@@ -122,8 +122,8 @@ export class AdminOrdersLocalGateway implements AdminOrdersGateway {
notes: '',
internalNotes: '',
timeline: [
{ status: 'pending', timestamp: createdAt, note: 'Order created' },
...(status !== 'pending' ? [{ status, timestamp: createdAt, note: `Status set to ${status}` }] : []),
{ status: 'pending', timestamp: createdAt, eventKey: 'created' },
...(status !== 'pending' ? [{ status, timestamp: createdAt, eventKey: 'statusChanged' as const }] : []),
],
archived: false,
createdAt,

View File

@@ -42,7 +42,7 @@
<tr>
<th scope="row">{{ tx.orderNumber }}</th>
<td>{{ ('adminTransactions.typeValue.' + tx.type) | translate }}</td>
<td>{{ tx.method }}</td>
<td>{{ ('adminTransactions.methodValue.' + tx.method) | translate }}</td>
<td><app-badge [variant]="tx.status === 'success' ? 'success' : tx.status === 'failed' ? 'danger' : 'neutral'">{{ ('adminTransactions.status.' + tx.status) | translate }}</app-badge></td>
<td>{{ tx.amount }} {{ tx.currency }}</td>
<td>
@@ -63,7 +63,7 @@
</app-table>
<div class="pager">
<span>{{ facade.total() }} {{ 'adminProducts.items' | translate }}</span>
<span>{{ facade.total() }} {{ 'adminTransactions.itemsCount' | translate }}</span>
<app-pagination [currentPage]="facade.filters().page" [totalPages]="totalPages()" (pageChange)="facade.updateFilters({ page: $event })" />
</div>
}

View File

@@ -41,7 +41,9 @@ export interface AdminSession {
}
export interface AdminUserAuditEntry {
action: string;
eventKey: 'roleChanged' | 'statusChanged';
roleId?: string;
status?: AdminUserStatus;
actor: string;
timestamp: string;
}

View File

@@ -28,7 +28,7 @@
<td>
<select [attr.aria-label]="'adminUsers.role' | translate" [ngModel]="user.roleId" (ngModelChange)="facade.setRole(user.id, $event)">
@for (role of facade.roles(); track role.id) {
<option [value]="role.id">{{ role.name }}</option>
<option [value]="role.id">{{ roleLabel(role.id) }}</option>
}
</select>
</td>
@@ -53,7 +53,7 @@
<app-input type="email" [ngModel]="inviteEmail()" (ngModelChange)="inviteEmail.set($event)" [placeholder]="'adminUsers.email' | translate" />
<select [attr.aria-label]="'adminUsers.role' | translate" [ngModel]="inviteRoleId()" (ngModelChange)="inviteRoleId.set($event)">
@for (role of facade.roles(); track role.id) {
<option [value]="role.id">{{ role.name }}</option>
<option [value]="role.id">{{ roleLabel(role.id) }}</option>
}
</select>
<select [attr.aria-label]="'adminUsers.scope' | translate" [ngModel]="inviteScope()" (ngModelChange)="inviteScope.set($event)">
@@ -77,7 +77,7 @@
@for (invite of facade.invitations(); track invite.id) {
<tr>
<th scope="row">{{ invite.email }}</th>
<td>{{ facade.roleName(invite.roleId) }}</td>
<td>{{ roleLabel(invite.roleId) }}</td>
<td><app-badge variant="neutral">{{ ('adminUsers.invitationStatus.' + invite.status) | translate }}</app-badge></td>
<td>
@if (invite.status === 'pending') {
@@ -96,7 +96,7 @@
<div class="card">
<h2>{{ 'adminUsers.roles' | translate }}</h2>
@for (role of facade.roles(); track role.id) {
<p><strong>{{ role.name }}</strong> — {{ role.permissions.join(', ') }}</p>
<p><strong>{{ roleLabel(role.id) }}</strong> — {{ role.permissions.map(permissionLabel).join(', ') }}</p>
}
</div>
@@ -111,7 +111,7 @@
<app-dialog [open]="!!facade.auditTarget()" [titleText]="'adminTransactions.audit' | translate" size="sm" (closed)="facade.closeAudit()">
@for (entry of facade.audit(); track $index) {
<p>{{ entry.timestamp | date:'short' }} — {{ entry.actor }} — {{ entry.action }}</p>
<p>{{ entry.timestamp | date:'short' }} — {{ ('adminUsers.actor.' + entry.actor) | translate }} — {{ auditText(entry) }}</p>
}
</app-dialog>
</section>

View File

@@ -45,4 +45,31 @@ export class AdminUsersPageComponent {
}
this.facade.setStatus(userId, next);
}
private static readonly PERMISSION_KEYS: Record<string, string> = {
'*': 'all',
'products.manage': 'productsManage',
'products.view': 'productsView',
'categories.manage': 'categoriesManage',
'orders.manage': 'ordersManage',
'orders.view': 'ordersView',
'media.manage': 'mediaManage',
};
readonly roleLabel = (roleId: string): string => this.translate.t('adminUsers.roleValue.' + roleId);
readonly permissionLabel = (code: string): string => {
const key = AdminUsersPageComponent.PERMISSION_KEYS[code];
return key ? this.translate.t('adminUsers.permission.' + key) : code;
};
auditText(entry: { eventKey: 'roleChanged' | 'statusChanged'; roleId?: string; status?: AdminUserStatus }): string {
if (entry.eventKey === 'roleChanged' && entry.roleId) {
return this.translate.t('adminUsers.audit.roleChanged', { role: this.roleLabel(entry.roleId) });
}
if (entry.eventKey === 'statusChanged' && entry.status) {
return this.translate.t('adminUsers.audit.statusChanged', { status: this.translate.t('adminUsers.statusValue.' + entry.status) });
}
return '';
}
}

View File

@@ -89,7 +89,9 @@ export class AdminUsersLocalGateway implements AdminUsersGateway {
this.users = users.map(user => user.id === userId ? updated : user);
this.audit[userId] = [
...(this.audit[userId] ?? []),
{ action: roleId ? `Role changed to ${roleId}` : `Status changed to ${status}`, actor: 'admin', timestamp: new Date().toISOString() },
roleId
? { eventKey: 'roleChanged', roleId, actor: 'admin', timestamp: new Date().toISOString() }
: { eventKey: 'statusChanged', status, actor: 'admin', timestamp: new Date().toISOString() },
];
return of(updated).pipe(delay(50));
}