infoalloggi-monorepo/apps/infoalloggi/src/forms/FormProfilo_Anagrafica.tsx

836 lines
24 KiB
TypeScript
Raw Normal View History

2025-08-28 18:27:07 +02:00
import { differenceInYears, format } from "date-fns";
import { enUS, it } from "date-fns/locale";
import { CalendarIcon } from "lucide-react";
import toast from "react-hot-toast";
import { z } from "zod/v4";
2025-08-04 17:45:44 +02:00
import {
2025-08-28 18:27:07 +02:00
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
2025-08-04 17:45:44 +02:00
} from "~/components/custom_ui/form";
import Input from "~/components/custom_ui/input";
import {
2025-08-28 18:27:07 +02:00
MultiSelect,
RepackValues,
UnpackOptions,
} from "~/components/custom_ui/multiselect";
import { Button } from "~/components/ui/button";
2025-08-04 17:45:44 +02:00
import { Calendar } from "~/components/ui/calendar";
import {
2025-08-28 18:27:07 +02:00
Popover,
PopoverContent,
PopoverTrigger,
} from "~/components/ui/popover";
2025-08-04 17:45:44 +02:00
import { Separator } from "~/components/ui/separator";
2025-08-28 18:27:07 +02:00
import { Switch } from "~/components/ui/switch";
import { getComuniNames } from "~/i18n/comuni";
2025-08-04 17:45:44 +02:00
import { GetNazioni } from "~/i18n/nazioni";
import { provincie } from "~/i18n/provincie";
import { checkFiscalCodeValidity, getComuneFieldValue } from "~/lib/form_utils";
2025-08-28 18:27:07 +02:00
import { cn } from "~/lib/utils";
import { useZodForm } from "~/lib/zodForm";
import { useTranslation } from "~/providers/I18nProvider";
2025-08-04 17:45:44 +02:00
import type { UserWithAnagrafica } from "~/server/services/user.service";
2025-08-28 18:27:07 +02:00
import { api } from "~/utils/api";
2025-08-04 17:45:44 +02:00
export const ProfileFormAnagrafica = ({
2025-08-28 18:27:07 +02:00
userData,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
userData: UserWithAnagrafica;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const profileFormSchema = z
.object({
data_nascita: z.date().optional(),
luogo_nascita: z.string().optional(),
sesso: z.string().optional(),
nazione_nascita: z.string().optional(),
codice_fiscale: z.string().optional(),
via_residenza: z.string().optional(),
civico_residenza: z.string().optional(),
cap_residenza: z.string().optional(),
comune_residenza: z.string().optional(),
provincia_residenza: z.string().optional(),
nazione_residenza: z.string().optional(),
azienda: z.boolean(),
fatturazione_aziendale: z.boolean(),
codice_destinatario: z.string().length(7).nullable(),
ragione_sociale: z.string().nullable(),
sede_legale: z.string().nullable(),
legale_rappresentante: z.string().nullable(),
p_iva: z.string().nullable(),
})
.superRefine(
(
{
data_nascita,
codice_fiscale,
sesso,
luogo_nascita,
nazione_nascita,
cap_residenza,
azienda,
p_iva,
ragione_sociale,
sede_legale,
legale_rappresentante,
fatturazione_aziendale,
codice_destinatario,
},
refinementContext,
) => {
if (azienda) {
if (!ragione_sociale || ragione_sociale.length < 1) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci la ragione sociale",
path: ["ragione_sociale"],
input: "",
});
}
if (!sede_legale || sede_legale.length < 1) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci la sede legale",
path: ["sede_legale"],
input: "",
});
}
if (!p_iva || p_iva.length < 1) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci la partita iva",
path: ["p_iva"],
input: "",
});
}
if (!legale_rappresentante || legale_rappresentante.length < 1) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci il legale rappresentante",
path: ["legale_rappresentante"],
input: "",
});
}
}
if (fatturazione_aziendale) {
if (!codice_destinatario || codice_destinatario.length !== 7) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci il codice destinatario",
path: ["codice_destinatario"],
input: "",
});
}
}
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
if (cap_residenza) {
if (cap_residenza.length !== 5) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci un CAP valido",
input: "",
});
}
}
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
if (data_nascita) {
const diff = differenceInYears(data_nascita, new Date());
if (diff === 0) {
refinementContext.issues.push({
code: "custom",
message: "Inserisci una data valida",
path: ["data_nascita"],
input: "",
});
}
}
if (
codice_fiscale &&
data_nascita &&
sesso &&
luogo_nascita &&
nazione_nascita
) {
checkFiscalCodeValidity({
path: "codice_fiscale",
codiceFiscale: codice_fiscale,
cognome: userData.cognome?.toUpperCase() || "",
nome: userData.nome?.toUpperCase() || "",
dataNascita: data_nascita,
sesso,
refinementContext,
luogoNascita: luogo_nascita,
nazioneNascita: nazione_nascita,
});
}
},
);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
type ProfileFormValues = z.infer<typeof profileFormSchema>;
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const defaultValues: ProfileFormValues = {
data_nascita: userData.data_nascita || new Date(),
luogo_nascita: userData.luogo_nascita || "",
nazione_nascita: userData.nazione_nascita || "",
codice_fiscale: userData.codice_fiscale || "",
via_residenza: userData.via_residenza || "",
civico_residenza: userData.civico_residenza || "",
cap_residenza: userData.cap_residenza || "",
comune_residenza: userData.comune_residenza || "",
provincia_residenza: userData.provincia_residenza || "",
nazione_residenza: userData.nazione_residenza || "",
sesso: userData.sesso || "",
azienda: userData.azienda || false,
ragione_sociale: userData.ragione_sociale || null,
sede_legale: userData.sede_legale || null,
legale_rappresentante: userData.legale_rappresentante || null,
p_iva: userData.p_iva || null,
fatturazione_aziendale: userData.fatturazione_aziendale || false,
codice_destinatario: userData.codice_destinatario || null,
};
const { locale, t } = useTranslation();
z.config(z.locales[locale]());
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const form = useZodForm(profileFormSchema, {
defaultValues: defaultValues,
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const utils = api.useUtils();
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const { mutate } = api.users.editUserAnagrafica.useMutation({
onSuccess: async () => {
toast.success(t.profile.aggiornato);
await utils.auth.getSession.invalidate();
await utils.users.getUser.invalidate();
await utils.users.getClientProfilo.invalidate();
},
onError: (error) => {
toast.error(error.message);
},
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
function onSubmit(fields: ProfileFormValues) {
mutate({
...fields,
userid: userData.id,
idanagrafica: userData.idanagrafica || undefined,
});
}
const nazioni_options = GetNazioni();
const comuni_options = getComuniNames();
const azienda = form.watch("azienda");
const nazione_nascita = form.watch("nazione_nascita");
const nazione_residenza = form.watch("nazione_residenza");
const fatturazione_aziendale = form.watch("fatturazione_aziendale");
const DatePickerLocale = locale === "it" ? it : enUS;
return (
<>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="h-full space-y-8 px-0.5"
>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="data_nascita"
render={({ field }) => (
<FormItem className="flex w-full flex-col">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="data_nascita">
{t.anagrafica.dataNascita}
</FormLabel>
<FormMessage />
</div>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={"outline"}
id="data_nascita"
className={cn(
"h-[42px] w-full pl-3 text-left font-medium",
!field.value && "text-muted-foreground",
`text-primary dark:bg-primary rounded-lg border border-neutral-300 bg-white shadow-xs outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium hover:border-neutral-400 focus:border-neutral-400 disabled:cursor-not-allowed disabled:opacity-50 aria-expanded:border-neutral-400 dark:border-neutral-500 dark:text-white dark:placeholder:text-neutral-400 dark:hover:border-neutral-400 dark:focus:border-transparent dark:aria-expanded:border-neutral-400`,
)}
>
{field.value ? (
format(field.value, "PPP", {
locale: DatePickerLocale,
})
) : (
<span>{t.anagrafica.scegli_data}</span>
)}
<CalendarIcon className="ml-auto size-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
locale={DatePickerLocale}
endMonth={new Date()}
mode="single"
defaultMonth={field.value}
captionLayout="dropdown"
selected={field.value}
onSelect={field.onChange}
disabled={(date) =>
date > new Date() || date < new Date("1900-01-01")
}
autoFocus
/>
</PopoverContent>
</Popover>
</FormItem>
)}
/>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormField
control={form.control}
name="nazione_nascita"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-naz">
{t.anagrafica.nazioneNascita}
</FormLabel>
<FormMessage />
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormControl>
<MultiSelect
elementId="select-naz"
elementName="nazione_nascita"
options={UnpackOptions({ options: nazioni_options })}
defaultValue={
field.value
? RepackValues({
options: nazioni_options,
values: field.value,
})
: undefined
}
onValueChange={async (value) => {
field.onChange(value.value);
await form.trigger("luogo_nascita");
}}
placeholder="Seleziona..."
isMulti={false}
/>
</FormControl>
</FormItem>
)}
/>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormField
control={form.control}
name="luogo_nascita"
render={({ field }) => {
if (nazione_nascita === "0") {
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-comune">
{t.anagrafica.luogoNascita}
</FormLabel>
<FormMessage />
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormControl>
<MultiSelect
elementId="select-comune"
elementName="luogo_nascita"
options={UnpackOptions({ options: comuni_options })}
defaultValue={
field.value
? getComuneFieldValue(field.value, comuni_options)
: undefined
}
onValueChange={async (value) => {
field.onChange(value.value);
await form.trigger("luogo_nascita");
await form.trigger("codice_fiscale");
}}
placeholder="Seleziona..."
isMulti={false}
/>
</FormControl>
</FormItem>
);
}
return (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="luogoNascita">
{t.anagrafica.luogoNascita}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
type="text"
id="luogoNascita"
onChange={async (v) => {
field.onChange(v.target.value);
await form.trigger("luogo_nascita");
await form.trigger("codice_fiscale");
}}
/>
</FormControl>
</FormItem>
);
}}
/>
</div>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="sesso"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="select-sesso">
{t.anagrafica.sesso}
</FormLabel>
<FormMessage />
</div>
<MultiSelect
elementId="select-sesso"
elementName="sesso"
isMulti={false}
placeholder=""
defaultValue={t.anagrafica.sesso_options.filter(
(v) => v.value === field.value,
)}
options={t.anagrafica.sesso_options}
onValueChange={(value) => {
field.onChange(value.value);
}}
/>
</FormItem>
)}
/>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormField
control={form.control}
name="codice_fiscale"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="codice_fiscale">
{t.anagrafica.codiceFiscale}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
onChange={async (v) => {
field.onChange(v.target.value.toUpperCase());
await form.trigger("codice_fiscale");
}}
type="text"
className="uppercase"
id="codice_fiscale"
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="azienda"
render={({ field }) => (
<FormItem className="mt-6 w-full">
<div className="flex items-center justify-center gap-x-4">
<FormLabel htmlFor="azienda">Azienda?</FormLabel>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={(v) => {
field.onChange(v);
if (!v) {
form.setValue("ragione_sociale", null);
form.setValue("sede_legale", null);
form.setValue("p_iva", null);
}
}}
id="azienda"
className="data-[state=checked]:bg-neutral-700"
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
</div>
{azienda && (
<>
<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}
type="text"
id="ragione_sociale"
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}
type="text"
id="sede_legale"
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}
type="text"
id="p_iva"
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}
type="text"
id="legale_rappresentante"
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">
Vuoi che la fatturazione sia intestata alla tua azienda?
</FormLabel>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={(v) => {
field.onChange(v);
if (!v) {
form.setValue("codice_destinatario", null);
}
}}
id="fatturazione_aziendale"
className="data-[state=checked]:bg-neutral-700"
/>
</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">
Codice destinatario (SDI)
</FormLabel>
<FormMessage />
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormControl>
<Input
{...field}
type="text"
id="codice_destinatario"
value={field.value || undefined}
/>
</FormControl>
</FormItem>
)}
/>
)}
</>
)}
<Separator />
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="nazione_residenza"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="nazione_residenza">
{t.recapiti.nazione}
</FormLabel>
<FormMessage />
</div>
<MultiSelect
elementId="nazione_residenza"
options={UnpackOptions({ options: nazioni_options })}
onValueChange={async (value) => {
form.setValue("provincia_residenza", "");
field.onChange(value.value);
await form.trigger("comune_residenza");
}}
defaultValue={
field.value
? RepackValues({
options: nazioni_options,
values: field.value,
})
: undefined
}
elementName="nazione"
isMulti={false}
placeholder="Seleziona"
/>
</FormItem>
)}
/>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormField
control={form.control}
name="provincia_residenza"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="provincia_residenza">
{t.recapiti.provincia}
</FormLabel>
<FormMessage />
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
{nazione_residenza === "0" ? (
<MultiSelect
elementId="provincia_residenza"
options={UnpackOptions({ options: provincie })}
onValueChange={(value) => {
field.onChange(value.value);
}}
defaultValue={
field.value
? RepackValues({
options: provincie,
values: field.value,
})
: undefined
}
elementName="provincia"
isMulti={false}
placeholder="Seleziona"
/>
) : (
<FormControl>
<Input {...field} type="text" id="provincia_residenza" />
</FormControl>
)}
</FormItem>
)}
/>
</div>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="comune_residenza"
render={({ field }) => {
if (nazione_residenza === "0") {
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>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormControl>
<MultiSelect
elementId="select-comune"
elementName="comune_residenza"
options={UnpackOptions({ options: comuni_options })}
defaultValue={
field.value
? getComuneFieldValue(field.value, comuni_options)
: undefined
}
onValueChange={async (value) => {
field.onChange(value.value);
await form.trigger("comune_residenza");
}}
placeholder="Seleziona..."
isMulti={false}
/>
</FormControl>
</FormItem>
);
}
return (
<FormItem className="w-full">
<div className="flex w-full flex-wrap items-center gap-x-2">
<FormLabel htmlFor="comune_residenza">
{t.recapiti.comune}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
type="text"
id="comune_residenza"
className="w-full"
onChange={async (v) => {
field.onChange(v.target.value);
await form.trigger("comune_residenza");
}}
/>
</FormControl>
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="cap_residenza"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="cap_residenza">
{t.recapiti.cap}
</FormLabel>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormMessage />
</div>
<FormControl>
<Input
{...field}
type="text"
id="cap_residenza"
placeholder="00100"
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
<FormField
control={form.control}
name="via_residenza"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="via_residenza">
{t.recapiti.indirizzo}
</FormLabel>
<FormMessage />
</div>
<FormControl>
<Input
{...field}
type="text"
id="via_residenza"
placeholder="Via Roma"
/>
</FormControl>
</FormItem>
)}
/>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormField
control={form.control}
name="civico_residenza"
render={({ field }) => (
<FormItem className="w-full">
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="civico_residenza">
{t.recapiti.civico}
</FormLabel>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<FormMessage />
</div>
<FormControl>
<Input
{...field}
type="text"
id="civico_residenza"
placeholder="50"
/>
</FormControl>
</FormItem>
)}
/>
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<Button type="submit">{t.profile.aggiorna}</Button>
</form>
</Form>
</>
);
2025-08-04 17:45:44 +02:00
};