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:
@@ -41,7 +41,9 @@ export interface AdminSession {
|
||||
}
|
||||
|
||||
export interface AdminUserAuditEntry {
|
||||
action: string;
|
||||
eventKey: 'roleChanged' | 'statusChanged';
|
||||
roleId?: string;
|
||||
status?: AdminUserStatus;
|
||||
actor: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user