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,33 +1,42 @@
|
||||
"use server";
|
||||
|
||||
import { getJsonPool, updateJsonPool, SETTINGS_KEYS } from "@/lib/settings";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import {
|
||||
addPoolValue,
|
||||
removePoolValue,
|
||||
TAXONOMY_FIELDS,
|
||||
type TaxonomyFieldId,
|
||||
} from "@/lib/taxonomy";
|
||||
|
||||
type PoolName = "tipo" | "obiettivo" | "categoria";
|
||||
|
||||
function poolKey(pool: PoolName): string {
|
||||
switch (pool) {
|
||||
case "tipo": return SETTINGS_KEYS.OFFER_TIPO_POOL;
|
||||
case "obiettivo": return SETTINGS_KEYS.OFFER_OBIETTIVO_POOL;
|
||||
case "categoria": return SETTINGS_KEYS.OFFER_CATEGORIA_POOL;
|
||||
}
|
||||
async function requireAdmin() {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) throw new Error("Non autorizzato");
|
||||
}
|
||||
|
||||
export async function addPoolValue(pool: PoolName, value: string): Promise<void> {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return;
|
||||
const key = poolKey(pool);
|
||||
const current = await getJsonPool(key);
|
||||
if (current.includes(trimmed)) return;
|
||||
await updateJsonPool(key, [...current, trimmed]);
|
||||
function assertField(fieldId: string): asserts fieldId is TaxonomyFieldId {
|
||||
if (!(fieldId in TAXONOMY_FIELDS)) throw new Error(`Tassonomia non valida: ${fieldId}`);
|
||||
}
|
||||
|
||||
function revalidateAll() {
|
||||
revalidatePath("/admin/impostazioni");
|
||||
revalidatePath("/admin/offers");
|
||||
revalidatePath("/admin/catalog");
|
||||
}
|
||||
|
||||
export async function removePoolValue(pool: PoolName, value: string): Promise<void> {
|
||||
const key = poolKey(pool);
|
||||
const current = await getJsonPool(key);
|
||||
await updateJsonPool(key, current.filter((v) => v !== value));
|
||||
revalidatePath("/admin/impostazioni");
|
||||
revalidatePath("/admin/offers");
|
||||
export async function addTaxonomyValue(fieldId: string, value: string): Promise<void> {
|
||||
await requireAdmin();
|
||||
assertField(fieldId);
|
||||
await addPoolValue(fieldId, value);
|
||||
revalidateAll();
|
||||
}
|
||||
|
||||
// Global delete: removes the value from the pool AND cascade-strips it from every
|
||||
// offer/service using it.
|
||||
export async function removeTaxonomyValue(fieldId: string, value: string): Promise<void> {
|
||||
await requireAdmin();
|
||||
assertField(fieldId);
|
||||
await removePoolValue(fieldId, value);
|
||||
revalidateAll();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user