Refactor comuni and provincie data structures; add dynamic FormDebug component

- Updated `initializeComuniNames` to accept an optional `provincia` parameter for filtering comuni by province.
- Renamed `getComuniNames` to `getComuni` and modified it to return an array of objects containing both comune names and their respective province siglas.
- Introduced `provincieWithSigla` array to map province names to their siglas, along with a new function `getProvinciaFromSigla` for retrieving province names based on sigla.
- Added a dynamic import for the `FormDebug` component to facilitate debugging in development mode while preventing it from rendering in production.
- Commented out console logs in `checkFiscalCodeValidity` for cleaner production output.
This commit is contained in:
Marco Pedone 2025-10-30 18:13:50 +01:00
parent da7683ba45
commit 967935f0a8
7 changed files with 1159 additions and 1012 deletions

View file

@ -11,7 +11,6 @@ import {
} from "react-hook-form"; } from "react-hook-form";
import { Label } from "src/components/ui/label"; import { Label } from "src/components/ui/label";
import { cn } from "src/lib/utils"; import { cn } from "src/lib/utils";
import { env } from "~/env";
const Form = FormProvider; const Form = FormProvider;
@ -171,23 +170,6 @@ const FormMessage = React.forwardRef<
}); });
FormMessage.displayName = "FormMessage"; FormMessage.displayName = "FormMessage";
const FormDebug = React.forwardRef<
React.ElementType,
React.HTMLAttributes<HTMLDivElement>
>(async () => {
if (env.NODE_ENV !== "development") {
return null;
}
const { control, formState } = useFormContext();
const devtools = await import(/* webpackIgnore: true */ "@hookform/devtools");
if (Object.keys(formState.errors).length > 0) {
console.error("errors", formState.errors);
}
return <devtools.DevTool control={control} />;
});
FormDebug.displayName = "FormDebug";
export { export {
useFormField, useFormField,
Form, Form,
@ -197,5 +179,4 @@ export {
FormDescription, FormDescription,
FormMessage, FormMessage,
FormField, FormField,
FormDebug,
}; };

View file

@ -1,7 +0,0 @@
import dynamic from "next/dynamic";
import type React from "react";
export const DevT: React.ElementType = dynamic(
() => import("@hookform/devtools").then((mod) => mod.DevTool),
{ ssr: false },
);

View file

@ -3,6 +3,7 @@ import { format } from "date-fns";
import { enUS, it } from "date-fns/locale"; import { enUS, it } from "date-fns/locale";
import { CalendarIcon, HelpCircleIcon, Mail } from "lucide-react"; import { CalendarIcon, HelpCircleIcon, Mail } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { type Control, useWatch } from "react-hook-form";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { Counter } from "~/components/counter"; import { Counter } from "~/components/counter";
@ -48,9 +49,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "~/components/ui/tooltip"; } from "~/components/ui/tooltip";
import { getComuniNames } from "~/i18n/comuni"; import { getComuni } from "~/i18n/comuni";
import { GetNazioni } from "~/i18n/nazioni"; import { GetNazioni } from "~/i18n/nazioni";
import { provincie } from "~/i18n/provincie"; import { getProvinciaFromSigla, provincieWithSigla } from "~/i18n/provincie";
import { checkFiscalCodeValidity, getComuneFieldValue } from "~/lib/form_utils"; import { checkFiscalCodeValidity, getComuneFieldValue } from "~/lib/form_utils";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
@ -67,6 +68,53 @@ type InitialData = {
anagrafica: UsersAnagrafica | undefined; anagrafica: UsersAnagrafica | undefined;
}; };
const FormNewServizioSchema = z.object({
animali: z.boolean(),
arredato: z.boolean(),
ascensore: z.boolean(),
azienda: z.boolean(),
budget: z.number().positive(),
cap_recapiti: z.string().length(5),
citta_recapiti: z.string().nonempty("Inserisci una città"),
civico_recapiti: z.string().nonempty("Inserisci un civico"),
codice_destinatario: z.string().nullable(),
codice_fiscale: z.string(),
cognome: z.string().nonempty("Inserisci un cognome valido"),
data_nascita: z.date(),
email: z.email(),
entromese: z.number().nullable(),
fatturazione_aziendale: z.boolean(),
fumatori: z.boolean(),
giardino: z.boolean(),
indirizzo_recapiti: z.string().nonempty("Inserisci un indirizzo"),
legale_rappresentante: z.string().nullable(),
luogo_nascita: z.string(),
motivazione_transitorio: z.string().nullable(),
n_adulti: z.number().min(1),
n_minori: z.number(),
nazione_nascita: z.string(),
nazione_recapiti: z
.string()
.refine((v) => v !== "", { params: { type: "nazione" } }),
nome: z.string().nonempty("Inserisci un nome valido"),
p_iva: z.string().nullable(),
parcheggio: z.boolean(),
permanenza: z.number().nullable(),
pianoterra: z.boolean(),
provincia_recapiti: z
.string()
.refine((v) => v !== "", { params: { type: "provincia" } }),
ragione_sociale: z.string().nullable(),
reddito: z.string().nullable(),
sede_legale: z.string().nullable(),
sesso: z.string(),
skipOrdine: z.boolean(),
telefono: z.string(),
terrazzo: z.boolean(),
tipologia: z.custom<TipologiaPosizioneEnum>(),
});
type FormNewServizioValues = z.infer<typeof FormNewServizioSchema>;
export const FormNewServizioAcquisto = ({ export const FormNewServizioAcquisto = ({
initialData, initialData,
userId, userId,
@ -77,198 +125,150 @@ export const FormNewServizioAcquisto = ({
isAdmin?: boolean; isAdmin?: boolean;
}) => { }) => {
const router = useRouter(); const router = useRouter();
const Schema = z const schema = FormNewServizioSchema.superRefine(
.object({ (
animali: z.boolean(), {
arredato: z.boolean(), telefono,
ascensore: z.boolean(), codice_fiscale,
azienda: z.boolean(), sesso,
budget: z.number().positive(), cognome,
cap_recapiti: z.string().length(5), nome,
citta_recapiti: z.string().nonempty("Inserisci una città"), data_nascita,
civico_recapiti: z.string().nonempty("Inserisci un civico"), luogo_nascita,
codice_destinatario: z.string().nullable(), nazione_nascita,
codice_fiscale: z.string(), n_adulti,
cognome: z.string().nonempty("Inserisci un cognome valido"), permanenza,
data_nascita: z.date(), entromese,
email: z.email(), motivazione_transitorio,
entromese: z.number().nullable(), reddito,
fatturazione_aziendale: z.boolean(), azienda,
fumatori: z.boolean(), p_iva,
giardino: z.boolean(), sede_legale,
indirizzo_recapiti: z.string().nonempty("Inserisci un indirizzo"), legale_rappresentante,
legale_rappresentante: z.string().nullable(), ragione_sociale,
luogo_nascita: z.string(), fatturazione_aziendale,
motivazione_transitorio: z.string().nullable(), codice_destinatario,
n_adulti: z.number().min(1), },
n_minori: z.number(), refinementContext,
nazione_nascita: z.string(), ) => {
nazione_recapiti: z if (entromese === null) {
.string() refinementContext.issues.push({
.refine((v) => v !== "", { params: { type: "nazione" } }), code: "custom",
nome: z.string().nonempty("Inserisci un nome valido"), input: "",
p_iva: z.string().nullable(), message: "Inserisci il mese di inizio",
parcheggio: z.boolean(), path: ["entromese"],
permanenza: z.number().nullable(),
pianoterra: z.boolean(),
provincia_recapiti: z
.string()
.refine((v) => v !== "", { params: { type: "provincia" } }),
ragione_sociale: z.string().nullable(),
reddito: z.string().nullable(),
sede_legale: z.string().nullable(),
sesso: z.string(),
skipOrdine: z.boolean(),
telefono: z.string(),
terrazzo: z.boolean(),
tipologia: z.custom<TipologiaPosizioneEnum>(),
})
.superRefine(
(
{
telefono,
codice_fiscale,
sesso,
cognome,
nome,
data_nascita,
luogo_nascita,
nazione_nascita,
n_adulti,
permanenza,
entromese,
motivazione_transitorio,
reddito,
azienda,
p_iva,
sede_legale,
legale_rappresentante,
ragione_sociale,
fatturazione_aziendale,
codice_destinatario,
},
refinementContext,
) => {
if (entromese === null) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci il mese di inizio",
path: ["entromese"],
});
}
if (azienda) {
if (!ragione_sociale || ragione_sociale.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la ragione sociale",
path: ["ragione_sociale"],
});
}
if (!sede_legale || sede_legale.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la sede legale",
path: ["sede_legale"],
});
}
if (!p_iva || p_iva.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la partita iva",
path: ["p_iva"],
});
}
if (!legale_rappresentante || legale_rappresentante.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci il legale rappresentante",
path: ["legale_rappresentante"],
});
}
}
if (fatturazione_aziendale) {
if (!codice_destinatario || codice_destinatario.length !== 7) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci un codice destinatario valido",
path: ["codice_destinatario"],
});
}
}
if (!telefono || telefono.length < 10) {
refinementContext.issues.push({
code: "custom",
input: "",
message:
"Il numero di telefono deve essere lungo almeno 10 caratteri",
});
}
checkFiscalCodeValidity({
codiceFiscale: codice_fiscale,
cognome,
dataNascita: data_nascita,
luogoNascita: luogo_nascita,
nazioneNascita: nazione_nascita,
nome,
path: "codice_fiscale",
refinementContext,
sesso,
}); });
if (n_adulti <= 0) { }
if (azienda) {
if (!ragione_sociale || ragione_sociale.length < 1) {
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
input: "", input: "",
message: "Inserisci il numero di persone", message: "Inserisci la ragione sociale",
path: ["n_adulti"], path: ["ragione_sociale"],
}); });
} }
if (!sede_legale || sede_legale.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la sede legale",
path: ["sede_legale"],
});
}
if (!p_iva || p_iva.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la partita iva",
path: ["p_iva"],
});
}
if (!legale_rappresentante || legale_rappresentante.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci il legale rappresentante",
path: ["legale_rappresentante"],
});
}
}
if (fatturazione_aziendale) {
if (!codice_destinatario || codice_destinatario.length !== 7) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci un codice destinatario valido",
path: ["codice_destinatario"],
});
}
}
if (!telefono || telefono.length < 10) {
refinementContext.issues.push({
code: "custom",
input: "",
message:
"Il numero di telefono deve essere lungo almeno 10 caratteri",
});
}
checkFiscalCodeValidity({
codiceFiscale: codice_fiscale,
cognome,
dataNascita: data_nascita,
luogoNascita: luogo_nascita,
nazioneNascita: nazione_nascita,
nome,
path: "codice_fiscale",
refinementContext,
sesso,
});
if (n_adulti <= 0) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci il numero di persone",
path: ["n_adulti"],
});
}
if (
permanenza === null &&
tipologia === TipologiaPosizioneEnum.Transitorio
) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci permanenza",
path: ["permanenza"],
});
}
if (tipologia === TipologiaPosizioneEnum.Transitorio) {
if ( if (
permanenza === null && motivazione_transitorio === "" ||
tipologia === TipologiaPosizioneEnum.Transitorio motivazione_transitorio === null
) { ) {
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
input: "", input: "",
message: "Inserisci permanenza", message: "Inserisci la motivazione",
path: ["permanenza"], path: ["motivazione_transitorio"],
}); });
} }
if (tipologia === TipologiaPosizioneEnum.Transitorio) { } else {
if ( if (reddito === "" || reddito === null) {
motivazione_transitorio === "" || refinementContext.issues.push({
motivazione_transitorio === null code: "custom",
) { input: "",
refinementContext.issues.push({ message: "Inserisci il reddito",
code: "custom", path: ["reddito"],
input: "", });
message: "Inserisci la motivazione",
path: ["motivazione_transitorio"],
});
}
} else {
if (reddito === "" || reddito === null) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci il reddito",
path: ["reddito"],
});
}
} }
}, }
); },
);
type FormValues = z.infer<typeof Schema>;
// This can come from your database or API. // This can come from your database or API.
const defaultValues: FormValues = { const defaultValues: FormNewServizioValues = {
...initialData.userData, ...initialData.userData,
azienda: initialData.anagrafica?.azienda || false, azienda: initialData.anagrafica?.azienda || false,
cap_recapiti: initialData.anagrafica?.cap_residenza || "", cap_recapiti: initialData.anagrafica?.cap_residenza || "",
@ -298,10 +298,10 @@ export const FormNewServizioAcquisto = ({
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
const nazioni_options = GetNazioni(); const nazioni_options = GetNazioni();
const comuni_options = getComuniNames(); const comuni_options = getComuni();
z.config(z.locales[locale]()); z.config(z.locales[locale]());
const form = useZodForm(Schema, { const form = useZodForm(schema, {
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
const DatePickerLocale = locale === "it" ? it : enUS; const DatePickerLocale = locale === "it" ? it : enUS;
@ -335,7 +335,7 @@ export const FormNewServizioAcquisto = ({
}, },
}); });
function onSubmit(fields: FormValues) { function onSubmit(fields: FormNewServizioValues) {
mutate({ mutate({
anagrafica: { anagrafica: {
azienda: fields.azienda, azienda: fields.azienda,
@ -399,17 +399,8 @@ export const FormNewServizioAcquisto = ({
["pianoterra", string], ["pianoterra", string],
]; ];
const altrePrefMapping = t.altrePrefMapping as altrePrefMappingType; const altrePrefMapping = t.altrePrefMapping as altrePrefMappingType;
const [ const [tipologia, nazione_nascita, nazione_recapiti, budget] = form.watch([
tipologia,
azienda,
fatturazione_aziendale,
nazione_nascita,
nazione_recapiti,
budget,
] = form.watch([
"tipologia", "tipologia",
"azienda",
"fatturazione_aziendale",
"nazione_nascita", "nazione_nascita",
"nazione_recapiti", "nazione_recapiti",
"budget", "budget",
@ -438,7 +429,10 @@ export const FormNewServizioAcquisto = ({
return ( return (
<Form {...form}> <Form {...form}>
<form className="space-y-8 px-0.5" onSubmit={form.handleSubmit(onSubmit)}> <form
className="space-y-8 px-0.5 pb-5"
onSubmit={form.handleSubmit(onSubmit)}
>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>{texts.dettagli}</CardTitle> <CardTitle>{texts.dettagli}</CardTitle>
@ -882,154 +876,9 @@ export const FormNewServizioAcquisto = ({
/> />
</div> </div>
{azienda && ( <AziendaSection control={form.control} setValue={form.setValue} />
<> <FatturazioneSection control={form.control} />
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="ragione_sociale"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="ragione_sociale">
{t.anagrafica.ragioneSociale}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="ragione_sociale"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="sede_legale"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="sede_legale">
{t.anagrafica.sedeLegale}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="sede_legale"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="p_iva"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="p_iva">
{t.anagrafica.p_iva}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="p_iva"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="legale_rappresentante"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="legale_rappresentante">
{t.anagrafica.legale_rappresentante}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="legale_rappresentante"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="fatturazione_aziendale"
render={({ field }) => (
<FormItem className="py-2">
<div className="flex items-center justify-start gap-x-4">
<FormLabel htmlFor="fatturazione_aziendale">
{texts.fattura}
</FormLabel>
<FormControl>
<Switch
checked={field.value}
className="data-[state=checked]:bg-neutral-700"
id="fatturazione_aziendale"
onCheckedChange={(v) => {
field.onChange(v);
if (!v) {
form.setValue("codice_destinatario", null);
}
}}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
{fatturazione_aziendale && (
<FormField
control={form.control}
name="codice_destinatario"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="codice_destinatario">
{texts.sdi}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="codice_destinatario"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
)}
</>
)}
<Separator /> <Separator />
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row"> <div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField <FormField
@ -1110,6 +959,9 @@ export const FormNewServizioAcquisto = ({
onValueChange={async (value) => { onValueChange={async (value) => {
field.onChange(value.value); field.onChange(value.value);
await form.trigger("luogo_nascita"); await form.trigger("luogo_nascita");
if (value.value !== "0") {
form.setValue("luogo_nascita", "");
}
}} }}
options={UnpackOptions({ options: nazioni_options })} options={UnpackOptions({ options: nazioni_options })}
placeholder="Seleziona..." placeholder="Seleziona..."
@ -1124,6 +976,7 @@ export const FormNewServizioAcquisto = ({
name="luogo_nascita" name="luogo_nascita"
render={({ field }) => { render={({ field }) => {
if (nazione_nascita === "0") { if (nazione_nascita === "0") {
const options = comuni_options.map((c) => c.nome);
return ( return (
<FormItem className="w-full"> <FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2"> <div className="flex flex-wrap items-center gap-x-2">
@ -1137,7 +990,7 @@ export const FormNewServizioAcquisto = ({
<MultiSelect <MultiSelect
defaultValue={getComuneFieldValue( defaultValue={getComuneFieldValue(
field.value, field.value,
comuni_options, options,
)} )}
elementId="select-comune-nascita" elementId="select-comune-nascita"
elementName="luogoNascita" elementName="luogoNascita"
@ -1146,7 +999,7 @@ export const FormNewServizioAcquisto = ({
field.onChange(value.value); field.onChange(value.value);
await form.trigger("luogo_nascita"); await form.trigger("luogo_nascita");
}} }}
options={UnpackOptions({ options: comuni_options })} options={UnpackOptions({ options })}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
@ -1310,7 +1163,9 @@ export const FormNewServizioAcquisto = ({
onValueChange={async (value) => { onValueChange={async (value) => {
field.onChange(value.value); field.onChange(value.value);
form.setValue("provincia_recapiti", ""); form.setValue("provincia_recapiti", "");
await form.trigger("citta_recapiti"); if (value.value !== "0") {
form.setValue("citta_recapiti", "");
}
}} }}
options={UnpackOptions({ options: nazioni_options })} options={UnpackOptions({ options: nazioni_options })}
placeholder="Seleziona..." placeholder="Seleziona..."
@ -1322,37 +1177,52 @@ export const FormNewServizioAcquisto = ({
<FormField <FormField
control={form.control} control={form.control}
name="provincia_recapiti" name="provincia_recapiti"
render={({ field }) => ( render={({ field }) => {
<FormItem className="w-full"> const provinciaName =
<div className="flex flex-wrap items-center gap-x-2"> field.value && getProvinciaFromSigla(field.value);
<FormLabel htmlFor="provinciaRecapiti"> return (
{t.recapiti.provincia} <FormItem className="w-full">
</FormLabel> <div className="flex flex-wrap items-center gap-x-2">
<FormMessage /> <FormLabel htmlFor="provinciaRecapiti">
</div> {t.recapiti.provincia}
</FormLabel>
<FormMessage />
</div>
{nazione_recapiti === "0" ? ( {nazione_recapiti === "0" ? (
<MultiSelect <MultiSelect
defaultValue={RepackValues({ defaultValue={
options: provincie, field.value && provinciaName
values: field.value, ? {
})} label: provinciaName,
elementId="provinciaRecapiti" value: field.value,
elementName="provincia" }
isMulti={false} : undefined
onValueChange={(value) => { }
field.onChange(value.value); elementId="provinciaRecapiti"
}} elementName="provincia"
options={UnpackOptions({ options: provincie })} isMulti={false}
placeholder="Seleziona" onValueChange={(value) => {
/> field.onChange(value.value);
) : ( }}
<FormControl> options={provincieWithSigla.map((prov) => ({
<Input {...field} id="provinciaRecapiti" type="text" /> label: prov.nome,
</FormControl> value: prov.sigla,
)} }))}
</FormItem> placeholder="Seleziona"
)} />
) : (
<FormControl>
<Input
{...field}
id="provinciaRecapiti"
type="text"
/>
</FormControl>
)}
</FormItem>
);
}}
/> />
</div> </div>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row"> <div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
@ -1361,6 +1231,12 @@ export const FormNewServizioAcquisto = ({
name="citta_recapiti" name="citta_recapiti"
render={({ field }) => { render={({ field }) => {
if (nazione_recapiti === "0") { if (nazione_recapiti === "0") {
const provincia = form.watch("provincia_recapiti");
const options = comuni_options
.filter((comune) =>
provincia ? comune.provincia === provincia : true,
)
.map((comune) => comune.nome);
return ( return (
<FormItem className="w-full"> <FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2"> <div className="flex flex-wrap items-center gap-x-2">
@ -1374,7 +1250,7 @@ export const FormNewServizioAcquisto = ({
<MultiSelect <MultiSelect
defaultValue={getComuneFieldValue( defaultValue={getComuneFieldValue(
field.value, field.value,
comuni_options, options,
)} )}
elementId="select-comune" elementId="select-comune"
elementName="citta_recapiti" elementName="citta_recapiti"
@ -1383,7 +1259,7 @@ export const FormNewServizioAcquisto = ({
field.onChange(value.value); field.onChange(value.value);
await form.trigger("citta_recapiti"); await form.trigger("citta_recapiti");
}} }}
options={UnpackOptions({ options: comuni_options })} options={UnpackOptions({ options })}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
@ -1484,7 +1360,7 @@ export const FormNewServizioAcquisto = ({
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<div className="flex w-full items-center justify-center gap-2 pb-8 sm:justify-end"> <div className="flex w-full flex-wrap items-center justify-center gap-2 pb-8 sm:justify-end">
{isAdmin && ( {isAdmin && (
<FormField <FormField
control={form.control} control={form.control}
@ -1520,3 +1396,194 @@ export const FormNewServizioAcquisto = ({
</Form> </Form>
); );
}; };
const AziendaSection = ({
control,
setValue,
}: {
control: Control<FormNewServizioValues>;
setValue: (
name: keyof FormNewServizioValues,
// biome-ignore lint/suspicious/noExplicitAny: <fine here>
value: any,
) => void;
}) => {
const { t, locale } = useTranslation();
const azienda = useWatch({
control,
name: "azienda",
});
return (
<>
<div
className={cn(
"flex w-full flex-col items-start justify-between gap-4 sm:flex-row",
!azienda && "hidden",
)}
>
<FormField
control={control}
name="ragione_sociale"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="ragione_sociale">
{t.anagrafica.ragioneSociale}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="ragione_sociale"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={control}
name="sede_legale"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="sede_legale">
{t.anagrafica.sedeLegale}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="sede_legale"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div
className={cn(
"flex w-full flex-col items-start justify-between gap-4 sm:flex-row",
!azienda && "hidden",
)}
>
<FormField
control={control}
name="p_iva"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="p_iva">{t.anagrafica.p_iva}</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="p_iva"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={control}
name="legale_rappresentante"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="legale_rappresentante">
{t.anagrafica.legale_rappresentante}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="legale_rappresentante"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<FormField
control={control}
name="fatturazione_aziendale"
render={({ field }) => (
<FormItem className={cn("py-2", !azienda && "hidden")}>
<div className="flex items-center justify-start gap-x-4">
<FormLabel htmlFor="fatturazione_aziendale">
{locale === "it"
? "Vuoi che la fatturazione sia intestata alla tua azienda?"
: "Do you want the invoice to be issued to your company?"}
</FormLabel>
<FormControl>
<Switch
checked={field.value}
className="data-[state=checked]:bg-neutral-700"
id="fatturazione_aziendale"
onCheckedChange={(v) => {
field.onChange(v);
if (!v) {
setValue("codice_destinatario", null);
}
}}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
</>
);
};
const FatturazioneSection = ({
control,
}: {
control: Control<FormNewServizioValues>;
}) => {
const { locale } = useTranslation();
const fatturazione_aziendale = useWatch({
control,
name: "fatturazione_aziendale",
});
return (
<FormField
control={control}
name="codice_destinatario"
render={({ field }) => (
<FormItem className={cn("w-full", !fatturazione_aziendale && "hidden")}>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="codice_destinatario">
{locale === "it"
? "Codice destinatario SDI"
: "SDI Recipient Code"}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="codice_destinatario"
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
);
};

File diff suppressed because it is too large Load diff

View file

@ -24,14 +24,14 @@ const searchComune = (comune: string): Comune | null => {
return result; return result;
}; };
let comuniNames: string[] | null = null; let comuniNames: string[] | null = null;
const initializeComuniNames = () => { const initializeComuniNames = (provincia?: string) => {
if (!comuniNames) { if (!comuniNames) {
comuniNames = comuniArray.map((comune) => comune.NOME); comuniNames = comuniArray.filter((c)=> provincia ? c.SIGLA==provincia:true).map((comune) => comune.NOME);
} }
}; };
export const getComuniNames = () => { export const getComuni = () => {
initializeComuniNames();
return comuniNames!; return comuniArray.map((comune) => ({nome:comune.NOME, provincia:comune.SIGLA}));
}; };
export const getComuneFromIndex = (index: number): Comune | null => { export const getComuneFromIndex = (index: number): Comune | null => {
initializeComuniNames(); initializeComuniNames();

View file

@ -1,112 +1,120 @@
export const provincie = [
"AGRIGENTO", export const getProvinciaFromSigla = (sigla: string): string | undefined => {
"ALESSANDRIA", const provincia = provincieWithSigla.find((prov) => prov.sigla === sigla);
"ANCONA", return provincia ? provincia.nome : undefined;
"AOSTA", }
"AREZZO", export const provincieWithSigla: {
"ASCOLI PICENO", nome: string;
"ASTI", sigla: string;
"AVELLINO", }[] = [
"BARI", {nome: "AGRIGENTO",sigla: "AG"},
"BARLETTA-ANDRIA-TRANI", {nome: "ALESSANDRIA",sigla: "AL"},
"BELLUNO", {nome: "ANCONA",sigla: "AN"},
"BENEVENTO", {nome: "AOSTA",sigla: "AO"},
"BERGAMO", {nome: "AREZZO",sigla: "AR"},
"BIELLA", {nome: "ASCOLI PICENO",sigla: "AP"},
"BOLOGNA", {nome: "ASTI",sigla: "AT"},
"BOLZANO", {nome: "AVELLINO",sigla: "AV"},
"BRESCIA", {nome: "BARI",sigla: "BA"},
"BRINDISI", {nome: "BARLETTA-ANDRIA-TRANI",sigla: "BT"},
"CAGLIARI", {nome: "BELLUNO",sigla: "BL"},
"CALTANISSETTA", {nome: "BENEVENTO",sigla: "BN"},
"CAMPOBASSO", {nome: "BERGAMO",sigla: "BG"},
"CARBONIA-IGLESIAS", {nome: "BIELLA",sigla: "BI"},
"CASERTA", {nome: "BOLOGNA",sigla: "BO"},
"CATANIA", {nome: "BOLZANO",sigla: "BZ"},
"CATANZARO", {nome: "BRESCIA",sigla: "BS"},
"CHIETI", {nome: "BRINDISI",sigla: "BR"},
"COMO", {nome: "CAGLIARI",sigla: "CA"},
"COSENZA", {nome: "CALTANISSETTA",sigla: "CL"},
"CREMONA", {nome: "CAMPOBASSO",sigla: "CB"},
"CROTONE", {nome: "CARBONIA-IGLESIAS",sigla: "CI"},
"CUNEO", {nome: "CASERTA",sigla: "CE"},
"ENNA", {nome: "CATANIA",sigla: "CT"},
"FERMO", {nome: "CATANZARO",sigla: "CZ"},
"FERRARA", {nome: "CHIETI",sigla: "CH"},
"FIRENZE", {nome: "COMO",sigla: "CO"},
"FOGGIA", {nome: "COSENZA",sigla: "CS"},
"FORLÌ-CESENA", {nome: "CREMONA",sigla: "CR"},
"FROSINONE", {nome: "CROTONE",sigla: "KR"},
"GENOVA", {nome: "CUNEO",sigla: "CN"},
"GORIZIA", {nome: "ENNA",sigla: "EN"},
"GROSSETO", {nome: "FERMO",sigla: "FM"},
"IMPERIA", {nome: "FERRARA",sigla: "FE"},
"ISERNIA", {nome: "FIRENZE",sigla: "FI"},
"LA SPEZIA", {nome: "FOGGIA",sigla: "FG"},
"L'AQUILA", {nome: "FORLÌ-CESENA",sigla: "FC"},
"LATINA", {nome: "FROSINONE",sigla: "FR"},
"LECCE", {nome: "GENOVA",sigla: "GE"},
"LECCO", {nome: "GORIZIA",sigla: "GO"},
"LIVORNO", {nome: "GROSSETO",sigla: "GR"},
"LODI", {nome: "IMPERIA",sigla: "IM"},
"LUCCA", {nome: "ISERNIA",sigla: "IS"},
"MACERATA", {nome: "LA SPEZIA",sigla: "SP"},
"MANTOVA", {nome: "L'AQUILA",sigla: "AQ"},
"MASSA-CARRARA", {nome: "LATINA",sigla: "LT"},
"MATERA", {nome: "LECCE",sigla: "LE"},
"MESSINA", {nome: "LECCO",sigla: "LC"},
"MILANO", {nome: "LIVORNO",sigla: "LI"},
"MODENA", {nome: "LODI",sigla: "LO"},
"MONZA E DELLA BRIANZA", {nome: "LUCCA",sigla: "LU"},
"NAPOLI", {nome: "MACERATA",sigla: "MC"},
"NOVARA", {nome: "MANTOVA",sigla: "MN"},
"NUORO", {nome: "MASSA-CARRARA",sigla: "MS"},
"OLBIA-TEMPIO", {nome: "MATERA",sigla: "MT"},
"ORISTANO", {nome: "MESSINA",sigla: "ME"},
"PADOVA", {nome: "MILANO",sigla: "MI"},
"PALERMO", {nome: "MODENA",sigla: "MO"},
"PARMA", {nome: "MONZA E DELLA BRIANZA",sigla: "MB"},
"PAVIA", {nome: "NAPOLI",sigla: "NA"},
"PERUGIA", {nome: "NOVARA",sigla: "NO"},
"PESARO E URBINO", {nome: "NUORO",sigla: "NU"},
"PESCARA", {nome: "OLBIA-TEMPIO",sigla: "OT"},
"PIACENZA", {nome: "ORISTANO",sigla: "OR"},
"PISA", {nome: "PADOVA",sigla: "PD"},
"PISTOIA", {nome: "PALERMO",sigla: "PA"},
"PORDENONE", {nome: "PARMA",sigla: "PR"},
"POTENZA", {nome: "PAVIA",sigla: "PV"},
"PRATO", {nome: "PERUGIA",sigla: "PG"},
"RAGUSA", {nome: "PESARO E URBINO",sigla: "PU"},
"RAVENNA", {nome: "PESCARA",sigla: "PE"},
"REGGIO CALABRIA", {nome: "PIACENZA",sigla: "PC"},
"REGGIO EMILIA", {nome: "PISA",sigla: "PI"},
"RIETI", {nome: "PISTOIA",sigla: "PT"},
"RIMINI", {nome: "PORDENONE",sigla: "PN"},
"ROMA", {nome: "POTENZA",sigla: "PZ"},
"ROVIGO", {nome: "PRATO",sigla: "PO"},
"SALERNO", {nome: "RAGUSA",sigla: "RG"},
"MEDIO CAMPIDANO", {nome: "RAVENNA",sigla: "RA"},
"SASSARI", {nome: "REGGIO CALABRIA",sigla: "RC"},
"SAVONA", {nome: "REGGIO EMILIA",sigla: "RE"},
"SIENA", {nome: "RIETI",sigla: "RI"},
"SIRACUSA", {nome: "RIMINI",sigla: "RN"},
"SONDRIO", {nome: "ROMA",sigla: "RM"},
"TARANTO", {nome: "ROVIGO",sigla: "RO"},
"TERAMO", {nome: "SALERNO",sigla: "SA"},
"TERNI", {nome: "MEDIO CAMPIDANO",sigla: "VS"},
"TORINO", {nome: "SASSARI",sigla: "SS"},
"OGLIASTRA", {nome: "SAVONA",sigla: "SV"},
"TRAPANI", {nome: "SIENA",sigla: "SI"},
"TRENTO", {nome: "SIRACUSA",sigla: "SR"},
"TREVISO", {nome: "SONDRIO",sigla: "SO"},
"TRIESTE", {nome: "TARANTO",sigla: "TA"},
"UDINE", {nome: "TERAMO",sigla: "TE"},
"VARESE", {nome: "TERNI",sigla: "TR"},
"VENEZIA", {nome: "TORINO",sigla: "TO"},
"VERBANO-CUSIO-OSSOLA", {nome: "OGLIASTRA",sigla: "OG"},
"VERCELLI", {nome: "TRAPANI",sigla: "TP"},
"VERONA", {nome: "TRENTO",sigla: "TN"},
"VIBO VALENTIA", {nome: "TREVISO",sigla: "TV"},
"VICENZA", {nome: "TRIESTE",sigla: "TS"},
"VITERBO", {nome: "UDINE",sigla: "UD"},
{nome: "VARESE",sigla: "VA"},
{nome: "VENEZIA",sigla: "VE"},
{nome: "VERBANO-CUSIO-OSSOLA",sigla: "VB"},
{nome: "VERCELLI",sigla: "VC"},
{nome: "VERONA",sigla: "VR"},
{nome: "VIBO VALENTIA",sigla: "VV"},
{nome: "VICENZA",sigla: "VI"},
{nome: "VITERBO",sigla: "VT"}
]; ];

View file

@ -1,8 +1,17 @@
import dynamic from "next/dynamic";
import type { z } from "zod/v4"; import type { z } from "zod/v4";
import type { ListOption } from "~/components/custom_ui/multiselect"; import type { ListOption } from "~/components/custom_ui/multiselect";
import { getComuneFromIndex } from "~/i18n/comuni"; import { getComuneFromIndex } from "~/i18n/comuni";
import { searchNazioneByIndex } from "~/i18n/nazioni"; import { searchNazioneByIndex } from "~/i18n/nazioni";
export const FormDebug: React.ElementType =
process.env.NODE_ENV === "development"
? dynamic(
() => import("@hookform/devtools").then((module) => module.DevTool),
{ ssr: false },
)
: () => null; // Return null component in production
export const getComuneFieldValue = ( export const getComuneFieldValue = (
fieldValue: string, fieldValue: string,
comuni_options: string[], comuni_options: string[],
@ -183,8 +192,8 @@ export const checkFiscalCodeValidity = ({
gen !== codiceFiscale.slice(0, 15) && gen !== codiceFiscale.slice(0, 15) &&
!(codiceFiscale in getAlternativeCF(gen)) !(codiceFiscale in getAlternativeCF(gen))
) { ) {
console.log("gen", gen); //console.log("gen", gen);
console.log("codiceFiscale", codiceFiscale); //console.log("codiceFiscale", codiceFiscale);
console.log("alternative", getAlternativeCF(gen)); console.log("alternative", getAlternativeCF(gen));
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",