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
@@ -0,0 +1,47 @@
|
||||
import "server-only";
|
||||
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
/**
|
||||
* Locate the auto-created "Imported Furniture" tree (caption_save
|
||||
* 'imported_furni' root) and every category sub-page beneath it. These are the
|
||||
* pages the import pipeline fills without review, so the organize-import tool
|
||||
* knows exactly which furniture is "inbox" work.
|
||||
*/
|
||||
export async function getImportedCategoryPageIds(): Promise<number[]> {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT id FROM catalog_pages
|
||||
WHERE caption_save = 'imported_furni'
|
||||
AND parent_id IN (-1, 0)
|
||||
LIMIT 1
|
||||
`)) as unknown as [Array<{ id: number }>, unknown];
|
||||
const rootId = Number(rows[0]?.id ?? 0);
|
||||
if (!rootId) return [];
|
||||
|
||||
const [childRows] = (await db.execute(sql`
|
||||
SELECT id FROM catalog_pages WHERE parent_id = ${rootId}
|
||||
`)) as unknown as [Array<{ id: number }>, unknown];
|
||||
const children = childRows.map((r) => Number(r.id));
|
||||
|
||||
const pageIds = [rootId, ...children];
|
||||
if (children.length > 0) {
|
||||
const [grandRows] = (await db.execute(sql`
|
||||
SELECT id FROM catalog_pages
|
||||
WHERE parent_id IN (${sql.join(children, sql`, `)})
|
||||
`)) as unknown as [Array<{ id: number }>, unknown];
|
||||
pageIds.push(...grandRows.map((r) => Number(r.id)));
|
||||
}
|
||||
return pageIds;
|
||||
}
|
||||
|
||||
/** Caption of the imported-furniture root page, when present. */
|
||||
export async function getImportedRootCaption(): Promise<string | null> {
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT caption FROM catalog_pages
|
||||
WHERE caption_save = 'imported_furni'
|
||||
AND parent_id IN (-1, 0)
|
||||
LIMIT 1
|
||||
`)) as unknown as [Array<{ caption: string }>, unknown];
|
||||
return rows[0]?.caption ?? null;
|
||||
}
|
||||
Reference in new issue
Block a user