Refactor Prezziario schema and related components

- Updated the Prezziario schema to replace boolean flags (isAcconto, isSaldo, isConsulenza, isStabile, isTransitorio) with new fields (order_type, service_type, service_variant) to better represent service attributes.
- Modified NewPrezzoModal to align with the updated schema.
- Renamed FormNewServizioAcquisto to FormServizioAcquisto in user onboarding pages for consistency.
- Adjusted imports and component usage in the user view and service onboarding pages to reflect the new form name.
This commit is contained in:
Marco Pedone 2026-03-12 16:26:01 +01:00
parent 6c53ef130c
commit 985a302a05
11 changed files with 174 additions and 201 deletions

View file

@ -45,7 +45,6 @@ type DataTableProps<TData, TValue> = {
highlightedRow?: string;
defaultColumnVisibility?: VisibilityState;
tabClassName?: string;
pagination?: boolean;
onStateChange?: () => void;
defaultFilterState?: ColumnFiltersState;
onTableInit?: (table: TableType<TData>) => void;
@ -79,7 +78,6 @@ export function DataTable<TData, TValue>({
highlightedRow,
defaultColumnVisibility,
tabClassName,
pagination = true,
onStateChange,
defaultFilterState,
onTableInit,
@ -88,6 +86,10 @@ export function DataTable<TData, TValue>({
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>(defaultSort || []);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [pagination, setPagination] = useState({
pageIndex: 0, //initial page index
pageSize: 20, //default page size
});
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
defaultFilterState || [],
@ -114,12 +116,14 @@ export function DataTable<TData, TValue>({
onGlobalFilterChange: setGlobalFilter,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onPaginationChange: setPagination,
state: {
columnFilters,
columnVisibility,
globalFilter,
rowSelection,
sorting,
pagination,
},
});
@ -235,9 +239,8 @@ export function DataTable<TData, TValue>({
</TableBody>
</Table>
</div>
{pagination ? (
<DataTablePagination hasSelectRow={hasSelectRow} table={table} />
) : null}
</div>
);
}

View file

@ -587,7 +587,8 @@ const TransitorioSection = () => {
};
const preferenzeDescrizione = data.desc_it?.match(/Permanenza:.*$/m);
const permanenzaAnnuncio = data.permanenza
const permanenzaAnnuncio =
data.permanenza !== null
? `${data.permanenza.join(" - ")} mesi`
: (preferenzeDescrizione?.[0] ??
"errore: testo non trovato in descrizione");
@ -601,7 +602,7 @@ const TransitorioSection = () => {
<MultiSelect
defaultValue={
servizio.permanenza
servizio.permanenza !== null
? {
label: t.parametri.permanenza[servizio.permanenza] || "",
value: servizio.permanenza.toString(),

View file

@ -1,6 +1,6 @@
import { useState } from "react";
import toast from "react-hot-toast";
import { FormNewServizio, type FormValues } from "~/forms/FormNewServizio";
import { FormServizio, type FormValues } from "~/forms/FormServizio";
import type { Servizio } from "~/schemas/public/Servizio";
import type { UsersId } from "~/schemas/public/Users";
import { api } from "~/utils/api";
@ -103,7 +103,7 @@ const NewServizioModal = ({
<DialogDescription className="sr-only">desc</DialogDescription>
</DialogHeader>
<div className="overflow-y-auto">
<FormNewServizio
<FormServizio
initialData={editData}
isLimited={false}
onSubmit={onSubmit}

View file

@ -9,7 +9,9 @@ import type {
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { cn, formatCurrency } from "~/lib/utils";
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
import type { Prezziario } from "~/schemas/public/Prezziario";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
export const PrezzarioTable = ({
data,
@ -20,36 +22,17 @@ export const PrezzarioTable = ({
setOpenEdit: (status: boolean) => void;
setEditData: (data: Prezziario | null) => void;
}) => {
const tabledata = data.map((row) => {
return {
actions: "Azioni",
categoria: [
row.isAcconto && "acconto",
row.isSaldo && "saldo",
row.isConsulenza && "consulenza",
].filter(Boolean),
desc_en: row.desc_en,
desc_it: row.desc_it,
id: row.idprezziario,
isAcconto: row.isAcconto,
isActive: row.isActive,
isConsulenza: row.isConsulenza,
isSaldo: row.isSaldo,
isStabile: row.isStabile,
isTransitorio: row.isTransitorio,
nome_en: row.nome_en,
nome_it: row.nome_it,
prezzo_cent: row.prezzo_cent,
sconto: row.sconto,
testo_condizioni: row.testo_condizioni,
};
});
const tabledata = data;
const cat_options = [
{ label: "acconto", value: "acconto" },
{ label: "saldo", value: "saldo" },
{ label: "consulenza", value: "consulenza" },
];
const tipologia_options = Object.values(OrderTypeEnum).map((value) => ({
label: value,
value: value,
}));
const cat_options = Object.values(TipologiaPosizioneEnum).map((value) => ({
label: value,
value: value,
}));
const columns_titles = {
actions: "Azioni",
@ -61,11 +44,10 @@ export const PrezzarioTable = ({
tipologia: "Tipo",
};
type Column = (typeof tabledata)[number];
const includesAll = (arr: string[], values: string[]) =>
values.every((v) => arr.includes(v));
const columns: ColumnDef<Column>[] = [
{
accessorKey: "id",
accessorKey: "idprezziario",
enableHiding: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.id} />
@ -85,10 +67,8 @@ export const PrezzarioTable = ({
),
},
{
accessorKey: "categoria",
filterFn: (row, id, value) => {
return includesAll(row.getValue(id), value as string[]);
},
accessorKey: "service_type",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
@ -98,23 +78,8 @@ export const PrezzarioTable = ({
},
{
accessorKey: "tipologia",
cell: ({ row }) => {
return (
<div>
{(() => {
if (row.original.isTransitorio && row.original.isStabile)
return "Transitorio/Stabile";
if (row.original.isTransitorio) return "Transitorio";
if (row.original.isStabile) return "Stabile";
return "";
})()}
</div>
);
},
filterFn: (row, id, value) => {
return includesAll(row.getValue(id), value as string[]);
},
accessorKey: "order_type",
header: ({ column }) => (
<DataTableColumnHeader
column={column}
@ -146,31 +111,16 @@ export const PrezzarioTable = ({
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.sconto} />
),
enableSorting: false,
},
{
accessorKey: "actions",
cell: ({ row }) => {
const rowData = row.original;
return (
<Button
aria-label="Modifica Prezziario"
onClick={() => {
setEditData({
desc_en: rowData.desc_en,
desc_it: rowData.desc_it,
idprezziario: rowData.id,
isAcconto: rowData.isAcconto,
isActive: rowData.isActive,
isConsulenza: rowData.isConsulenza,
isSaldo: rowData.isSaldo,
isStabile: rowData.isStabile,
isTransitorio: rowData.isTransitorio,
nome_en: rowData.nome_en,
nome_it: rowData.nome_it,
prezzo_cent: rowData.prezzo_cent,
sconto: rowData.sconto,
testo_condizioni: rowData.testo_condizioni,
});
setEditData(row.original);
setOpenEdit(true);
}}
variant="ghost"
@ -188,13 +138,18 @@ export const PrezzarioTable = ({
];
const pinnedFiltri: PinnedFiltro[] = [
{
columnName: "categoria",
columnName: "service_type",
options: cat_options,
title: "Categoria",
},
{
columnName: "order_type",
options: tipologia_options,
title: "Tipologia",
},
];
const searchFiltro: SearchFiltro = {
columnName: "id",
columnName: "idprezziario",
placeholder: "Filtra codice...",
};
return (
@ -203,7 +158,7 @@ export const PrezzarioTable = ({
columns={columns}
columns_titles={columns_titles}
data={tabledata}
defaultSort={[{ desc: false, id: "id" }]}
defaultSort={[{ desc: false, id: "idprezziario" }]}
pinnedFiltri={pinnedFiltri}
searchColumn={searchFiltro}
/>

View file

@ -20,11 +20,14 @@ import {
SelectValue,
} from "~/components/ui/select";
import { Switch } from "~/components/ui/switch";
import { FieldResetButton } from "~/forms/comps";
import { useZodForm } from "~/lib/zodForm";
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
import type {
Prezziario,
PrezziarioIdprezziario,
} from "~/schemas/public/Prezziario";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { api } from "~/utils/api";
type FormPrezzoProps = {
@ -38,28 +41,24 @@ export const FormPrezzo = ({
const { data: stringhe_condizioni } =
api.strings.getStringheCondizioni.useQuery();
const stringhe_condizioni_options = [
"",
...(stringhe_condizioni
const stringhe_condizioni_options = stringhe_condizioni
? stringhe_condizioni.map((v) => String(v.stinga_id))
: []),
];
: [];
const PrezzoSchema = z
.object({
desc_en: z.string(),
desc_it: z.string(),
idprezziario: z.custom<PrezziarioIdprezziario>(),
isAcconto: z.boolean(),
isActive: z.boolean(),
isConsulenza: z.boolean(),
isSaldo: z.boolean(),
isStabile: z.boolean(),
isTransitorio: z.boolean(),
nome_en: z.string(),
nome_it: z.string(),
prezzo_cent: z.number().min(0),
sconto: z.number().min(0).max(100),
testo_condizioni: z.string().nullable(),
order_type: z.enum(OrderTypeEnum).nullable(),
service_type: z.enum(TipologiaPosizioneEnum).nullable(),
service_variant: z.number().nullable(),
})
.superRefine(({ testo_condizioni }, ctx) => {
if (testo_condizioni) {
@ -84,17 +83,15 @@ export const FormPrezzo = ({
desc_en: "",
desc_it: "",
idprezziario: "" as PrezziarioIdprezziario,
isAcconto: false,
isActive: false,
isConsulenza: false,
isSaldo: false,
isStabile: false,
isTransitorio: false,
nome_en: "",
nome_it: "",
prezzo_cent: 0,
sconto: 0,
testo_condizioni: "",
order_type: null,
service_type: null,
service_variant: null,
};
z.config(z.locales.it());
@ -228,6 +225,13 @@ export const FormPrezzo = ({
>
ID Testo Condizioni
</FormLabel>
{field.value !== null && (
<FieldResetButton
onClick={() =>
form.setValue("testo_condizioni", null)
}
/>
)}
<FormMessage />
</div>
@ -243,10 +247,9 @@ export const FormPrezzo = ({
elementId="select-testo-condizioni"
elementName="testo_condizioni"
isMulti={false}
key={`${field.value}-select-testo`}
onValueChange={async (value) => {
if (value.value === "") {
field.onChange(undefined);
} else {
if (value.value !== "") {
field.onChange(
stringhe_condizioni_options[
Number.parseInt(value.value)
@ -268,101 +271,116 @@ export const FormPrezzo = ({
<div className="flex w-full flex-col gap-4 md:w-1/3">
<FormField
control={form.control}
name="isAcconto"
name="order_type"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="isAcconto">Acconto</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="isAcconto"
onCheckedChange={field.onChange}
<FormLabel htmlFor="select-order_type">
Tipologia
</FormLabel>
{field.value !== null && (
<FieldResetButton
onClick={() => form.setValue("order_type", null)}
/>
</FormControl>
)}
<FormMessage />
</div>
<FormControl>
<Select
defaultValue={field.value || ""}
key={`${field.value}-select-order_type`}
onValueChange={field.onChange}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Seleziona tipologia" />
</SelectTrigger>
</FormControl>
<SelectContent id="select-order_type">
{Object.values(OrderTypeEnum).map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isSaldo"
name="service_type"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="isSaldo">Saldo</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="isSaldo"
onCheckedChange={field.onChange}
<FormLabel htmlFor="select-service_type">
Categoria
</FormLabel>
{field.value !== null && (
<FieldResetButton
onClick={() => form.setValue("service_type", null)}
/>
</FormControl>
)}
<FormMessage />
</div>
<FormControl>
<Select
defaultValue={field.value || ""}
key={`${field.value}-select-service_type`}
onValueChange={field.onChange}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Seleziona categoria" />
</SelectTrigger>
</FormControl>
<SelectContent id="select-service_type">
{Object.values(TipologiaPosizioneEnum).map(
(option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
),
)}
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isConsulenza"
name="service_variant"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="isConsulenza">Consulenza</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="isConsulenza"
onCheckedChange={field.onChange}
<FormLabel htmlFor="service_variant">
Variante Categoria
</FormLabel>
{field.value !== null && (
<FieldResetButton
onClick={() => form.setValue("service_variant", null)}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isTransitorio"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="isTransitorio">Transitorio</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="isTransitorio"
onCheckedChange={field.onChange}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isStabile"
render={({ field }) => (
<FormItem>
<div className="flex flex-wrap items-center gap-x-2">
<FormLabel htmlFor="isStabile">Stabile</FormLabel>
<FormControl>
<Switch
className="data-[state=checked]:bg-neutral-700"
defaultChecked={field.value}
id="isStabile"
onCheckedChange={field.onChange}
<Input
placeholder=""
{...field}
id="service_variant"
onChange={(e) => {
const v = e.target.value.trim();
if (v !== null && v !== "") {
field.onChange(parseInt(v, 10));
} else {
field.onChange(null);
}
}}
value={field.value !== null ? String(field.value) : ""}
/>
</FormControl>
<FormMessage />
</div>
</FormItem>
)}
/>

View file

@ -68,7 +68,7 @@ const Schema = z
export type FormValues = z.infer<typeof Schema>;
export const FormNewServizio = ({
export const FormServizio = ({
initialData,
onSubmit,
isLimited,
@ -290,7 +290,7 @@ export const FormNewServizio = ({
<FormControl>
<MultiSelect
defaultValue={
field.value
field.value !== null
? {
label: t.parametri.permanenza[field.value] || "",
value: field.value.toString(),

View file

@ -74,7 +74,7 @@ type InitialData = {
anagrafica: UsersAnagrafica | undefined;
};
const FormNewServizioSchema = z.object({
const schema = z.object({
animali: z.boolean(),
arredato: z.boolean(),
ascensore: z.boolean(),
@ -109,9 +109,9 @@ const FormNewServizioSchema = z.object({
scadenza_motivazione_transitoria: z.date().nullable(),
skipPayment: z.boolean(),
});
type FormNewServizioValues = z.infer<typeof FormNewServizioSchema>;
type FormServizioAcquistoValues = z.infer<typeof schema>;
export const FormNewServizioAcquisto = ({
export const FormServizioAcquisto = ({
initialData,
userId,
isAdmin,
@ -122,8 +122,8 @@ export const FormNewServizioAcquisto = ({
}) => {
const router = useRouter();
const { comuni, nazioni } = useCatasto();
console.log(isAdmin);
const schema = FormNewServizioSchema.superRefine(
const form_schema = schema.superRefine(
(
{
telefono,
@ -270,7 +270,7 @@ export const FormNewServizioAcquisto = ({
);
// This can come from your database or API.
const defaultValues: FormNewServizioValues = {
const defaultValues: FormServizioAcquistoValues = {
...initialData.userData,
cap_recapiti: initialData.anagrafica?.cap_residenza || "",
citta_recapiti: initialData.anagrafica?.comune_residenza || null,
@ -291,7 +291,7 @@ export const FormNewServizioAcquisto = ({
const { locale, t } = useTranslation();
z.config(z.locales[locale]());
const form = useZodForm(schema, {
const form = useZodForm(form_schema, {
defaultValues: defaultValues,
});
const DatePickerLocale = locale === "it" ? it : enUS;
@ -323,7 +323,7 @@ export const FormNewServizioAcquisto = ({
},
});
function onSubmit(fields: FormNewServizioValues) {
function onSubmit(fields: FormServizioAcquistoValues) {
const a: UsersAnagraficaUpdate = {
cap_residenza: fields.cap_recapiti,
civico_residenza: fields.civico_recapiti,
@ -572,7 +572,7 @@ export const FormNewServizioAcquisto = ({
<FormControl>
<MultiSelect
defaultValue={
field.value
field.value != null
? {
label:
t.parametri.permanenza[field.value] || "",
@ -1255,10 +1255,10 @@ const ResidenzaSection = ({
trigger,
setValue,
}: {
control: Control<FormNewServizioValues>;
trigger: (name?: keyof FormNewServizioValues) => Promise<boolean>;
control: Control<FormServizioAcquistoValues>;
trigger: (name?: keyof FormServizioAcquistoValues) => Promise<boolean>;
// biome-ignore lint/suspicious/noExplicitAny: <fine here>
setValue: (field: keyof FormNewServizioValues, value: any) => void;
setValue: (field: keyof FormServizioAcquistoValues, value: any) => void;
}) => {
const { t } = useTranslation();
const { comuni, nazioni, provincie } = useCatasto();

View file

@ -130,12 +130,10 @@ const NewPrezzoModal = () => {
desc_en: "translations to be inserted",
desc_it: "traduzioni da inserire",
idprezziario: "" as PrezziarioIdprezziario,
isAcconto: false,
order_type: null,
service_type: null,
service_variant: null,
isActive: true,
isConsulenza: false,
isSaldo: false,
isStabile: false,
isTransitorio: false,
nome_en: "New service",
nome_it: "Nuovo servizio",
prezzo_cent: 0,

View file

@ -2,7 +2,7 @@ import type { GetServerSideProps } from "next";
import { AreaRiservataLayoutUserView } from "~/components/Layout";
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { FormNewServizioAcquisto } from "~/forms/FormNewServizioAcquisto";
import { FormServizioAcquisto } from "~/forms/FormServizioAcquisto";
import type { NextPageWithLayout } from "~/pages/_app";
import { CatastoProvider } from "~/providers/CatastoProvider";
import type { ServizioServizioId } from "~/schemas/public/Servizio";
@ -40,7 +40,7 @@ const OnboardUser: NextPageWithLayout<OnboardUserProps> = ({
</h3>
</div>
<CatastoProvider>
<FormNewServizioAcquisto
<FormServizioAcquisto
initialData={data}
isAdmin={true}
userId={data.userData.id}

View file

@ -3,7 +3,7 @@ import { AreaRiservataLayout } from "~/components/Layout";
import { LoadingPage } from "~/components/loading";
import { OnboardTutorial } from "~/components/onboard_tutorial";
import { Status500 } from "~/components/status-page";
import { FormNewServizioAcquisto } from "~/forms/FormNewServizioAcquisto";
import { FormServizioAcquisto } from "~/forms/FormServizioAcquisto";
import type { NextPageWithLayout } from "~/pages/_app";
import { CatastoProvider } from "~/providers/CatastoProvider";
import { useTranslation } from "~/providers/I18nProvider";
@ -44,7 +44,7 @@ const OnboardServizio: NextPageWithLayout<OnboardServizioProps> = ({
</div>
<OnboardTutorial />
<CatastoProvider>
<FormNewServizioAcquisto
<FormServizioAcquisto
initialData={data}
isAdmin={false}
userId={data.userData.id}

View file

@ -1,6 +1,8 @@
// @generated
// This file is automatically generated by Kanel. Do not modify manually.
import type { default as OrderTypeEnum } from './OrderTypeEnum';
import type { default as TipologiaPosizioneEnum } from './TipologiaPosizioneEnum';
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
/** Identifier type for public.prezziario */
@ -26,15 +28,11 @@ export default interface PrezziarioTable {
sconto: ColumnType<number, number | undefined, number>;
isAcconto: ColumnType<boolean, boolean | undefined, boolean>;
order_type: ColumnType<OrderTypeEnum | null, OrderTypeEnum | null, OrderTypeEnum | null>;
isSaldo: ColumnType<boolean, boolean | undefined, boolean>;
service_type: ColumnType<TipologiaPosizioneEnum | null, TipologiaPosizioneEnum | null, TipologiaPosizioneEnum | null>;
isConsulenza: ColumnType<boolean, boolean | undefined, boolean>;
isStabile: ColumnType<boolean, boolean | undefined, boolean>;
isTransitorio: ColumnType<boolean, boolean | undefined, boolean>;
service_variant: ColumnType<number | null, number | null, number | null>;
}
export type Prezziario = Selectable<PrezziarioTable>;