2026-02-24 10:40:32 +01:00
|
|
|
import { format } from "date-fns";
|
|
|
|
|
import { it } from "date-fns/locale";
|
|
|
|
|
import { CalendarIcon } from "lucide-react";
|
|
|
|
|
import toast from "react-hot-toast";
|
|
|
|
|
import { z } from "zod/v4";
|
|
|
|
|
import {
|
|
|
|
|
CredenzaBody,
|
|
|
|
|
CredenzaClose,
|
|
|
|
|
CredenzaFooter,
|
|
|
|
|
} from "~/components/custom_ui/credenza";
|
|
|
|
|
import {
|
|
|
|
|
Form,
|
|
|
|
|
FormControl,
|
|
|
|
|
FormDescription,
|
|
|
|
|
FormField,
|
|
|
|
|
FormItem,
|
|
|
|
|
FormLabel,
|
|
|
|
|
FormMessage,
|
|
|
|
|
} from "~/components/custom_ui/form";
|
|
|
|
|
import { MultiSelect } from "~/components/custom_ui/multiselect";
|
|
|
|
|
import { Button } from "~/components/ui/button";
|
|
|
|
|
import { Calendar } from "~/components/ui/calendar";
|
|
|
|
|
import {
|
|
|
|
|
Popover,
|
|
|
|
|
PopoverContent,
|
|
|
|
|
PopoverTrigger,
|
|
|
|
|
} from "~/components/ui/popover";
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from "~/components/ui/select";
|
|
|
|
|
import { Switch } from "~/components/ui/switch";
|
|
|
|
|
import { cn, formatCurrency } from "~/lib/utils";
|
|
|
|
|
import { useZodForm } from "~/lib/zodForm";
|
|
|
|
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
|
|
|
|
import type { Prezziario } from "~/schemas/public/Prezziario";
|
|
|
|
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
|
|
|
|
import type { UsersId } from "~/schemas/public/Users";
|
|
|
|
|
import { zPrezziarioId } from "~/server/utils/zod_types";
|
|
|
|
|
import { api } from "~/utils/api";
|
|
|
|
|
|
|
|
|
|
type FormProps = {
|
|
|
|
|
prezziario: Prezziario[];
|
|
|
|
|
servizioId: ServizioServizioId;
|
|
|
|
|
userId: UsersId;
|
|
|
|
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const FormNewOrder = ({
|
|
|
|
|
prezziario,
|
|
|
|
|
servizioId,
|
|
|
|
|
userId,
|
|
|
|
|
setOpen,
|
|
|
|
|
}: FormProps) => {
|
|
|
|
|
const schema = z.object({
|
|
|
|
|
packid: zPrezziarioId,
|
|
|
|
|
isActive: z.boolean(),
|
2026-03-08 01:02:57 +01:00
|
|
|
type: z.enum(OrderTypeEnum),
|
2026-02-24 10:40:32 +01:00
|
|
|
created_at: z.date(),
|
2026-03-08 01:02:57 +01:00
|
|
|
amount_cent: z.number(),
|
|
|
|
|
sconto: z.number().min(0).max(100),
|
2026-02-24 10:40:32 +01:00
|
|
|
});
|
|
|
|
|
type FormValues = z.infer<typeof schema>;
|
|
|
|
|
|
|
|
|
|
z.config(z.locales.it());
|
|
|
|
|
|
|
|
|
|
const form = useZodForm(schema, {
|
2026-03-08 01:02:57 +01:00
|
|
|
defaultValues: {
|
|
|
|
|
sconto: 0,
|
|
|
|
|
amount_cent: 0,
|
|
|
|
|
isActive: false,
|
|
|
|
|
},
|
2026-02-24 10:40:32 +01:00
|
|
|
});
|
|
|
|
|
const utils = api.useUtils();
|
|
|
|
|
const { mutate: createOrder } = api.servizio.createOrdine.useMutation({
|
|
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(`Errore nella creazione dell'ordine: ${error.message}`);
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async () => {
|
|
|
|
|
toast.success("Ordine creato con successo");
|
2026-03-08 01:02:57 +01:00
|
|
|
await utils.servizio.invalidate();
|
2026-02-24 10:40:32 +01:00
|
|
|
setOpen(false);
|
|
|
|
|
form.reset();
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-03-08 01:02:57 +01:00
|
|
|
const { watch } = form;
|
|
|
|
|
|
|
|
|
|
const scontoWch = watch(["sconto", "amount_cent"]);
|
2026-02-24 10:40:32 +01:00
|
|
|
function onSubmit(fields: FormValues) {
|
|
|
|
|
createOrder({
|
2026-03-08 01:02:57 +01:00
|
|
|
data: { ...fields, servizio_id: servizioId, userid: userId },
|
2026-02-24 10:40:32 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Form {...form}>
|
|
|
|
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
|
|
|
<CredenzaBody className="max-h-[80vh] w-full pb-5">
|
|
|
|
|
<div className="space-y-8 px-0.5">
|
|
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="packid"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem className="w-full">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-x-2">
|
|
|
|
|
<FormLabel htmlFor="select-pack">Servizio</FormLabel>
|
|
|
|
|
<FormMessage />
|
|
|
|
|
</div>
|
|
|
|
|
<FormControl>
|
|
|
|
|
<MultiSelect
|
|
|
|
|
defaultValue={undefined}
|
|
|
|
|
elementId="select-pack"
|
|
|
|
|
elementName="packid"
|
|
|
|
|
isMulti={false}
|
|
|
|
|
onValueChange={(value) => {
|
|
|
|
|
const selected = zPrezziarioId.safeParse(value.value);
|
|
|
|
|
if (selected.success) {
|
|
|
|
|
field.onChange(selected.data);
|
2026-03-08 01:02:57 +01:00
|
|
|
const selectedPrezzo = prezziario.find(
|
|
|
|
|
(p) => p.idprezziario === selected.data,
|
|
|
|
|
);
|
|
|
|
|
if (selectedPrezzo) {
|
|
|
|
|
form.setValue(
|
|
|
|
|
"amount_cent",
|
|
|
|
|
selectedPrezzo.prezzo_cent,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-02-24 10:40:32 +01:00
|
|
|
} else {
|
|
|
|
|
console.error(
|
|
|
|
|
"Invalid packid selected",
|
|
|
|
|
value.value,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
options={prezziario.map((p) => ({
|
|
|
|
|
label: `${p.idprezziario} - ${p.nome_it} (${formatCurrency(p.prezzo_cent / 100)})`,
|
|
|
|
|
value: p.idprezziario,
|
|
|
|
|
}))}
|
|
|
|
|
placeholder="Seleziona..."
|
|
|
|
|
/>
|
|
|
|
|
</FormControl>
|
|
|
|
|
</FormItem>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="type"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem>
|
|
|
|
|
<div className="flex flex-wrap items-center gap-x-2">
|
|
|
|
|
<FormLabel htmlFor="titolo">Tipologia</FormLabel>
|
|
|
|
|
<FormMessage />
|
|
|
|
|
</div>
|
|
|
|
|
<FormControl>
|
|
|
|
|
<Select
|
|
|
|
|
defaultValue={field.value || ""}
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
>
|
|
|
|
|
<FormControl>
|
|
|
|
|
<SelectTrigger className="w-full">
|
|
|
|
|
<SelectValue placeholder="Seleziona tipologia" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
</FormControl>
|
|
|
|
|
<SelectContent id="select-color">
|
|
|
|
|
{Object.values(OrderTypeEnum).map((option) => (
|
|
|
|
|
<SelectItem key={option} value={option}>
|
|
|
|
|
{option}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</FormControl>
|
|
|
|
|
</FormItem>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="created_at"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem className="flex w-full flex-col">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-x-2">
|
|
|
|
|
<FormLabel htmlFor="created_at">Data Ordine</FormLabel>
|
|
|
|
|
<FormMessage />
|
|
|
|
|
</div>
|
|
|
|
|
<Popover>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<FormControl>
|
|
|
|
|
<Button
|
|
|
|
|
className={cn(
|
2026-03-12 12:11:41 +01:00
|
|
|
"h-10 w-full bg-card pl-3 text-left font-medium",
|
2026-02-24 10:40:32 +01:00
|
|
|
!field.value && "text-muted-foreground",
|
|
|
|
|
"rounded-lg border border-neutral-300 shadow-xs outline-hidden file:border-0 file:bg-transparent file:font-medium file:text-sm disabled:cursor-not-allowed disabled:opacity-50 dark:focus:border-transparent",
|
|
|
|
|
)}
|
|
|
|
|
id="created_at"
|
|
|
|
|
variant={"outline"}
|
|
|
|
|
>
|
|
|
|
|
{field.value ? (
|
|
|
|
|
format(field.value, "PPP", {
|
|
|
|
|
locale: it,
|
|
|
|
|
})
|
|
|
|
|
) : (
|
|
|
|
|
<span>Scegli data</span>
|
|
|
|
|
)}
|
|
|
|
|
<CalendarIcon className="ml-auto size-4 opacity-50" />
|
|
|
|
|
</Button>
|
|
|
|
|
</FormControl>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent align="start" className="w-auto p-0">
|
|
|
|
|
<Calendar
|
|
|
|
|
autoFocus
|
|
|
|
|
captionLayout="dropdown"
|
|
|
|
|
defaultMonth={new Date()}
|
|
|
|
|
endMonth={new Date(2050, 12)}
|
|
|
|
|
locale={it}
|
|
|
|
|
mode="single"
|
|
|
|
|
onSelect={field.onChange}
|
|
|
|
|
selected={field.value || undefined}
|
|
|
|
|
/>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
</FormItem>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="isActive"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem className="w-full">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-x-2">
|
2026-03-08 01:02:57 +01:00
|
|
|
<FormLabel htmlFor="isActive">Pagato</FormLabel>
|
2026-02-24 10:40:32 +01:00
|
|
|
<FormControl>
|
|
|
|
|
<Switch
|
|
|
|
|
className="data-[state=checked]:bg-neutral-700"
|
|
|
|
|
defaultChecked={field.value}
|
|
|
|
|
id="isActive"
|
|
|
|
|
onCheckedChange={field.onChange}
|
|
|
|
|
/>
|
|
|
|
|
</FormControl>
|
|
|
|
|
<FormMessage />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<FormDescription>
|
2026-03-08 01:02:57 +01:00
|
|
|
Imposta se il servizio è pagato
|
2026-02-24 10:40:32 +01:00
|
|
|
</FormDescription>
|
|
|
|
|
</FormItem>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
2026-03-08 01:02:57 +01:00
|
|
|
<FormField
|
|
|
|
|
control={form.control}
|
|
|
|
|
name="sconto"
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
<FormItem>
|
|
|
|
|
<div className="flex flex-wrap items-center gap-x-2">
|
|
|
|
|
<FormLabel htmlFor="sconto">Sconto</FormLabel>
|
|
|
|
|
<FormControl>
|
|
|
|
|
<Select
|
|
|
|
|
defaultValue={String(field.value)}
|
|
|
|
|
name="sconto"
|
|
|
|
|
onValueChange={(value) => {
|
|
|
|
|
field.onChange(Number.parseInt(value));
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger className="w-45">
|
|
|
|
|
<SelectValue placeholder="Sconto" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{[
|
|
|
|
|
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60,
|
|
|
|
|
65, 70, 75, 80, 85, 90, 95, 100,
|
|
|
|
|
].map((v) => {
|
|
|
|
|
return (
|
|
|
|
|
<SelectItem key={v} value={String(v)}>
|
|
|
|
|
{v}%
|
|
|
|
|
</SelectItem>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</FormControl>
|
|
|
|
|
<FormMessage />
|
|
|
|
|
</div>
|
|
|
|
|
</FormItem>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{scontoWch && scontoWch[0] !== 0 ? (
|
|
|
|
|
<span>
|
|
|
|
|
Prezzo scontato:{" "}
|
|
|
|
|
{formatCurrency(
|
|
|
|
|
((1 - scontoWch[0] / 100) * scontoWch[1]) / 100,
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
) : (
|
|
|
|
|
<span>Prezzo: {formatCurrency(scontoWch[1] / 100)}</span>
|
|
|
|
|
)}
|
2026-02-24 10:40:32 +01:00
|
|
|
</div>
|
|
|
|
|
</CredenzaBody>
|
|
|
|
|
|
|
|
|
|
<CredenzaFooter>
|
|
|
|
|
<Button type="submit" variant="success">
|
|
|
|
|
Salva
|
|
|
|
|
</Button>
|
|
|
|
|
<CredenzaClose asChild>
|
|
|
|
|
<Button aria-label="Close Credenza" variant="outline">
|
|
|
|
|
Chiudi
|
|
|
|
|
</Button>
|
|
|
|
|
</CredenzaClose>
|
|
|
|
|
</CredenzaFooter>
|
|
|
|
|
</form>
|
|
|
|
|
</Form>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|