feat(catalog): add reviewed bulk edits and complete category duplication

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

@@ -34,6 +34,7 @@ import {
X,
} from "lucide-react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import {
@@ -80,6 +81,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { BulkOfferEditor } from "@/features/catalog/components/bulk-offer-editor";
import { useServerAction } from "@/hooks/use-server-action";
import { getSuggestions, recordMove } from "@/lib/move-suggestions";
import { cn } from "@/lib/utils";
@@ -167,6 +169,10 @@ export function CatalogItemsTable({
allPages,
}: CatalogItemsTableProps) {
const router = useRouter();
const bulk = useTranslations("pages.admin.catalog.bulk");
const [bulkRefreshFrom, setBulkRefreshFrom] = useState<
CatalogItemData[] | null
>(null);
const { run, isPending } = useServerAction();
const { confirm, dialog: confirmDialog } = useConfirmDialog();
const [addOpen, setAddOpen] = useState(false);
@@ -294,7 +300,26 @@ export function CatalogItemsTable({
return itemOrder.some((id, idx) => id !== originalOrder[idx]);
}, [items, itemOrder]);
const hasUnsavedChanges = modifiedItems.length > 0 || hasOrderChanges;
const hasUnsavedChanges =
bulkRefreshFrom === null && (modifiedItems.length > 0 || hasOrderChanges);
useEffect(() => {
if (bulkRefreshFrom === null || items === bulkRefreshFrom) return;
setInlineEdits(
Object.fromEntries(
items.map((item) => [
item.id,
{
costCredits: item.costCredits,
costPoints: item.costPoints,
pointsType: item.pointsType,
amount: item.amount,
},
]),
),
);
setItemOrder(items.map((item) => item.id));
setBulkRefreshFrom(null);
}, [items, bulkRefreshFrom]);
useEffect(() => {
onDirtyChange?.(hasUnsavedChanges);
}, [hasUnsavedChanges, onDirtyChange]);
@@ -904,7 +929,19 @@ export function CatalogItemsTable({
return (
<>
{confirmDialog}
<Card>
{bulkRefreshFrom !== null && (
<p
role="status"
className="flex items-center gap-2 text-sm text-muted-foreground"
>
<Loader2 className="size-4 animate-spin" />
{bulk("refreshing")}
</p>
)}
<Card
inert={bulkRefreshFrom !== null}
aria-busy={bulkRefreshFrom !== null}
>
<CardHeader className="space-y-3">
<div className="flex flex-row items-center justify-between gap-4">
<div className="min-w-0">
@@ -958,6 +995,21 @@ export function CatalogItemsTable({
<>
{selected.size > 0 && (
<>
<BulkOfferEditor
ids={[...selected]}
allPages={allPages}
disabled={
hasUnsavedChanges ||
isPending ||
savingItemId !== null
}
onApplied={() => {
setBulkRefreshFrom(items);
setSelected(new Set());
if (onRefresh) onRefresh();
else router.refresh();
}}
/>
<Button
variant="outline"
size="sm"