fix: category bulk-delete had no confirmation, single-delete used native dialogs
Bulk-delete called facade.applyBulkDelete() straight from the button with zero confirmation. Single-delete had a confirm gate but via raw window.confirm/alert instead of the themed dialog used elsewhere. Migrated both to app-confirm-dialog (single + new bulk), and the delete-blocked message to a themed app-dialog instead of window.alert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,18 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { AdminCategoriesFacade } from '../facade/admin-categories.facade';
|
import { AdminCategoriesFacade } from '../facade/admin-categories.facade';
|
||||||
import { AdminCategoriesListComponent } from '../components/admin-categories-list.component';
|
import { AdminCategoriesListComponent } from '../components/admin-categories-list.component';
|
||||||
import { LanguageService } from '../../../../services/language.service';
|
import { LanguageService } from '../../../../services/language.service';
|
||||||
import { TranslateService } from '../../../../i18n/translate.service';
|
import { TranslateService } from '../../../../i18n/translate.service';
|
||||||
|
import { ConfirmDialogComponent } from '../../../../shared/ui/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { DialogComponent } from '../../../../shared/ui/dialog/dialog.component';
|
||||||
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||||
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin-categories-list-page',
|
selector: 'app-admin-categories-list-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AdminCategoriesListComponent],
|
imports: [AdminCategoriesListComponent, ConfirmDialogComponent, DialogComponent, ButtonComponent, TranslatePipe],
|
||||||
template: `<app-admin-categories-list
|
template: `<app-admin-categories-list
|
||||||
[treeRows]="facade.visibleTreeRows()"
|
[treeRows]="facade.visibleTreeRows()"
|
||||||
[flatCategories]="facade.filteredCategories()"
|
[flatCategories]="facade.filteredCategories()"
|
||||||
@@ -37,14 +41,36 @@ import { TranslateService } from '../../../../i18n/translate.service';
|
|||||||
(selectionChange)="facade.toggleSelection($event.id, $event.checked)"
|
(selectionChange)="facade.toggleSelection($event.id, $event.checked)"
|
||||||
(selectAll)="facade.toggleAll($event)"
|
(selectAll)="facade.toggleAll($event)"
|
||||||
(bulkVisibility)="facade.applyBulkVisibility($event)"
|
(bulkVisibility)="facade.applyBulkVisibility($event)"
|
||||||
(bulkDelete)="facade.applyBulkDelete()"
|
(bulkDelete)="bulkDelete()"
|
||||||
(bulkDuplicate)="facade.applyBulkDuplicate()"
|
(bulkDuplicate)="facade.applyBulkDuplicate()"
|
||||||
(bulkAssignParent)="facade.applyBulkAssignParent($event)"
|
(bulkAssignParent)="facade.applyBulkAssignParent($event)"
|
||||||
(bulkAssignImage)="facade.applyBulkAssignImage($event)"
|
(bulkAssignImage)="facade.applyBulkAssignImage($event)"
|
||||||
(bulkExport)="facade.exportSelectedAsCsv()"
|
(bulkExport)="facade.exportSelectedAsCsv()"
|
||||||
(viewModeChange)="facade.setViewMode($event)"
|
(viewModeChange)="facade.setViewMode($event)"
|
||||||
(densityChange)="facade.setDensity($event)"
|
(densityChange)="facade.setDensity($event)"
|
||||||
(columnToggle)="facade.setColumnVisible($event.column, $event.visible)" />`,
|
(columnToggle)="facade.setColumnVisible($event.column, $event.visible)" />
|
||||||
|
<app-confirm-dialog
|
||||||
|
[open]="!!pendingDeleteId()"
|
||||||
|
[titleText]="'adminCategories.deleteTitle' | translate"
|
||||||
|
[message]="'adminCategories.confirmDelete' | translate"
|
||||||
|
[destructive]="true"
|
||||||
|
(confirmed)="confirmDelete()"
|
||||||
|
(cancelled)="pendingDeleteId.set(null)" />
|
||||||
|
<app-confirm-dialog
|
||||||
|
[open]="bulkDeleteConfirmOpen()"
|
||||||
|
[titleText]="'adminCategories.deleteTitle' | translate"
|
||||||
|
[message]="'adminCategories.confirmBulkDelete' | translate"
|
||||||
|
[destructive]="true"
|
||||||
|
(confirmed)="confirmBulkDelete()"
|
||||||
|
(cancelled)="bulkDeleteConfirmOpen.set(false)" />
|
||||||
|
@if (deleteBlockedMessage()) {
|
||||||
|
<app-dialog [open]="true" [titleText]="'adminCategories.deleteBlocked' | translate" size="sm" (closed)="deleteBlockedMessage.set(null)">
|
||||||
|
<p>{{ deleteBlockedMessage() }}</p>
|
||||||
|
<div class="app-confirm-dialog__actions">
|
||||||
|
<app-button variant="primary" (click)="deleteBlockedMessage.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-dialog>
|
||||||
|
}`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class AdminCategoriesListPageComponent {
|
export class AdminCategoriesListPageComponent {
|
||||||
@@ -57,6 +83,10 @@ export class AdminCategoriesListPageComponent {
|
|||||||
readonly isExpandedFn = (id: string) => this.facade.isExpanded(id);
|
readonly isExpandedFn = (id: string) => this.facade.isExpanded(id);
|
||||||
readonly canDeleteFn = (id: string) => this.facade.canDelete(id);
|
readonly canDeleteFn = (id: string) => this.facade.canDelete(id);
|
||||||
|
|
||||||
|
readonly pendingDeleteId = signal<string | null>(null);
|
||||||
|
readonly bulkDeleteConfirmOpen = signal(false);
|
||||||
|
readonly deleteBlockedMessage = signal<string | null>(null);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.facade.loadList();
|
this.facade.loadList();
|
||||||
this.facade.loadDashboardStats();
|
this.facade.loadDashboardStats();
|
||||||
@@ -75,12 +105,27 @@ export class AdminCategoriesListPageComponent {
|
|||||||
|
|
||||||
deleteOne(id: string): void {
|
deleteOne(id: string): void {
|
||||||
if (!this.facade.canDelete(id)) {
|
if (!this.facade.canDelete(id)) {
|
||||||
window.alert(this.translate.t('adminCategories.deleteBlocked'));
|
this.deleteBlockedMessage.set(this.translate.t('adminCategories.deleteBlocked'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (window.confirm(this.translate.t('adminCategories.confirmDelete'))) {
|
this.pendingDeleteId.set(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDelete(): void {
|
||||||
|
const id = this.pendingDeleteId();
|
||||||
|
if (id) {
|
||||||
this.facade.deleteOne(id);
|
this.facade.deleteOne(id);
|
||||||
}
|
}
|
||||||
|
this.pendingDeleteId.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkDelete(): void {
|
||||||
|
this.bulkDeleteConfirmOpen.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmBulkDelete(): void {
|
||||||
|
this.facade.applyBulkDelete();
|
||||||
|
this.bulkDeleteConfirmOpen.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private lang(): string {
|
private lang(): string {
|
||||||
|
|||||||
@@ -1549,6 +1549,8 @@ export const en: Translations = {
|
|||||||
saveDraft: 'Save draft',
|
saveDraft: 'Save draft',
|
||||||
publish: 'Publish',
|
publish: 'Publish',
|
||||||
confirmDelete: 'Delete this category? This cannot be undone.',
|
confirmDelete: 'Delete this category? This cannot be undone.',
|
||||||
|
confirmBulkDelete: 'Delete the selected categories? This cannot be undone.',
|
||||||
|
deleteTitle: 'Delete category',
|
||||||
confirmLeaveUnsaved: 'You have unsaved changes. Leave without saving?',
|
confirmLeaveUnsaved: 'You have unsaved changes. Leave without saving?',
|
||||||
deleteBlocked: 'This category can\'t be deleted while it has subcategories or assigned products.',
|
deleteBlocked: 'This category can\'t be deleted while it has subcategories or assigned products.',
|
||||||
emptyTitle: 'No categories yet',
|
emptyTitle: 'No categories yet',
|
||||||
|
|||||||
@@ -1544,6 +1544,8 @@ export const hy: Translations = {
|
|||||||
saveDraft: 'Պահպանել սևագիրը',
|
saveDraft: 'Պահպանել սևագիրը',
|
||||||
publish: 'Հրապարակել',
|
publish: 'Հրապարակել',
|
||||||
confirmDelete: 'Ջնջե՞լ այս կատեգորիան։ Հնարավոր չէ հետարկել։',
|
confirmDelete: 'Ջնջե՞լ այս կատեգորիան։ Հնարավոր չէ հետարկել։',
|
||||||
|
confirmBulkDelete: 'Ջնջե՞լ ընտրված կատեգորիաները։ Հնարավոր չէ հետարկել։',
|
||||||
|
deleteTitle: 'Ջնջել կատեգորիան',
|
||||||
confirmLeaveUnsaved: 'Դուք ունեք չպահպանված փոփոխություններ։ Դո՞ւրս գալ առանց պահպանելու։',
|
confirmLeaveUnsaved: 'Դուք ունեք չպահպանված փոփոխություններ։ Դո՞ւրս գալ առանց պահպանելու։',
|
||||||
deleteBlocked: 'Այս կատեգորիան հնարավոր չէ ջնջել, քանի դեռ ունի ենթակատեգորիաներ կամ նշանակված ապրանքներ։',
|
deleteBlocked: 'Այս կատեգորիան հնարավոր չէ ջնջել, քանի դեռ ունի ենթակատեգորիաներ կամ նշանակված ապրանքներ։',
|
||||||
emptyTitle: 'Կատեգորիաներ դեռ չկան',
|
emptyTitle: 'Կատեգորիաներ դեռ չկան',
|
||||||
|
|||||||
@@ -1544,6 +1544,8 @@ export const ru: Translations = {
|
|||||||
saveDraft: 'Сохранить черновик',
|
saveDraft: 'Сохранить черновик',
|
||||||
publish: 'Опубликовать',
|
publish: 'Опубликовать',
|
||||||
confirmDelete: 'Удалить эту категорию? Это действие нельзя отменить.',
|
confirmDelete: 'Удалить эту категорию? Это действие нельзя отменить.',
|
||||||
|
confirmBulkDelete: 'Удалить выбранные категории? Это действие нельзя отменить.',
|
||||||
|
deleteTitle: 'Удалить категорию',
|
||||||
confirmLeaveUnsaved: 'У вас есть несохранённые изменения. Выйти без сохранения?',
|
confirmLeaveUnsaved: 'У вас есть несохранённые изменения. Выйти без сохранения?',
|
||||||
deleteBlocked: 'Эту категорию нельзя удалить, пока у неё есть подкатегории или назначенные товары.',
|
deleteBlocked: 'Эту категорию нельзя удалить, пока у неё есть подкатегории или назначенные товары.',
|
||||||
emptyTitle: 'Категорий пока нет',
|
emptyTitle: 'Категорий пока нет',
|
||||||
|
|||||||
@@ -1557,6 +1557,8 @@ export interface Translations {
|
|||||||
saveDraft: string;
|
saveDraft: string;
|
||||||
publish: string;
|
publish: string;
|
||||||
confirmDelete: string;
|
confirmDelete: string;
|
||||||
|
confirmBulkDelete: string;
|
||||||
|
deleteTitle: string;
|
||||||
confirmLeaveUnsaved: string;
|
confirmLeaveUnsaved: string;
|
||||||
deleteBlocked: string;
|
deleteBlocked: string;
|
||||||
emptyTitle: string;
|
emptyTitle: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user