Files
clienthub/src/components/admin/impostazioni/TaxonomyManager.tsx
T
simone 3fcb10dac6 feat(impostazioni): rinomina di un valore di tassonomia, fasi dei progetti incluse
Dalle impostazioni si poteva solo aggiungere o eliminare un valore: per
cambiargli nome bisognava cancellarlo — strappandolo via da ogni servizio che
lo usava — e ricrearlo a mano. La matita nel chip fa il rename in un passo.

renamePoolValue esisteva gia' e propagava ovunque, tranne in un punto:
importOfferIntoProject copia services.fase dentro phases.title e poi ritrova
la fase confrontando i titoli (phases.offer_phase_id non viene mai popolata,
quindi il titolo e' l'unico legame). Un rename fermo al catalogo lasciava le
fasi dei progetti col vecchio nome e al re-import ne nasceva una duplicata.
Ora propaga anche li', con lo stesso match trim+lowercase del merge.

E' l'unico rename di tassonomia che scrive fuori dal dominio catalogo/offerte,
quindi e' l'unico che chiede conferma, dicendo quante fasi e quanti progetti
sta per toccare.

Tolte anche le UPDATE manuali in renameServiceOption/renameOfferOption: erano
la stessa scrittura che renamePoolValue faceva subito dopo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:08:20 +02:00

109 lines
3.7 KiB
TypeScript

"use client";
import { Tag, BookOpen } from "lucide-react";
import { PoolManager } from "./PoolManager";
import {
addTaxonomyValue,
getFaseRenameImpact,
removeTaxonomyValue,
renameTaxonomyValue,
} 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" },
];
// Renaming a Fase also rewrites `phases.title` in projects that already imported
// it — the only taxonomy rename that touches delivery data, so it asks first and
// says exactly how much it will touch.
async function faseRenameWarning(oldValue: string, newValue: string): Promise<string | null> {
const { phases, projects } = await getFaseRenameImpact(oldValue);
if (phases === 0) return null;
const fasi = phases === 1 ? "1 fase" : `${phases} fasi`;
const prog = projects === 1 ? "1 progetto" : `${projects} progetti`;
return `Rinominare "${oldValue}" in "${newValue}"?\n\nVerrà aggiornato anche il titolo di ${fasi} in ${prog} già avviati.`;
}
function Section({
icon,
title,
subtitle,
fields,
pools,
gridClass,
}: {
icon: React.ReactNode;
title: string;
subtitle: string;
fields: FieldDef[];
pools: Record<string, string[]>;
gridClass: string;
}) {
return (
<section className="rounded-xl border border-border-light bg-card p-6 shadow-card">
<div className="mb-6 flex items-start gap-3">
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-border-light bg-muted text-muted-foreground">
{icon}
</div>
<div>
<h2 className="text-xs font-bold uppercase tracking-wider text-foreground">{title}</h2>
<p className="mt-0.5 text-xs text-tertiary">{subtitle}</p>
</div>
</div>
<div className={`grid gap-6 border-t border-border-light pt-4 ${gridClass}`}>
{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)}
onRename={(o, n) => renameTaxonomyValue(f.id, o, n)}
getRenameWarning={f.id === "service_fase" ? faseRenameWarning : undefined}
/>
))}
</div>
</section>
);
}
export function TaxonomyManager({ pools }: { pools: Record<string, string[]> }) {
return (
<div className="space-y-8">
<Section
icon={<Tag className="h-5 w-5" />}
title="Tassonomie Offerte"
subtitle="Valori dei campi Categoria, Ticket, Tipo e Obiettivo dell'editor offerte."
fields={OFFER_FIELDS}
pools={pools}
gridClass="grid-cols-1 md:grid-cols-2"
/>
<Section
icon={<BookOpen className="h-5 w-5" />}
title="Tassonomie Catalogo"
subtitle="Valori dei campi Fase, Offerta e Pacchetto del catalogo servizi."
fields={CATALOG_FIELDS}
pools={pools}
gridClass="grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
/>
<p className="max-w-4xl px-2 text-xs leading-relaxed text-tertiary">
I valori si sincronizzano ovunque: creandone uno dall&apos;editor offerte o dal catalogo
comparirà qui automaticamente. Rinominando un valore da qui il nuovo nome sostituisce il
vecchio ovunque sia usato; eliminandolo viene rimosso ovunque.
</p>
</div>
);
}