feat: centralized Notion-style taxonomy management in settings
Single persistent option pool per taxonomy (7 fields: offer categoria/ticket/tipo/obiettivo + catalog fase/offerta/pacchetto), stored as JSON in the settings table (no DB migration). - src/lib/taxonomy.ts: pools with lazy seed from in-use values, add/remove(cascade)/rename helpers - Inline creation anywhere registers into the pool (save offer, addServiceOption, updateServiceField fase, quickAdd, create macro) - Deselecting on a row never touches the pool; global delete only from settings (cascade-strips tags / nulls columns) - getOfferFieldOptions + getCatalogFieldOptions read from pools - Settings: TaxonomyManager (offer + catalog groups), confirm on delete Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+14
-24
@@ -24,6 +24,7 @@ import {
|
||||
tags,
|
||||
} from "@/db/schema";
|
||||
import { eq, inArray, asc, desc, isNull, sql, and } from "drizzle-orm";
|
||||
import { getPool } from "@/lib/taxonomy";
|
||||
import { LEAD_STAGES } from "@/lib/lead-validators";
|
||||
import type {
|
||||
Client,
|
||||
@@ -420,32 +421,21 @@ export type CatalogFieldOptions = {
|
||||
};
|
||||
|
||||
export async function getCatalogFieldOptions(): Promise<CatalogFieldOptions> {
|
||||
const tagRows = await db
|
||||
.selectDistinct({ name: tags.name, type: tags.entity_type })
|
||||
.from(tags)
|
||||
.where(inArray(tags.entity_type, [TAG_ENTITY, PACCHETTO_ENTITY]));
|
||||
// fase / Offerta (tag) / Pacchetto read from the centralized, persistent
|
||||
// taxonomy pools (src/lib/taxonomy.ts). `categoria` (services.category) is
|
||||
// hidden in the catalog UI and stays derived from in-use values.
|
||||
const [tag, pacchetto, fase, catRows] = await Promise.all([
|
||||
getPool("service_offerta"),
|
||||
getPool("service_pacchetto"),
|
||||
getPool("service_fase"),
|
||||
db.selectDistinct({ value: services.category }).from(services),
|
||||
]);
|
||||
|
||||
// Distinct category / fase values (single-select pools); nulls filtered below.
|
||||
const catRows = await db
|
||||
.selectDistinct({ value: services.category })
|
||||
.from(services);
|
||||
const faseRows = await db
|
||||
.selectDistinct({ value: services.fase })
|
||||
.from(services);
|
||||
const categoria = Array.from(
|
||||
new Set(catRows.map((r) => r.value).filter((v): v is string => !!v && v.trim().length > 0))
|
||||
).sort((a, b) => a.localeCompare(b, "it"));
|
||||
|
||||
const sortUnique = (arr: (string | null)[]) =>
|
||||
Array.from(new Set(arr.filter((v): v is string => !!v && v.trim().length > 0))).sort(
|
||||
(a, b) => a.localeCompare(b, "it")
|
||||
);
|
||||
|
||||
return {
|
||||
tag: sortUnique(tagRows.filter((r) => r.type === TAG_ENTITY).map((r) => r.name)),
|
||||
pacchetto: sortUnique(
|
||||
tagRows.filter((r) => r.type === PACCHETTO_ENTITY).map((r) => r.name)
|
||||
),
|
||||
categoria: sortUnique(catRows.map((r) => r.value)),
|
||||
fase: sortUnique(faseRows.map((r) => r.value)),
|
||||
};
|
||||
return { tag, pacchetto, categoria, fase };
|
||||
}
|
||||
|
||||
// ── ProjectWithPayments — used by /admin/projects list ───────────────────────
|
||||
|
||||
Reference in New Issue
Block a user