feat(admin): issue #145 — delete-blocked hint + Deactivate/Edit alternative
- categories dialog: red hint with count + Deactivate button when products>0 - locations dialog: red hint + Deactivate button when categories/products>0 - catalog dialog: deleteError shown on 400/409; Deactivate for category/subcategory/location, Edit for product - Deactivate calls existing PATCH toggle, closes dialog, reloads - tsc clean
This commit is contained in:
@@ -211,6 +211,7 @@ export function CatalogPage() {
|
||||
id: number;
|
||||
name: string;
|
||||
} | null>(null);
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
|
||||
// Inline rename state
|
||||
const [renamingId, setRenamingId] = useState<string | null>(null);
|
||||
@@ -353,14 +354,38 @@ export function CatalogPage() {
|
||||
const res = await fetch(`/api/${typePath}/${deleteTarget.id}`, { method: "DELETE" });
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
throw new Error(data.error || "Failed to delete");
|
||||
const msg = data.error || "Failed to delete";
|
||||
setDeleteError(msg);
|
||||
toast.error(msg);
|
||||
return;
|
||||
}
|
||||
toast.success(`${deleteTarget.type} deleted`);
|
||||
setDeleteTarget(null);
|
||||
setDeleteError(null);
|
||||
fetchTree();
|
||||
fetchProducts();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to delete");
|
||||
const msg = e instanceof Error ? e.message : "Failed to delete";
|
||||
setDeleteError(msg);
|
||||
toast.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeactivateFromCatalog = () => {
|
||||
if (!deleteTarget) return;
|
||||
handleToggleActive(deleteTarget.type, deleteTarget.id);
|
||||
setDeleteTarget(null);
|
||||
setDeleteError(null);
|
||||
};
|
||||
|
||||
const handleEditFromCatalog = () => {
|
||||
if (!deleteTarget || deleteTarget.type !== "product") return;
|
||||
// Find the product in the current products list
|
||||
const product = products.find((p) => p.id === deleteTarget.id);
|
||||
setDeleteTarget(null);
|
||||
setDeleteError(null);
|
||||
if (product) {
|
||||
openProductModal(product);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1488,17 +1513,32 @@ export function CatalogPage() {
|
||||
</Dialog>
|
||||
|
||||
{/* ─── Delete Confirmation ─── */}
|
||||
<AlertDialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>
|
||||
<AlertDialog open={!!deleteTarget} onOpenChange={(open) => { if (!open) { setDeleteTarget(null); setDeleteError(null); } }}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete {deleteTarget?.type}?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete "{deleteTarget?.name}"? This action cannot be
|
||||
undone.
|
||||
{deleteError && (
|
||||
<span className="block mt-2 text-red-600 font-medium">
|
||||
{deleteError}
|
||||
</span>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
{deleteError && deleteTarget && deleteTarget.type !== "product" && (
|
||||
<Button variant="outline" onClick={handleDeactivateFromCatalog}>
|
||||
🔕 Deactivate
|
||||
</Button>
|
||||
)}
|
||||
{deleteError && deleteTarget && deleteTarget.type === "product" && (
|
||||
<Button variant="outline" onClick={handleEditFromCatalog}>
|
||||
✏️ Edit
|
||||
</Button>
|
||||
)}
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
|
||||
@@ -242,6 +242,12 @@ export function CategoriesPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeactivate = () => {
|
||||
if (!deleteTarget) return;
|
||||
handleToggle(deleteTarget);
|
||||
setDeleteTarget(null);
|
||||
};
|
||||
|
||||
// Group locations by country > city > district
|
||||
const groupedLocations = locations.reduce<Record<string, Record<string, Record<string, LocationItem>>>>(
|
||||
(acc, loc) => {
|
||||
@@ -469,13 +475,18 @@ export function CategoriesPage() {
|
||||
Are you sure you want to delete "{deleteTarget?.name}"? This action cannot be undone.
|
||||
{deleteTarget && deleteTarget._count.products > 0 && (
|
||||
<span className="block mt-2 text-red-600 font-medium">
|
||||
This category has {deleteTarget._count.products} product(s) and cannot be deleted.
|
||||
This category has {deleteTarget._count.products} product(s) and cannot be deleted. You can deactivate it instead.
|
||||
</span>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
{deleteTarget && deleteTarget._count.products > 0 && (
|
||||
<Button variant="outline" onClick={handleDeactivate}>
|
||||
🔕 Deactivate
|
||||
</Button>
|
||||
)}
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
disabled={deleting || (deleteTarget ? deleteTarget._count.products > 0 : true)}
|
||||
|
||||
@@ -201,6 +201,12 @@ export function LocationsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeactivate = () => {
|
||||
if (!deleteTarget) return;
|
||||
handleToggle(deleteTarget);
|
||||
setDeleteTarget(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-enter p-4 md:p-6 space-y-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
@@ -344,13 +350,18 @@ export function LocationsPage() {
|
||||
{deleteTarget && (deleteTarget._count.categories > 0 || deleteTarget._count.products > 0) && (
|
||||
<span className="block mt-2 text-red-600 font-medium">
|
||||
This location has {deleteTarget._count.categories} categor{deleteTarget._count.categories === 1 ? "y" : "ies"}
|
||||
and {deleteTarget._count.products} product(s) and cannot be deleted.
|
||||
{" "}and {deleteTarget._count.products} product(s) and cannot be deleted. You can deactivate it instead.
|
||||
</span>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
{deleteTarget && (deleteTarget._count.categories > 0 || deleteTarget._count.products > 0) && (
|
||||
<Button variant="outline" onClick={handleDeactivate}>
|
||||
🔕 Deactivate
|
||||
</Button>
|
||||
)}
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
disabled={deleting || (deleteTarget ? (deleteTarget._count.categories > 0 || deleteTarget._count.products > 0) : true)}
|
||||
|
||||
Reference in New Issue
Block a user