feat(studio): add organize-imports tool with suggestion review and approval
Adds a Studio 'Organize imports' dialog that groups recently imported furniture and furniture already sitting in the auto-created import pages into suggested catalog categories. Each group is presented with its suggested name, icon, and layout which can be overridden before approval; approved groups are turned into real catalog pages in a single atomic export run. Offers already inside the import subtree are moved to the new pages; brand-new furniture gets a fresh offer. - groupSuggestedCategories: generic pure helper reusing the same per-item label heuristic that drives suggestCategoryName; items with no label land in a 'Other Furni' remainder bucket - GET /api/admin/import/organize: returns items from the imported furniture tree (catalog_items JOIN items_base via page id set from the imported-furniture root) union audit-logged but not-yet-placed recent imports; marks alreadyPlaced vs new - organizeImportFurni server action: validates import-page membership before moving any offer, creates pages + inserts/moves items in one withCatalogExport snapshot, logs activity - OrganizeImportsDialog: full-featured Studio dialog with price panel (applies to new offers only), parent select, per-group approval, editable name/icon/layout with suggestion reset chips, source tags - import-pages.ts server helper: locates the imported-furniture tree - Studio nav: 'Organize imports' button with FolderTree icon, gated on CATALOG_EDIT, wired next to the catalog manager - en + nl translations for organizeImports.* keys with ICU plurals - groupSuggestedCategories unit tests (deterministic grouping, remainder handling, size+alpha ordering, label consistency)
This commit is contained in:
1 parent
f5c2c05f6e
commit
01a85ebcd0
9 files changed
+1230
-15
No files matched your search
@@ -7,6 +7,7 @@ import {
|
||||
ClipboardCheck,
|
||||
Copy,
|
||||
Dumbbell,
|
||||
FolderTree,
|
||||
Image as ImageIcon,
|
||||
type LucideIcon,
|
||||
Puzzle,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { CatalogManagerDialog } from "@/app/admin/catalog/catalog-manager-dialog-wrapper";
|
||||
import { OrganizeImportsDialog } from "@/components/admin/studio/organize-imports-dialog";
|
||||
import Link from "@/components/link";
|
||||
import {
|
||||
Popover,
|
||||
@@ -113,6 +115,7 @@ export function StudioLayoutClient({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const pathname = usePathname() ?? "";
|
||||
const [organizeOpen, setOrganizeOpen] = useState(false);
|
||||
return (
|
||||
<div className="flex h-[calc(100dvh-7rem)] min-h-[600px] min-w-0 flex-col overflow-hidden rounded-xl border border-[var(--admin-border)] bg-[var(--admin-canvas)] text-[var(--admin-text)]">
|
||||
<nav
|
||||
@@ -144,11 +147,21 @@ export function StudioLayoutClient({
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{canEditCatalog && (
|
||||
<CatalogManagerDialog
|
||||
catalogType="normal"
|
||||
canEdit
|
||||
triggerLabel="Catalog manager"
|
||||
/>
|
||||
<>
|
||||
<CatalogManagerDialog
|
||||
catalogType="normal"
|
||||
canEdit
|
||||
triggerLabel="Catalog manager"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOrganizeOpen(true)}
|
||||
className="flex min-h-10 items-center gap-2 rounded-lg px-3 text-sm font-medium text-[var(--admin-text-muted)] hover:bg-[var(--admin-accent)]/10"
|
||||
>
|
||||
<FolderTree size={16} />
|
||||
Organize imports
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<StudioMenu
|
||||
label="Import & sync"
|
||||
@@ -163,6 +176,12 @@ export function StudioLayoutClient({
|
||||
</div>
|
||||
</nav>
|
||||
<div className="min-h-0 min-w-0 flex-1 overflow-auto">{children}</div>
|
||||
{canEditCatalog && (
|
||||
<OrganizeImportsDialog
|
||||
open={organizeOpen}
|
||||
onOpenChange={setOrganizeOpen}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user