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:
@@ -1,43 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { PoolManager } from "./PoolManager";
|
||||
import { addPoolValue, removePoolValue } from "@/app/admin/impostazioni/actions";
|
||||
|
||||
export function OfferPoolsSection({
|
||||
tipoPool,
|
||||
obiettivoPool,
|
||||
categoriaPool,
|
||||
}: {
|
||||
tipoPool: string[];
|
||||
obiettivoPool: string[];
|
||||
categoriaPool: string[];
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] p-6">
|
||||
<h2 className="text-base font-semibold text-[#1a1a1a] mb-1">Categorie Offerta</h2>
|
||||
<p className="text-xs text-[#71717a] mb-5">
|
||||
Valori disponibili nei campi Tipo, Obiettivo e Categoria dell'editor offerte.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-6">
|
||||
<PoolManager
|
||||
label="Tipo offerta"
|
||||
pool={tipoPool}
|
||||
onAdd={(v) => addPoolValue("tipo", v)}
|
||||
onRemove={(v) => removePoolValue("tipo", v)}
|
||||
/>
|
||||
<PoolManager
|
||||
label="Obiettivo"
|
||||
pool={obiettivoPool}
|
||||
onAdd={(v) => addPoolValue("obiettivo", v)}
|
||||
onRemove={(v) => removePoolValue("obiettivo", v)}
|
||||
/>
|
||||
<PoolManager
|
||||
label="Categoria"
|
||||
pool={categoriaPool}
|
||||
onAdd={(v) => addPoolValue("categoria", v)}
|
||||
onRemove={(v) => removePoolValue("categoria", v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,10 @@ export function PoolManager({
|
||||
}
|
||||
|
||||
function handleRemove(value: string) {
|
||||
const ok = window.confirm(
|
||||
`Eliminare "${value}" da «${label}»?\n\nVerrà rimosso ovunque: sparirà dal menu e da tutte le offerte/servizi che lo usano.`
|
||||
);
|
||||
if (!ok) return;
|
||||
startTransition(() => onRemove(value));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { PoolManager } from "./PoolManager";
|
||||
import { addTaxonomyValue, removeTaxonomyValue } from "@/app/admin/impostazioni/actions";
|
||||
|
||||
type FieldDef = { id: string; label: string };
|
||||
|
||||
const OFFER_FIELDS: FieldDef[] = [
|
||||
{ id: "offer_categoria", label: "Categoria" },
|
||||
{ id: "offer_ticket", label: "Ticket" },
|
||||
{ id: "offer_tipo", label: "Tipo" },
|
||||
{ id: "offer_obiettivo", label: "Obiettivo" },
|
||||
];
|
||||
|
||||
const CATALOG_FIELDS: FieldDef[] = [
|
||||
{ id: "service_fase", label: "Fase" },
|
||||
{ id: "service_offerta", label: "Offerta" },
|
||||
{ id: "service_pacchetto", label: "Pacchetto" },
|
||||
];
|
||||
|
||||
function Section({
|
||||
title,
|
||||
subtitle,
|
||||
fields,
|
||||
pools,
|
||||
}: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
fields: FieldDef[];
|
||||
pools: Record<string, string[]>;
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] p-6">
|
||||
<h2 className="text-base font-semibold text-[#1a1a1a] mb-1">{title}</h2>
|
||||
<p className="text-xs text-[#71717a] mb-5">{subtitle}</p>
|
||||
<div className="flex flex-wrap gap-6">
|
||||
{fields.map((f) => (
|
||||
<PoolManager
|
||||
key={f.id}
|
||||
label={f.label}
|
||||
pool={pools[f.id] ?? []}
|
||||
onAdd={(v) => addTaxonomyValue(f.id, v)}
|
||||
onRemove={(v) => removeTaxonomyValue(f.id, v)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TaxonomyManager({ pools }: { pools: Record<string, string[]> }) {
|
||||
return (
|
||||
<>
|
||||
<Section
|
||||
title="Tassonomie Offerte"
|
||||
subtitle="Valori disponibili nei campi Categoria, Ticket, Tipo e Obiettivo dell'editor offerte."
|
||||
fields={OFFER_FIELDS}
|
||||
pools={pools}
|
||||
/>
|
||||
<Section
|
||||
title="Tassonomie Catalogo"
|
||||
subtitle="Valori disponibili nei campi Fase, Offerta e Pacchetto del catalogo servizi."
|
||||
fields={CATALOG_FIELDS}
|
||||
pools={pools}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user