feat(catalog): add reviewed bulk edits and complete category duplication
CI / check (push) Successful in 1m14s
CI / deploy (push) Successful in 1m11s

This commit is contained in:
Simo committed 2026-09-06 20:25:19 +02:00
1 parent 0bedc04692
commit 55a47949f1
26 files changed
+2504 -85

No files matched your search

@@ -17,7 +17,16 @@ import {
useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Loader2, Pencil, Plus, Power, PowerOff, Trash2 } from "lucide-react";
import {
Copy,
Loader2,
Pencil,
Plus,
Power,
PowerOff,
Trash2,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useCallback, useState } from "react";
import { toast } from "sonner";
import {
@@ -41,6 +50,8 @@ import {
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { requestCatalogNavigation } from "@/features/catalog/client/use-catalog-navigation";
import { DuplicateCategoryDialog } from "@/features/catalog/components/duplicate-category-dialog";
import type { DuplicateInput } from "@/features/catalog/domain/duplicate";
import { cn } from "@/lib/utils";
import { useCatalogManager } from "./use-catalog-store";
@@ -67,8 +78,12 @@ export function RootTabs({
loading,
onMutation,
}: RootTabsProps) {
const { catalogType, canEdit } = useCatalogManager();
const { catalogType, canEdit, state } = useCatalogManager();
const t = useTranslations("pages.admin.catalog.workspace.duplicate");
const { confirm, dialog: confirmDialog } = useConfirmDialog();
const [duplicateSource, setDuplicateSource] = useState<DuplicateInput | null>(
null,
);
const [activeDragId, setActiveDragId] = useState<number | null>(null);
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
@@ -158,6 +173,13 @@ export function RootTabs({
style={{ backgroundColor: "var(--admin-surface)" }}
>
{confirmDialog}
{duplicateSource && (
<DuplicateCategoryDialog
source={duplicateSource}
onClose={() => setDuplicateSource(null)}
onComplete={() => onMutation?.()}
/>
)}
{loading && (
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground mx-2" />
)}
@@ -180,6 +202,19 @@ export function RootTabs({
onEdit={() => onEditTab(tab.id)}
onToggle={() => handleToggle(tab)}
onDelete={() => handleDelete(tab)}
onDuplicate={() => {
if (state.editorDirty) {
toast.error(t("draftBlocked"));
return;
}
if (canEdit && requestCatalogNavigation())
setDuplicateSource({
kind: catalogType,
sourceId: tab.id,
parentId: -1,
name: `${tab.caption.slice(0, 121)} (copy)`,
});
}}
/>
))}
</SortableContext>
@@ -218,6 +253,7 @@ function SortableRootTab({
onEdit,
onToggle,
onDelete,
onDuplicate,
}: {
tab: RootTab;
isActive: boolean;
@@ -225,6 +261,7 @@ function SortableRootTab({
onEdit: () => void;
onToggle: () => void;
onDelete: () => void;
onDuplicate: () => void;
}) {
const {
attributes,
@@ -237,6 +274,8 @@ function SortableRootTab({
id: tab.id,
});
const t = useTranslations("pages.admin.catalog.workspace.duplicate");
const { canEdit } = useCatalogManager();
const style: React.CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
@@ -256,6 +295,12 @@ function SortableRootTab({
</div>
</ContextMenuTrigger>
<ContextMenuContent className="w-44">
{canEdit && (
<ContextMenuItem onClick={onDuplicate}>
<Copy className="mr-2 h-3.5 w-3.5" />
{t("title")}
</ContextMenuItem>
)}
<ContextMenuItem onClick={onEdit}>
<Pencil className="mr-2 h-3.5 w-3.5" />
Edit page