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>
This commit is contained in:
2026-08-20 16:08:20 +02:00
parent 97cc6460a0
commit 3fcb10dac6
6 changed files with 168 additions and 32 deletions
@@ -2,7 +2,12 @@
import { Tag, BookOpen } from "lucide-react";
import { PoolManager } from "./PoolManager";
import { addTaxonomyValue, removeTaxonomyValue } from "@/app/admin/impostazioni/actions";
import {
addTaxonomyValue,
getFaseRenameImpact,
removeTaxonomyValue,
renameTaxonomyValue,
} from "@/app/admin/impostazioni/actions";
type FieldDef = { id: string; label: string };
@@ -19,6 +24,17 @@ const CATALOG_FIELDS: FieldDef[] = [
{ 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,
@@ -54,6 +70,8 @@ function Section({
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>
@@ -82,7 +100,8 @@ export function TaxonomyManager({ pools }: { pools: Record<string, string[]> })
/>
<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. Eliminando un valore da qui viene rimosso ovunque sia usato.
comparirà qui automaticamente. Rinominando un valore da qui il nuovo nome sostituisce il
vecchio ovunque sia usato; eliminandolo viene rimosso ovunque.
</p>
</div>
);