This commit is contained in:
Marco Pedone 2025-11-13 15:45:55 +01:00
parent 306be40555
commit 219f76dd10
16 changed files with 827 additions and 41077 deletions

View file

@ -18,11 +18,7 @@ import {
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import InputWIcon from "~/components/custom_ui/input-icon"; import InputWIcon from "~/components/custom_ui/input-icon";
import { import { MultiSelect, UnpackOptions } from "~/components/custom_ui/multiselect";
MultiSelect,
RepackValues,
UnpackOptions,
} from "~/components/custom_ui/multiselect";
import { PhoneInput } from "~/components/phone-input"; import { PhoneInput } from "~/components/phone-input";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -49,12 +45,16 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "~/components/ui/tooltip"; } from "~/components/ui/tooltip";
import { getComuni } from "~/i18n/comuni"; import {
import { GetNazioni } from "~/i18n/nazioni"; getProvinciaFromSigla,
import { getProvinciaFromSigla, provincieWithSigla } from "~/i18n/provincie"; ITALY_CATASTO_CODE,
import { checkFiscalCodeValidity, getComuneFieldValue } from "~/lib/form_utils"; parseDBComune,
parseDBNazione,
} from "~/lib/catasto";
import { checkFiscalCodeValidity } from "~/lib/form_utils";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { useCatasto } from "~/providers/CatastoProvider";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { Servizio } from "~/schemas/public/Servizio"; import type { Servizio } from "~/schemas/public/Servizio";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum"; import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
@ -75,8 +75,8 @@ const FormNewServizioSchema = z.object({
azienda: z.boolean(), azienda: z.boolean(),
budget: z.number().positive(), budget: z.number().positive(),
cap_recapiti: z.string().length(5), cap_recapiti: z.string().length(5),
citta_recapiti: z.string().nonempty("Inserisci una città"), citta_recapiti: z.string().nullable(),
civico_recapiti: z.string().nonempty("Inserisci un civico"), civico_recapiti: z.string(),
codice_destinatario: z.string().nullable(), codice_destinatario: z.string().nullable(),
codice_fiscale: z.string(), codice_fiscale: z.string(),
cognome: z.string().nonempty("Inserisci un cognome valido"), cognome: z.string().nonempty("Inserisci un cognome valido"),
@ -88,22 +88,18 @@ const FormNewServizioSchema = z.object({
giardino: z.boolean(), giardino: z.boolean(),
indirizzo_recapiti: z.string().nonempty("Inserisci un indirizzo"), indirizzo_recapiti: z.string().nonempty("Inserisci un indirizzo"),
legale_rappresentante: z.string().nullable(), legale_rappresentante: z.string().nullable(),
luogo_nascita: z.string(), luogo_nascita: z.string().nullable(),
motivazione_transitorio: z.string().nullable(), motivazione_transitorio: z.string().nullable(),
n_adulti: z.number().min(1), n_adulti: z.number().min(1),
n_minori: z.number(), n_minori: z.number(),
nazione_nascita: z.string(), nazione_nascita: z.string().nullable(),
nazione_recapiti: z nazione_recapiti: z.string().nullable(),
.string()
.refine((v) => v !== "", { params: { type: "nazione" } }),
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
p_iva: z.string().nullable(), p_iva: z.string().nullable(),
parcheggio: z.boolean(), parcheggio: z.boolean(),
permanenza: z.number().nullable(), permanenza: z.number().nullable(),
pianoterra: z.boolean(), pianoterra: z.boolean(),
provincia_recapiti: z provincia_recapiti: z.string().nullable(),
.string()
.refine((v) => v !== "", { params: { type: "provincia" } }),
ragione_sociale: z.string().nullable(), ragione_sociale: z.string().nullable(),
reddito: z.string().nullable(), reddito: z.string().nullable(),
sede_legale: z.string().nullable(), sede_legale: z.string().nullable(),
@ -126,6 +122,7 @@ export const FormNewServizioAcquisto = ({
isAdmin?: boolean; isAdmin?: boolean;
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { comuni, nazioni } = useCatasto();
const schema = FormNewServizioSchema.superRefine( const schema = FormNewServizioSchema.superRefine(
( (
{ {
@ -150,6 +147,9 @@ export const FormNewServizioAcquisto = ({
ragione_sociale, ragione_sociale,
fatturazione_aziendale, fatturazione_aziendale,
codice_destinatario, codice_destinatario,
citta_recapiti,
nazione_recapiti,
provincia_recapiti,
}, },
refinementContext, refinementContext,
) => { ) => {
@ -212,8 +212,50 @@ export const FormNewServizioAcquisto = ({
input: "", input: "",
message: message:
"Il numero di telefono deve essere lungo almeno 10 caratteri", "Il numero di telefono deve essere lungo almeno 10 caratteri",
path: ["telefono"],
}); });
} }
if (!luogo_nascita || luogo_nascita.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci il luogo di nascita",
path: ["luogo_nascita"],
});
}
if (!nazione_nascita || nazione_nascita.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la nazione di nascita",
path: ["nazione_nascita"],
});
}
if (!citta_recapiti || citta_recapiti.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la città di recapiti",
path: ["citta_recapiti"],
});
}
if (!nazione_recapiti || nazione_recapiti.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la nazione di recapiti",
path: ["nazione_recapiti"],
});
}
if (!provincia_recapiti || provincia_recapiti.length < 1) {
refinementContext.issues.push({
code: "custom",
input: "",
message: "Inserisci la provincia di recapiti",
path: ["provincia_recapiti"],
});
}
if (luogo_nascita && nazione_nascita) {
checkFiscalCodeValidity({ checkFiscalCodeValidity({
codiceFiscale: codice_fiscale, codiceFiscale: codice_fiscale,
cognome, cognome,
@ -224,7 +266,10 @@ export const FormNewServizioAcquisto = ({
path: "codice_fiscale", path: "codice_fiscale",
refinementContext, refinementContext,
sesso, sesso,
comuneCatasto: comuni.find((c) => c.catasto === luogo_nascita),
nazioneCatasto: nazioni.find((c) => c.catasto === nazione_nascita),
}); });
}
if (n_adulti <= 0) { if (n_adulti <= 0) {
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
@ -287,7 +332,7 @@ export const FormNewServizioAcquisto = ({
...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 || "",
citta_recapiti: initialData.anagrafica?.comune_residenza || "", citta_recapiti: initialData.anagrafica?.comune_residenza || null,
civico_recapiti: initialData.anagrafica?.civico_residenza || "", civico_recapiti: initialData.anagrafica?.civico_residenza || "",
codice_destinatario: initialData.anagrafica?.codice_destinatario || "", codice_destinatario: initialData.anagrafica?.codice_destinatario || "",
codice_fiscale: initialData.anagrafica?.codice_fiscale || "", codice_fiscale: initialData.anagrafica?.codice_fiscale || "",
@ -298,12 +343,12 @@ export const FormNewServizioAcquisto = ({
initialData.anagrafica?.fatturazione_aziendale || false, initialData.anagrafica?.fatturazione_aziendale || false,
indirizzo_recapiti: initialData.anagrafica?.via_residenza || "", indirizzo_recapiti: initialData.anagrafica?.via_residenza || "",
legale_rappresentante: initialData.anagrafica?.legale_rappresentante || "", legale_rappresentante: initialData.anagrafica?.legale_rappresentante || "",
luogo_nascita: initialData.anagrafica?.luogo_nascita || "", luogo_nascita: initialData.anagrafica?.luogo_nascita || null,
nazione_nascita: initialData.anagrafica?.nazione_nascita || "", nazione_nascita: initialData.anagrafica?.nazione_nascita || null,
nazione_recapiti: initialData.anagrafica?.nazione_residenza || "", nazione_recapiti: initialData.anagrafica?.nazione_residenza || null,
p_iva: initialData.anagrafica?.p_iva || "", p_iva: initialData.anagrafica?.p_iva || "",
provincia_recapiti: initialData.anagrafica?.provincia_residenza || "", provincia_recapiti: initialData.anagrafica?.provincia_residenza || null,
ragione_sociale: initialData.anagrafica?.ragione_sociale || "", ragione_sociale: initialData.anagrafica?.ragione_sociale || "",
sede_legale: initialData.anagrafica?.sede_legale || "", sede_legale: initialData.anagrafica?.sede_legale || "",
sesso: initialData.anagrafica?.sesso || "M", sesso: initialData.anagrafica?.sesso || "M",
@ -311,8 +356,6 @@ export const FormNewServizioAcquisto = ({
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
const nazioni_options = GetNazioni();
const comuni_options = getComuni();
z.config(z.locales[locale]()); z.config(z.locales[locale]());
const form = useZodForm(schema, { const form = useZodForm(schema, {
@ -417,10 +460,9 @@ export const FormNewServizioAcquisto = ({
["pianoterra", string], ["pianoterra", string],
]; ];
const altrePrefMapping = t.altrePrefMapping as altrePrefMappingType; const altrePrefMapping = t.altrePrefMapping as altrePrefMappingType;
const [tipologia, nazione_nascita, nazione_recapiti, budget] = form.watch([ const [tipologia, nazione_nascita, budget] = form.watch([
"tipologia", "tipologia",
"nazione_nascita", "nazione_nascita",
"nazione_recapiti",
"budget", "budget",
]); ]);
@ -1004,7 +1046,12 @@ export const FormNewServizioAcquisto = ({
<FormField <FormField
control={form.control} control={form.control}
name="nazione_nascita" name="nazione_nascita"
render={({ field }) => ( render={({ field }) => {
const parsedNazioneNascita = parseDBNazione(
field.value,
nazioni,
);
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">
<FormLabel htmlFor="select-naz-nascita"> <FormLabel htmlFor="select-naz-nascita">
@ -1015,34 +1062,50 @@ export const FormNewServizioAcquisto = ({
<FormControl> <FormControl>
<MultiSelect <MultiSelect
defaultValue={RepackValues({ defaultValue={
options: nazioni_options, field.value
values: field.value, ? parsedNazioneNascita
})} ? {
label: parsedNazioneNascita.nome,
value: parsedNazioneNascita.catasto,
}
: {
label: field.value,
value: field.value,
}
: undefined
}
elementId="select-naz-nascita" elementId="select-naz-nascita"
elementName="nazioneNascita" elementName="nazioneNascita"
isMulti={false} isMulti={false}
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", ""); form.setValue("luogo_nascita", null);
}
}} }}
options={UnpackOptions({ options: nazioni_options })} options={nazioni.map((n) => ({
label: n.nome,
value: n.catasto,
}))}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
)} );
}}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="luogo_nascita" name="luogo_nascita"
render={({ field }) => { render={({ field }) => {
if (nazione_nascita === "0") { if (nazione_nascita === ITALY_CATASTO_CODE) {
const options = comuni_options.map((c) => c.nome); const parsedComuneNascita = parseDBComune(
field.value,
comuni,
);
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">
@ -1054,18 +1117,32 @@ export const FormNewServizioAcquisto = ({
<FormControl> <FormControl>
<MultiSelect <MultiSelect
defaultValue={getComuneFieldValue( defaultValue={
field.value, field.value
options, ? parsedComuneNascita
)} ? {
label: `${parsedComuneNascita.nome} (${parsedComuneNascita.sigla})`,
value: parsedComuneNascita.catasto,
}
: {
label: field.value,
value: field.value,
}
: undefined
}
elementId="select-comune-nascita" elementId="select-comune-nascita"
elementName="luogoNascita" elementName="luogoNascita"
isMulti={false} isMulti={false}
onValueChange={async (value) => { onValueChange={async (value) => {
field.onChange(value.value); field.onChange(value.value);
await form.trigger("luogo_nascita"); await form.trigger("luogo_nascita");
await form.trigger("codice_fiscale");
}} }}
options={UnpackOptions({ options })} options={comuni.map((c) => ({
label: `${c.nome} (${c.sigla})`,
value: c.catasto,
}))}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
@ -1087,8 +1164,10 @@ export const FormNewServizioAcquisto = ({
onChange={async (v) => { onChange={async (v) => {
field.onChange(v.target.value); field.onChange(v.target.value);
await form.trigger("luogo_nascita"); await form.trigger("luogo_nascita");
await form.trigger("codice_fiscale");
}} }}
type="text" type="text"
value={field.value || undefined}
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
@ -1204,179 +1283,11 @@ export const FormNewServizioAcquisto = ({
/> />
</div> </div>
<Separator /> <Separator />
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row"> <ResidenzaSection
<FormField
control={form.control} control={form.control}
name="nazione_recapiti" setValue={form.setValue}
render={({ field }) => ( trigger={form.trigger}
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-naz">
{t.recapiti.nazione}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<MultiSelect
defaultValue={RepackValues({
options: nazioni_options,
values: field.value,
})}
elementId="select-naz"
elementName="nazioneRecapiti"
isMulti={false}
onValueChange={async (value) => {
field.onChange(value.value);
form.setValue("provincia_recapiti", "");
if (value.value !== "0") {
form.setValue("citta_recapiti", "");
}
}}
options={UnpackOptions({ options: nazioni_options })}
placeholder="Seleziona..."
/> />
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="provincia_recapiti"
render={({ field }) => {
const provinciaName =
field.value && getProvinciaFromSigla(field.value);
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="provinciaRecapiti">
{t.recapiti.provincia}
</FormLabel>
<FormMessage />
</div>
{nazione_recapiti === "0" ? (
<MultiSelect
defaultValue={
field.value && provinciaName
? {
label: provinciaName,
value: field.value,
}
: undefined
}
elementId="provinciaRecapiti"
elementName="provincia"
isMulti={false}
onValueChange={(value) => {
field.onChange(value.value);
}}
options={provincieWithSigla.map((prov) => ({
label: prov.nome,
value: prov.sigla,
}))}
placeholder="Seleziona"
/>
) : (
<FormControl>
<Input
{...field}
id="provinciaRecapiti"
type="text"
/>
</FormControl>
)}
</FormItem>
);
}}
/>
</div>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="citta_recapiti"
render={({ field }) => {
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 (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-comune">
{t.recapiti.comune}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<MultiSelect
defaultValue={getComuneFieldValue(
field.value,
options,
)}
elementId="select-comune"
elementName="citta_recapiti"
isMulti={false}
onValueChange={async (value) => {
field.onChange(value.value);
await form.trigger("citta_recapiti");
}}
options={UnpackOptions({ options })}
placeholder="Seleziona..."
/>
</FormControl>
</FormItem>
);
}
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="citta_recapiti">
{t.recapiti.comune}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="citta_recapiti"
onChange={async (v) => {
field.onChange(v.target.value);
await form.trigger("citta_recapiti");
}}
type="text"
/>
</FormControl>
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="cap_recapiti"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="cap">{t.recapiti.cap}</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="cap"
placeholder="00100"
type="text"
/>
</FormControl>
</FormItem>
)}
/>
</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">
<FormField <FormField
control={form.control} control={form.control}
@ -1653,3 +1564,229 @@ const FatturazioneSection = ({
/> />
); );
}; };
const ResidenzaSection = ({
control,
trigger,
setValue,
}: {
control: Control<FormNewServizioValues>;
trigger: (name?: keyof FormNewServizioValues) => Promise<boolean>;
// biome-ignore lint/suspicious/noExplicitAny: <fine here>
setValue: (field: keyof FormNewServizioValues, value: any) => void;
}) => {
const { t } = useTranslation();
const { comuni, nazioni, provincie } = useCatasto();
const nazione_recapiti = useWatch({
control,
name: "nazione_recapiti",
});
const provincia_recapiti = useWatch({
control,
name: "provincia_recapiti",
});
return (
<>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={control}
name="nazione_recapiti"
render={({ field }) => {
const parsedNazioneResidenza = parseDBNazione(field.value, nazioni);
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-naz">
{t.recapiti.nazione}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<MultiSelect
defaultValue={
field.value
? parsedNazioneResidenza
? {
label: parsedNazioneResidenza.nome,
value: parsedNazioneResidenza.catasto,
}
: {
label: field.value,
value: field.value,
}
: undefined
}
elementId="select-naz"
elementName="nazioneRecapiti"
isMulti={false}
onValueChange={async (value) => {
setValue("provincia_recapiti", null);
setValue("citta_recapiti", null);
field.onChange(value.value);
await trigger("citta_recapiti");
}}
options={nazioni.map((n) => ({
label: n.nome,
value: n.catasto,
}))}
placeholder="Seleziona..."
/>
</FormControl>
</FormItem>
);
}}
/>
<FormField
control={control}
name="provincia_recapiti"
render={({ field }) => {
const parseProvincia = getProvinciaFromSigla(
field.value,
provincie,
);
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="provinciaRecapiti">
{t.recapiti.provincia}
</FormLabel>
<FormMessage />
</div>
{nazione_recapiti === ITALY_CATASTO_CODE ? (
<MultiSelect
defaultValue={
field.value && parseProvincia
? {
label: parseProvincia.nome,
value: parseProvincia.sigla,
}
: undefined
}
elementId="provinciaRecapiti"
elementName="provincia"
isMulti={false}
onValueChange={(value) => {
field.onChange(value.value);
setValue("citta_recapiti", null);
}}
options={provincie.map((prov) => ({
label: prov.nome,
value: prov.sigla,
}))}
placeholder="Seleziona"
/>
) : (
<FormControl>
<Input
{...field}
id="provinciaRecapiti"
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={control}
name="citta_recapiti"
render={({ field }) => {
if (nazione_recapiti === ITALY_CATASTO_CODE) {
const parsedComuneResidenza = parseDBComune(field.value, comuni);
const options = comuni
.filter((comune) =>
provincia_recapiti
? comune.sigla === provincia_recapiti
: true,
)
.map((comune) => ({
label: comune.nome,
value: comune.catasto,
}));
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-comune">
{t.recapiti.comune}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<MultiSelect
defaultValue={
field.value && parsedComuneResidenza
? {
label: parsedComuneResidenza.nome,
value: parsedComuneResidenza.catasto,
}
: undefined
}
elementId="select-comune"
elementName="citta_recapiti"
isMulti={false}
onValueChange={async (value) => {
field.onChange(value.value);
await trigger("citta_recapiti");
}}
options={options}
placeholder="Seleziona..."
/>
</FormControl>
</FormItem>
);
}
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="citta_recapiti">
{t.recapiti.comune}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
id="citta_recapiti"
onChange={async (v) => {
field.onChange(v.target.value);
await trigger("citta_recapiti");
}}
type="text"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
);
}}
/>
<FormField
control={control}
name="cap_recapiti"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="cap">{t.recapiti.cap}</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input {...field} id="cap" placeholder="00100" type="text" />
</FormControl>
</FormItem>
)}
/>
</div>
</>
);
};

View file

@ -13,11 +13,7 @@ import {
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { import { MultiSelect } from "~/components/custom_ui/multiselect";
MultiSelect,
RepackValues,
UnpackOptions,
} from "~/components/custom_ui/multiselect";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Calendar } from "~/components/ui/calendar"; import { Calendar } from "~/components/ui/calendar";
import { import {
@ -27,12 +23,16 @@ import {
} from "~/components/ui/popover"; } from "~/components/ui/popover";
import { Separator } from "~/components/ui/separator"; import { Separator } from "~/components/ui/separator";
import { Switch } from "~/components/ui/switch"; import { Switch } from "~/components/ui/switch";
import { getComuni } from "~/i18n/comuni"; import {
import { GetNazioni } from "~/i18n/nazioni"; getProvinciaFromSigla,
import { getProvinciaFromSigla, provincieWithSigla } from "~/i18n/provincie"; ITALY_CATASTO_CODE,
import { checkFiscalCodeValidity, getComuneFieldValue } from "~/lib/form_utils"; parseDBComune,
parseDBNazione,
} from "~/lib/catasto";
import { checkFiscalCodeValidity } from "~/lib/form_utils";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { useCatasto } from "~/providers/CatastoProvider";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { UserWithAnagrafica } from "~/server/services/user.service"; import type { UserWithAnagrafica } from "~/server/services/user.service";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
@ -43,15 +43,15 @@ const profileFormSchema = z.object({
civico_residenza: z.string().optional(), civico_residenza: z.string().optional(),
codice_destinatario: z.string().length(7).nullable(), codice_destinatario: z.string().length(7).nullable(),
codice_fiscale: z.string().optional(), codice_fiscale: z.string().optional(),
comune_residenza: z.string().optional(), comune_residenza: z.string().nullable(),
data_nascita: z.date().optional(), data_nascita: z.date().optional(),
fatturazione_aziendale: z.boolean(), fatturazione_aziendale: z.boolean(),
legale_rappresentante: z.string().nullable(), legale_rappresentante: z.string().nullable(),
luogo_nascita: z.string().optional(), luogo_nascita: z.string().nullable(),
nazione_nascita: z.string().optional(), nazione_nascita: z.string().nullable(),
nazione_residenza: z.string().optional(), nazione_residenza: z.string().nullable(),
p_iva: z.string().nullable(), p_iva: z.string().nullable(),
provincia_residenza: z.string().optional(), provincia_residenza: z.string().nullable(),
ragione_sociale: z.string().nullable(), ragione_sociale: z.string().nullable(),
sede_legale: z.string().nullable(), sede_legale: z.string().nullable(),
sesso: z.string().optional(), sesso: z.string().optional(),
@ -64,6 +64,8 @@ export const ProfileFormAnagrafica = ({
}: { }: {
userData: UserWithAnagrafica; userData: UserWithAnagrafica;
}) => { }) => {
const { comuni, nazioni } = useCatasto();
const schema = profileFormSchema.superRefine( const schema = profileFormSchema.superRefine(
( (
{ {
@ -167,6 +169,8 @@ export const ProfileFormAnagrafica = ({
path: "codice_fiscale", path: "codice_fiscale",
refinementContext, refinementContext,
sesso, sesso,
comuneCatasto: comuni.find((c) => c.catasto === luogo_nascita),
nazioneCatasto: nazioni.find((c) => c.catasto === nazione_nascita),
}); });
} }
}, },
@ -178,15 +182,15 @@ export const ProfileFormAnagrafica = ({
civico_residenza: userData.civico_residenza || "", civico_residenza: userData.civico_residenza || "",
codice_destinatario: userData.codice_destinatario || null, codice_destinatario: userData.codice_destinatario || null,
codice_fiscale: userData.codice_fiscale || "", codice_fiscale: userData.codice_fiscale || "",
comune_residenza: userData.comune_residenza || "", comune_residenza: userData.comune_residenza,
data_nascita: userData.data_nascita || new Date(), data_nascita: userData.data_nascita || new Date(),
fatturazione_aziendale: userData.fatturazione_aziendale || false, fatturazione_aziendale: userData.fatturazione_aziendale || false,
legale_rappresentante: userData.legale_rappresentante || null, legale_rappresentante: userData.legale_rappresentante || null,
luogo_nascita: userData.luogo_nascita || "", luogo_nascita: userData.luogo_nascita,
nazione_nascita: userData.nazione_nascita || "", nazione_nascita: userData.nazione_nascita,
nazione_residenza: userData.nazione_residenza || "", nazione_residenza: userData.nazione_residenza,
p_iva: userData.p_iva || null, p_iva: userData.p_iva || null,
provincia_residenza: userData.provincia_residenza || "", provincia_residenza: userData.provincia_residenza,
ragione_sociale: userData.ragione_sociale || null, ragione_sociale: userData.ragione_sociale || null,
sede_legale: userData.sede_legale || null, sede_legale: userData.sede_legale || null,
sesso: userData.sesso || "", sesso: userData.sesso || "",
@ -222,8 +226,6 @@ export const ProfileFormAnagrafica = ({
data: { ...fields }, data: { ...fields },
}); });
} }
const nazioni_options = GetNazioni();
const comuni_options = getComuni();
return ( return (
<> <>
@ -288,7 +290,12 @@ export const ProfileFormAnagrafica = ({
<FormField <FormField
control={form.control} control={form.control}
name="nazione_nascita" name="nazione_nascita"
render={({ field }) => ( render={({ field }) => {
const parsedNazioneNascita = parseDBNazione(
field.value,
nazioni,
);
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">
<FormLabel htmlFor="select-naz"> <FormLabel htmlFor="select-naz">
@ -301,10 +308,15 @@ export const ProfileFormAnagrafica = ({
<MultiSelect <MultiSelect
defaultValue={ defaultValue={
field.value field.value
? RepackValues({ ? parsedNazioneNascita
options: nazioni_options, ? {
values: field.value, label: parsedNazioneNascita.nome,
}) value: parsedNazioneNascita.catasto,
}
: {
label: field.value,
value: field.value,
}
: undefined : undefined
} }
elementId="select-naz" elementId="select-naz"
@ -313,21 +325,29 @@ export const ProfileFormAnagrafica = ({
onValueChange={async (value) => { onValueChange={async (value) => {
field.onChange(value.value); field.onChange(value.value);
await form.trigger("luogo_nascita"); await form.trigger("luogo_nascita");
form.setValue("luogo_nascita", null);
}} }}
options={UnpackOptions({ options: nazioni_options })} options={nazioni.map((n) => ({
label: n.nome,
value: n.catasto,
}))}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
)} );
}}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="luogo_nascita" name="luogo_nascita"
render={({ field }) => { render={({ field }) => {
if (form.watch("nazione_nascita") === "0") { if (form.watch("nazione_nascita") === ITALY_CATASTO_CODE) {
const options = comuni_options.map((c) => c.nome); const parsedComuneNascita = parseDBComune(
field.value,
comuni,
);
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">
@ -340,8 +360,11 @@ export const ProfileFormAnagrafica = ({
<FormControl> <FormControl>
<MultiSelect <MultiSelect
defaultValue={ defaultValue={
field.value field.value && parsedComuneNascita
? getComuneFieldValue(field.value, options) ? {
label: `${parsedComuneNascita.nome} (${parsedComuneNascita.sigla})`,
value: parsedComuneNascita.catasto,
}
: undefined : undefined
} }
elementId="select-comune" elementId="select-comune"
@ -352,7 +375,10 @@ export const ProfileFormAnagrafica = ({
await form.trigger("luogo_nascita"); await form.trigger("luogo_nascita");
await form.trigger("codice_fiscale"); await form.trigger("codice_fiscale");
}} }}
options={UnpackOptions({ options })} options={comuni.map((c) => ({
label: `${c.nome} (${c.sigla})`,
value: c.catasto,
}))}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
@ -377,6 +403,7 @@ export const ProfileFormAnagrafica = ({
await form.trigger("codice_fiscale"); await form.trigger("codice_fiscale");
}} }}
type="text" type="text"
value={field.value || ""}
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
@ -680,9 +707,9 @@ const ResidenzaSection = ({
setValue: (field: keyof ProfileFormValues, value: any) => void; setValue: (field: keyof ProfileFormValues, value: any) => void;
trigger: (name?: keyof ProfileFormValues) => Promise<boolean>; trigger: (name?: keyof ProfileFormValues) => Promise<boolean>;
}) => { }) => {
const { comuni, nazioni, provincie } = useCatasto();
const { t } = useTranslation(); const { t } = useTranslation();
const nazioni_options = GetNazioni();
const comuni_options = getComuni();
const nazione_residenza = useWatch({ const nazione_residenza = useWatch({
control: control, control: control,
name: "nazione_residenza", name: "nazione_residenza",
@ -698,7 +725,9 @@ const ResidenzaSection = ({
<FormField <FormField
control={control} control={control}
name="nazione_residenza" name="nazione_residenza"
render={({ field }) => ( render={({ field }) => {
const parsedNazioneResidenza = parseDBNazione(field.value, nazioni);
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">
<FormLabel htmlFor="nazione_residenza"> <FormLabel htmlFor="nazione_residenza">
@ -709,33 +738,45 @@ const ResidenzaSection = ({
<MultiSelect <MultiSelect
defaultValue={ defaultValue={
field.value field.value
? RepackValues({ ? parsedNazioneResidenza
options: nazioni_options, ? {
values: field.value, label: parsedNazioneResidenza.nome,
}) value: parsedNazioneResidenza.catasto,
}
: {
label: field.value,
value: field.value,
}
: undefined : undefined
} }
elementId="nazione_residenza" elementId="nazione_residenza"
elementName="nazione" elementName="nazione"
isMulti={false} isMulti={false}
onValueChange={async (value) => { onValueChange={async (value) => {
setValue("provincia_residenza", ""); setValue("provincia_residenza", null);
setValue("comune_residenza", null);
field.onChange(value.value); field.onChange(value.value);
await trigger("comune_residenza"); await trigger("comune_residenza");
}} }}
options={UnpackOptions({ options: nazioni_options })} options={nazioni.map((n) => ({
label: n.nome,
value: n.catasto,
}))}
placeholder="Seleziona" placeholder="Seleziona"
/> />
</FormItem> </FormItem>
)} );
}}
/> />
<FormField <FormField
control={control} control={control}
name="provincia_residenza" name="provincia_residenza"
render={({ field }) => { render={({ field }) => {
const provinciaName = const parseProvincia = getProvinciaFromSigla(
field.value && getProvinciaFromSigla(field.value); field.value,
provincie,
);
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">
@ -745,13 +786,13 @@ const ResidenzaSection = ({
<FormMessage /> <FormMessage />
</div> </div>
{nazione_residenza === "0" ? ( {nazione_residenza === ITALY_CATASTO_CODE ? (
<MultiSelect <MultiSelect
defaultValue={ defaultValue={
field.value && provinciaName field.value && parseProvincia
? { ? {
label: provinciaName, label: parseProvincia.nome,
value: field.value, value: parseProvincia.sigla,
} }
: undefined : undefined
} }
@ -760,8 +801,9 @@ const ResidenzaSection = ({
isMulti={false} isMulti={false}
onValueChange={(value) => { onValueChange={(value) => {
field.onChange(value.value); field.onChange(value.value);
setValue("comune_residenza", null);
}} }}
options={provincieWithSigla.map((prov) => ({ options={provincie.map((prov) => ({
label: prov.nome, label: prov.nome,
value: prov.sigla, value: prov.sigla,
}))} }))}
@ -769,7 +811,12 @@ const ResidenzaSection = ({
/> />
) : ( ) : (
<FormControl> <FormControl>
<Input {...field} id="provincia_residenza" type="text" /> <Input
{...field}
id="provincia_residenza"
type="text"
value={field.value || undefined}
/>
</FormControl> </FormControl>
)} )}
</FormItem> </FormItem>
@ -782,14 +829,19 @@ const ResidenzaSection = ({
control={control} control={control}
name="comune_residenza" name="comune_residenza"
render={({ field }) => { render={({ field }) => {
if (nazione_residenza === "0") { if (nazione_residenza === ITALY_CATASTO_CODE) {
const options = comuni_options const parsedComuneResidenza = parseDBComune(field.value, comuni);
const options = comuni
.filter((comune) => .filter((comune) =>
provincia_residenza provincia_residenza
? comune.provincia === provincia_residenza ? comune.sigla === provincia_residenza
: true, : true,
) )
.map((comune) => comune.nome); .map((comune) => ({
label: comune.nome,
value: comune.catasto,
}));
return ( return (
<FormItem className="w-full"> <FormItem className="w-full">
@ -803,8 +855,11 @@ const ResidenzaSection = ({
<FormControl> <FormControl>
<MultiSelect <MultiSelect
defaultValue={ defaultValue={
field.value field.value && parsedComuneResidenza
? getComuneFieldValue(field.value, options) ? {
label: parsedComuneResidenza.nome,
value: parsedComuneResidenza.catasto,
}
: undefined : undefined
} }
elementId="select-comune" elementId="select-comune"
@ -814,9 +869,7 @@ const ResidenzaSection = ({
field.onChange(value.value); field.onChange(value.value);
await trigger("comune_residenza"); await trigger("comune_residenza");
}} }}
options={UnpackOptions({ options={options}
options: options,
})}
placeholder="Seleziona..." placeholder="Seleziona..."
/> />
</FormControl> </FormControl>
@ -841,6 +894,7 @@ const ResidenzaSection = ({
await trigger("comune_residenza"); await trigger("comune_residenza");
}} }}
type="text" type="text"
value={field.value || undefined}
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

@ -0,0 +1,60 @@
import type { Comuni } from "~/schemas/public/Comuni";
import type { Nazioni } from "~/schemas/public/Nazioni";
import type { Provincie } from "~/schemas/public/Provincie";
export const ITALY_CATASTO_CODE = "0000";
export const isNazioneCatastoValid = (str: string) => {
if (str.length !== 4) return false;
if (str === ITALY_CATASTO_CODE) return true;
return /^Z[0-9]{3}$/.test(str);
};
export const isComuneCatastoValid = (str: string) => {
if (str.length !== 4) return false;
return /^[A-Z][0-9]{3}$/.test(str);
};
export const getComuneFromCatasto = (
codice_catasto: string,
comuni_options: Comuni[],
): Comuni | undefined => {
return comuni_options.find((c) => c.catasto === codice_catasto);
};
export const getNazioneFromCatasto = (
codice_catasto: string,
nazioni_options: Nazioni[],
): Nazioni | undefined => {
return nazioni_options.find((n) => n.catasto === codice_catasto);
};
export const parseDBComune = (
db_value: string | null,
comuni: Comuni[],
): Comuni | undefined => {
if (!db_value) return undefined;
if (isComuneCatastoValid(db_value)) {
return getComuneFromCatasto(db_value, comuni);
}
return undefined;
};
export const parseDBNazione = (
db_value: string | null,
nazioni: Nazioni[],
): Nazioni | undefined => {
if (!db_value) return undefined;
if (isNazioneCatastoValid(db_value)) {
return getNazioneFromCatasto(db_value, nazioni);
}
return undefined;
};
export const getProvinciaFromSigla = (
str: string | null,
provincie: Provincie[],
): Provincie | undefined => {
if (!str) return undefined;
if (str.length !== 2) return undefined;
return provincie.find((p) => p.sigla === str);
};

View file

@ -1,8 +1,9 @@
import dynamic from "next/dynamic"; 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 type { Comuni } from "~/schemas/public/Comuni";
import { searchNazioneByIndex } from "~/i18n/nazioni"; import type { Nazioni } from "~/schemas/public/Nazioni";
import { ITALY_CATASTO_CODE } from "./catasto";
export const FormDebug: React.ElementType = export const FormDebug: React.ElementType =
process.env.NODE_ENV === "development" process.env.NODE_ENV === "development"
@ -87,50 +88,22 @@ const getAlternativeCF = (input: string): string[] => {
return result; return result;
}; };
const genCatasto = ({
luogoNascita,
nazioneNascita,
}: {
luogoNascita: string;
nazioneNascita: string;
}) => {
let catasto = "";
if (nazioneNascita === "0") {
if (luogoNascita !== "") {
const comune = getComuneFromIndex(parseInt(luogoNascita));
if (comune) {
catasto = comune.CATASTO;
}
}
} else {
if (nazioneNascita !== "") {
const nazione = searchNazioneByIndex(parseInt(nazioneNascita));
if (nazione) {
catasto = nazione.CATASTO;
}
}
}
return catasto;
};
const gen15CF = ({ const gen15CF = ({
cognome, cognome,
nome, nome,
dataNascita, dataNascita,
sesso, sesso,
luogoNascita, catasto,
nazioneNascita,
}: { }: {
cognome: string; cognome: string;
nome: string; nome: string;
dataNascita: Date; dataNascita: Date;
sesso: string; sesso: string;
luogoNascita: string; catasto: string;
nazioneNascita: string;
}): string => { }): string => {
const data = new Date(dataNascita); const data = new Date(dataNascita);
return `${getInizialiCF(cognome)}${getInizialiCF(nome)}${data.getFullYear().toString().slice(-2)}${getMonthLetterCF(data.getMonth())}${getDayCF(data.getDate(), sesso)}${genCatasto({ luogoNascita, nazioneNascita })}`; return `${getInizialiCF(cognome)}${getInizialiCF(nome)}${data.getFullYear().toString().slice(-2)}${getMonthLetterCF(data.getMonth())}${getDayCF(data.getDate(), sesso)}${catasto}`;
}; };
const set1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const set1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@ -165,6 +138,8 @@ export const checkFiscalCodeValidity = ({
refinementContext, refinementContext,
luogoNascita, luogoNascita,
nazioneNascita, nazioneNascita,
comuneCatasto,
nazioneCatasto,
}: { }: {
path: string; path: string;
codiceFiscale: string; codiceFiscale: string;
@ -175,14 +150,31 @@ export const checkFiscalCodeValidity = ({
refinementContext: z.RefinementCtx; refinementContext: z.RefinementCtx;
luogoNascita: string; luogoNascita: string;
nazioneNascita: string; nazioneNascita: string;
comuneCatasto: Comuni | undefined;
nazioneCatasto: Nazioni | undefined;
}) => { }) => {
const cognomeUpper = cognome.toUpperCase(); const cognomeUpper = cognome.toUpperCase();
const nomeUpper = nome.toUpperCase(); const nomeUpper = nome.toUpperCase();
let codiceCatasto = "";
if (nazioneNascita === ITALY_CATASTO_CODE) {
if (luogoNascita !== "") {
if (comuneCatasto) {
codiceCatasto = comuneCatasto.catasto;
}
}
} else {
if (nazioneNascita !== "") {
if (nazioneCatasto) {
codiceCatasto = nazioneCatasto.catasto;
}
}
}
const gen = gen15CF({ const gen = gen15CF({
cognome: cognomeUpper, cognome: cognomeUpper,
dataNascita, dataNascita,
luogoNascita, catasto: codiceCatasto,
nazioneNascita,
nome: nomeUpper, nome: nomeUpper,
sesso, sesso,
}); });
@ -228,7 +220,7 @@ export const checkFiscalCodeValidity = ({
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
input: "", input: "",
message: "Inserisci un codice fiscale valido", message: `Inserisci un codice fiscale valido: received ${codiceFiscale}`,
path: [path], path: [path],
}); });
} }
@ -277,10 +269,13 @@ export const checkFiscalCodeValidity = ({
path: [path], path: [path],
}); });
} }
if (nazioneNascita === "0") {
if (nazioneNascita === ITALY_CATASTO_CODE) {
if (luogoNascita !== "") { if (luogoNascita !== "") {
const comune = getComuneFromIndex(parseInt(luogoNascita)); if (
if (comune && codiceFiscale.slice(11, 15) !== comune.CATASTO) { comuneCatasto &&
codiceFiscale.slice(11, 15) !== comuneCatasto.catasto
) {
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
input: "", input: "",
@ -291,8 +286,10 @@ export const checkFiscalCodeValidity = ({
} }
} else { } else {
if (nazioneNascita !== "") { if (nazioneNascita !== "") {
const nazione = searchNazioneByIndex(parseInt(nazioneNascita)); if (
if (nazione && codiceFiscale.slice(11, 15) !== nazione.CATASTO) { nazioneCatasto &&
codiceFiscale.slice(11, 15) !== nazioneCatasto.catasto
) {
refinementContext.issues.push({ refinementContext.issues.push({
code: "custom", code: "custom",
input: "", input: "",

View file

@ -33,6 +33,7 @@ import { OverridePasswordForm } from "~/forms/FormOverridePassword";
import { ProfileFormAccount } from "~/forms/FormProfilo_Account"; import { ProfileFormAccount } from "~/forms/FormProfilo_Account";
import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica"; import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { CatastoProvider } from "~/providers/CatastoProvider";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper"; import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
import { zUserId } from "~/server/utils/zod_types"; import { zUserId } from "~/server/utils/zod_types";
@ -193,7 +194,9 @@ const EditUser: NextPageWithLayout<EditUserProps> = ({
</div> </div>
</TabsContent> </TabsContent>
<TabsContent className="mt-5 h-full space-y-6" value="anagrafica"> <TabsContent className="mt-5 h-full space-y-6" value="anagrafica">
<CatastoProvider>
<ProfileFormAnagrafica userData={userData} /> <ProfileFormAnagrafica userData={userData} />
</CatastoProvider>
</TabsContent> </TabsContent>
<TabsContent className="mt-5 h-full space-y-6" value="documenti"> <TabsContent className="mt-5 h-full space-y-6" value="documenti">
<DocumentiPersonali userData={userData} /> <DocumentiPersonali userData={userData} />

View file

@ -4,6 +4,7 @@ import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page"; import { Status500 } from "~/components/status-page";
import { FormNewServizioAcquisto } from "~/forms/FormNewServizioAcquisto"; import { FormNewServizioAcquisto } from "~/forms/FormNewServizioAcquisto";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { CatastoProvider } from "~/providers/CatastoProvider";
import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { ServizioServizioId } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper"; import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
@ -34,11 +35,13 @@ const OnboardUser: NextPageWithLayout<OnboardUserProps> = ({
Onboarding Utente (Versione Admin) Onboarding Utente (Versione Admin)
</h3> </h3>
</div> </div>
<CatastoProvider>
<FormNewServizioAcquisto <FormNewServizioAcquisto
initialData={data} initialData={data}
isAdmin isAdmin
userId={data.userData.id} userId={data.userData.id}
/> />
</CatastoProvider>
</div> </div>
); );
}; };

View file

@ -16,6 +16,7 @@ import { ChangePasswordForm } from "~/forms/FormChangePassword";
import { ProfileFormAccount } from "~/forms/FormProfilo_Account"; import { ProfileFormAccount } from "~/forms/FormProfilo_Account";
import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica"; import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { CatastoProvider } from "~/providers/CatastoProvider";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { useEnforcedSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
@ -104,7 +105,9 @@ const Dashboard: NextPageWithLayout = () => {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<CatastoProvider>
<ProfileFormAnagrafica userData={userData} /> <ProfileFormAnagrafica userData={userData} />
</CatastoProvider>
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>

View file

@ -5,6 +5,7 @@ import { OnboardTutorial } from "~/components/onboard_tutorial";
import { Status500 } from "~/components/status-page"; import { Status500 } from "~/components/status-page";
import { FormNewServizioAcquisto } from "~/forms/FormNewServizioAcquisto"; import { FormNewServizioAcquisto } from "~/forms/FormNewServizioAcquisto";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { CatastoProvider } from "~/providers/CatastoProvider";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { ServizioServizioId } from "~/schemas/public/Servizio";
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper"; import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
@ -36,7 +37,9 @@ const OnboardServizio: NextPageWithLayout<OnboardServizioProps> = ({
</h3> </h3>
</div> </div>
<OnboardTutorial /> <OnboardTutorial />
<CatastoProvider>
<FormNewServizioAcquisto initialData={data} userId={data.userData.id} /> <FormNewServizioAcquisto initialData={data} userId={data.userData.id} />
</CatastoProvider>
</div> </div>
); );
}; };

View file

@ -0,0 +1,42 @@
import { createContext, type ReactNode, useContext } from "react";
import { LoadingPage } from "~/components/loading";
import type { Comuni } from "~/schemas/public/Comuni";
import type { Nazioni } from "~/schemas/public/Nazioni";
import type { Provincie } from "~/schemas/public/Provincie";
import { api } from "~/utils/api";
type CatastoContextType = {
comuni: Comuni[];
provincie: Provincie[];
nazioni: Nazioni[];
};
const CatastoContext = createContext<CatastoContextType | null>(null);
export const CatastoProvider = ({ children }: { children: ReactNode }) => {
const { data: comuni = [], isLoading: isLoadingComune } =
api.catasto.getComuni.useQuery();
const { data: provincie = [], isLoading: isLoadingProvincie } =
api.catasto.getProvincie.useQuery();
const { data: nazioni = [], isLoading: isLoadingNazioni } =
api.catasto.getNazioni.useQuery();
return (
<CatastoContext.Provider value={{ comuni, provincie, nazioni }}>
{isLoadingComune || isLoadingProvincie || isLoadingNazioni ? (
<LoadingPage />
) : (
children
)}
</CatastoContext.Provider>
);
};
export const useCatasto = () => {
const context = useContext(CatastoContext);
if (!context) {
throw new Error("useCatasto must be used within a CatastoProvider");
}
return context;
};

View file

@ -19,6 +19,7 @@ import { stripeRouter } from "~/server/api/routers/stripe";
import { testRouter } from "~/server/api/routers/test"; import { testRouter } from "~/server/api/routers/test";
import { usersRouter } from "~/server/api/routers/users"; import { usersRouter } from "~/server/api/routers/users";
import { createTRPCRouter } from "~/server/api/trpc"; import { createTRPCRouter } from "~/server/api/trpc";
import { catastoRouter } from "./routers/catasto";
import { syncRouter } from "./routers/sync"; import { syncRouter } from "./routers/sync";
//import { lazy } from '@trpc/server'; //import { lazy } from '@trpc/server';
@ -45,5 +46,6 @@ export const appRouter = createTRPCRouter({
test: testRouter, test: testRouter,
users: usersRouter, users: usersRouter,
sync: syncRouter, sync: syncRouter,
catasto: catastoRouter,
}); });
export type AppRouter = typeof appRouter; export type AppRouter = typeof appRouter;

View file

@ -0,0 +1,41 @@
import z from "zod";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
import {
getComuneById,
getComuni,
getNazioneById,
getNazioni,
getProvincie,
} from "~/server/controllers/catasto.controller";
import { zComuneId, zNazioneId } from "~/server/utils/zod_types";
export const catastoRouter = createTRPCRouter({
getComuni: publicProcedure.query(async () => {
return await getComuni();
}),
getComuneById: publicProcedure
.input(
z.object({
id: zComuneId,
}),
)
.query(async ({ input }) => {
return await getComuneById(input.id);
}),
getProvincie: publicProcedure.query(async () => {
return await getProvincie();
}),
getNazioni: publicProcedure.query(async () => {
return await getNazioni();
}),
getNazioneById: publicProcedure
.input(
z.object({
id: zNazioneId,
}),
)
.query(async ({ input }) => {
return await getNazioneById(input.id);
}),
});

View file

@ -0,0 +1,67 @@
import { TRPCError } from "@trpc/server";
import type { Comuni, ComuniId } from "~/schemas/public/Comuni";
import type { NazioniId } from "~/schemas/public/Nazioni";
import { db } from "../db";
export type Comune = Pick<Comuni, "id" | "nome" | "sigla">;
export const getComuni = async () => {
try {
return await db.selectFrom("comuni").selectAll().execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while getting Comuni: ${(e as Error).message}`,
});
}
};
export const getComuneById = async (id: ComuniId) => {
try {
return await db
.selectFrom("comuni")
.selectAll()
.where("id", "=", id)
.executeTakeFirst();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while getting ComuneById: ${(e as Error).message}`,
});
}
};
export const getProvincie = async () => {
try {
return await db.selectFrom("provincie").selectAll().execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while getting Provincie: ${(e as Error).message}`,
});
}
};
export const getNazioni = async () => {
try {
return await db.selectFrom("nazioni").selectAll().execute();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while getting Nazioni: ${(e as Error).message}`,
});
}
};
export const getNazioneById = async (id: NazioniId) => {
try {
return await db
.selectFrom("nazioni")
.selectAll()
.where("id", "=", id)
.executeTakeFirst();
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error while getting NazioneById: ${(e as Error).message}`,
});
}
};

View file

@ -3,9 +3,11 @@ import type { AnnunciId } from "~/schemas/public/Annunci";
import type { BanlistId } from "~/schemas/public/Banlist"; import type { BanlistId } from "~/schemas/public/Banlist";
import BanType from "~/schemas/public/BanType"; import BanType from "~/schemas/public/BanType";
import type { ChatsChatid } from "~/schemas/public/Chats"; import type { ChatsChatid } from "~/schemas/public/Chats";
import type { ComuniId } from "~/schemas/public/Comuni";
import type { EtichetteIdEtichetta } from "~/schemas/public/Etichette"; import type { EtichetteIdEtichetta } from "~/schemas/public/Etichette";
import type { FlagsId } from "~/schemas/public/Flags"; import type { FlagsId } from "~/schemas/public/Flags";
import type { MessagesMessageid } from "~/schemas/public/Messages"; import type { MessagesMessageid } from "~/schemas/public/Messages";
import type { NazioniId } from "~/schemas/public/Nazioni";
import type { OrdiniOrdineId } from "~/schemas/public/Ordini"; import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
import type { PaymentsId } from "~/schemas/public/Payments"; import type { PaymentsId } from "~/schemas/public/Payments";
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario"; import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
@ -82,3 +84,13 @@ export const zPagamentoId = z.custom<PaymentsId>(
(val) => typeof val === "string", (val) => typeof val === "string",
"falied to validate PagamentoId", "falied to validate PagamentoId",
); );
export const zComuneId = z.custom<ComuniId>(
(val) => typeof val === "number",
"falied to validate ComuneId",
);
export const zNazioneId = z.custom<NazioniId>(
(val) => typeof val === "number",
"falied to validate NazioneId",
);