Refactor various components for improved UI consistency and functionality; update layout styles, enhance user interactions, and introduce reset button functionality in forms.
This commit is contained in:
parent
1baca2997a
commit
c87a7238a7
14 changed files with 150 additions and 275 deletions
|
|
@ -190,7 +190,7 @@ export const AreaRiservataLayoutUserView = ({
|
||||||
isAdmin
|
isAdmin
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mx-4 my-3 flex-1 overflow-auto">
|
<div className="mx-2 my-3 flex-1 overflow-auto lg:mx-4">
|
||||||
<UserViewHeader />
|
<UserViewHeader />
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ export const MultiSelect = ({
|
||||||
control: () =>
|
control: () =>
|
||||||
cn(
|
cn(
|
||||||
"mt-2 w-full rounded-lg h-min-[42px] bg-card shadow-xs outline-hidden text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
"mt-2 w-full rounded-lg h-min-[42px] bg-card shadow-xs outline-hidden text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
disabled && "bg-muted opacity-50 cursor-not-allowed",
|
||||||
),
|
),
|
||||||
input: () => cn("*:ring-0 focus:*:ring-0"),
|
input: () => cn("*:ring-0 focus:*:ring-0"),
|
||||||
menu: () =>
|
menu: () =>
|
||||||
|
|
|
||||||
|
|
@ -169,21 +169,6 @@ const DocSection = () => {
|
||||||
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
const { data, updateServizioAnnunci } = useServizioAnnuncio();
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
// const { mutate: updateServizio } = api.servizio.UpdateConfermaDoc.useMutation(
|
|
||||||
// {
|
|
||||||
// onError: (error) => {
|
|
||||||
// toast.error(error.message);
|
|
||||||
// },
|
|
||||||
// onSuccess: async () => {
|
|
||||||
// await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
|
||||||
// await utils.servizio.getServizio.invalidate({
|
|
||||||
// servizioId,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// toast.success("Dati salvati con successo");
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
const { data: files, isLoading } = api.storage.getAll.useQuery();
|
||||||
|
|
||||||
|
|
@ -622,6 +607,7 @@ const StabileSection = () => {
|
||||||
1000,
|
1000,
|
||||||
[value],
|
[value],
|
||||||
);
|
);
|
||||||
|
//TODO selettore per pack consulenza
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 rounded-md border border-border bg-muted p-2 sm:flex-row">
|
<div className="flex flex-col gap-2 rounded-md border border-border bg-muted p-2 sm:flex-row">
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export const AdminContratto = () => {
|
||||||
aria-label="Open Contratto Section"
|
aria-label="Open Contratto Section"
|
||||||
className="w-fit"
|
className="w-fit"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant={data.doc_contratto_added ? "success" : "secondary"}
|
variant={data.doc_contratto_added ? "success" : "orange"}
|
||||||
>
|
>
|
||||||
{data.doc_contratto_added ? <CircleCheck /> : <Wrench />}
|
{data.doc_contratto_added ? <CircleCheck /> : <Wrench />}
|
||||||
Contratto
|
Contratto
|
||||||
|
|
|
||||||
|
|
@ -134,12 +134,11 @@ export const UserActions = () => {
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full flex-col flex-wrap gap-2 sm:flex-row">
|
<div className="flex w-full flex-col flex-wrap gap-2 sm:flex-row">
|
||||||
<ContattiProprietarioModal />
|
<ContattiProprietarioModal />
|
||||||
|
{ConfermaUtente
|
||||||
|
? !data.hasConfermaAdmin && <ConfermaInLavorazioneModal />
|
||||||
|
: ContattiSbloccati && <ConfermaAnnuncioModal />}
|
||||||
|
|
||||||
{ContattiSbloccati && !ConfermaUtente && <ConfermaAnnuncioModal />}
|
{AccettatoConferma ? (
|
||||||
{ConfermaUtente && !data.hasConfermaAdmin && (
|
|
||||||
<ConfermaInLavorazioneModal />
|
|
||||||
)}
|
|
||||||
{AccettatoConferma && (
|
|
||||||
<Link
|
<Link
|
||||||
aria-label="Conferma immobile"
|
aria-label="Conferma immobile"
|
||||||
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
|
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
|
||||||
|
|
@ -149,8 +148,8 @@ export const UserActions = () => {
|
||||||
Rivedi la conferma
|
Rivedi la conferma
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
) : (
|
||||||
{ConfermaLavorata &&
|
ConfermaLavorata &&
|
||||||
(data.doc_conferma_ref ? (
|
(data.doc_conferma_ref ? (
|
||||||
<Link
|
<Link
|
||||||
aria-label="Conferma immobile"
|
aria-label="Conferma immobile"
|
||||||
|
|
@ -181,7 +180,8 @@ export const UserActions = () => {
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.accettato_conferma_at != null && <PostConfermaSection />}
|
{data.accettato_conferma_at != null && <PostConfermaSection />}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Toolbox } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import {
|
import {
|
||||||
AdminActions,
|
AdminActions,
|
||||||
|
|
@ -10,22 +11,21 @@ import { cn, formatCurrency } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from "../ui/collapsible";
|
||||||
import { AnnuncioDettaglio, TipoBadge } from "./annuncio_dettaglio";
|
import { AnnuncioDettaglio, TipoBadge } from "./annuncio_dettaglio";
|
||||||
|
|
||||||
export const AnnuncioCard = ({
|
export const AnnuncioCard = ({ className }: { className?: string }) => {
|
||||||
className,
|
|
||||||
confirming,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
confirming?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { isAdmin } = useServizio();
|
const { isAdmin } = useServizio();
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicAnnuncioCard
|
<BasicAnnuncioCard
|
||||||
className={className}
|
className={className}
|
||||||
confirming={confirming}
|
|
||||||
data={{
|
data={{
|
||||||
...data,
|
...data,
|
||||||
modificato_il: data.modificato_il ? new Date(data.modificato_il) : null,
|
modificato_il: data.modificato_il ? new Date(data.modificato_il) : null,
|
||||||
|
|
@ -33,18 +33,63 @@ export const AnnuncioCard = ({
|
||||||
? new Date(data.media_updated_at)
|
? new Date(data.media_updated_at)
|
||||||
: null,
|
: null,
|
||||||
}}
|
}}
|
||||||
interactions={isAdmin ? <AdminActions /> : <UserActions />}
|
interactions={
|
||||||
|
isAdmin ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Collapsible className="space-y-2">
|
||||||
|
<CollapsibleTrigger>
|
||||||
|
<Button variant="outline">
|
||||||
|
<Toolbox />
|
||||||
|
<span>Azioni Utente</span>
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="rounded-md border border-border bg-muted p-2">
|
||||||
|
<UserActions />
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<AdminActions />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<UserActions />
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BasicAnnuncioCard = ({
|
||||||
|
interactions,
|
||||||
|
className,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
interactions?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
data: AnnuncioRicerca;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Card className={cn("w-full gap-2 border-white py-3", className)}>
|
||||||
|
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-2">
|
||||||
|
<CardTitle className="text-2xl">Annuncio {data.codice}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="px-3">
|
||||||
|
<div
|
||||||
|
className={cn("@container flex w-full flex-col gap-4")}
|
||||||
|
id="container"
|
||||||
|
>
|
||||||
|
<AnnuncioDisplay data={data} />
|
||||||
|
<div className="flex w-full flex-col gap-4">{interactions}</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const AnnuncioDisplay = ({
|
export const AnnuncioDisplay = ({
|
||||||
data,
|
data,
|
||||||
confirming,
|
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
data: AnnuncioRicerca;
|
data: AnnuncioRicerca;
|
||||||
confirming?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -53,7 +98,6 @@ export const AnnuncioDisplay = ({
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid @md:grid-cols-[12rem_1fr] grid-cols-[7rem_1fr] gap-x-4 gap-y-2 rounded-md md:max-w-3xl md:border md:border-white md:bg-[#e6e9ec]/50 lg:gap-x-4 xl:gap-x-6 dark:md:border-primary dark:md:bg-card",
|
"grid @md:grid-cols-[12rem_1fr] grid-cols-[7rem_1fr] gap-x-4 gap-y-2 rounded-md md:max-w-3xl md:border md:border-white md:bg-[#e6e9ec]/50 lg:gap-x-4 xl:gap-x-6 dark:md:border-primary dark:md:bg-card",
|
||||||
confirming && "xl:w-1/2",
|
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -103,42 +147,3 @@ export const AnnuncioDisplay = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BasicAnnuncioCard = ({
|
|
||||||
interactions,
|
|
||||||
className,
|
|
||||||
data,
|
|
||||||
confirming,
|
|
||||||
}: {
|
|
||||||
interactions?: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
data: AnnuncioRicerca;
|
|
||||||
confirming?: boolean;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Card className={cn("w-full gap-2 border-white py-3", className)}>
|
|
||||||
<CardHeader className="flex flex-row flex-wrap items-center justify-between space-y-0 px-3 pb-2">
|
|
||||||
<CardTitle className="text-2xl">Annuncio {data.codice}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="px-3">
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"@container flex w-full flex-col gap-4",
|
|
||||||
confirming && "xl:flex-row xl:gap-6",
|
|
||||||
)}
|
|
||||||
id="container"
|
|
||||||
>
|
|
||||||
<AnnuncioDisplay confirming={confirming} data={data} />
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex w-full flex-col gap-4",
|
|
||||||
confirming && "xl:w-1/2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{interactions}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -285,10 +285,7 @@ export const ServizioContent = () => {
|
||||||
servizioId={servizio.servizio_id}
|
servizioId={servizio.servizio_id}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
>
|
>
|
||||||
<AnnuncioCard
|
<AnnuncioCard className="rounded-md border-primary" />
|
||||||
className="rounded-md border-primary"
|
|
||||||
confirming
|
|
||||||
/>
|
|
||||||
</ServizioAnnuncioProvider>
|
</ServizioAnnuncioProvider>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ import {
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
ClockAlert,
|
ClockAlert,
|
||||||
Cog,
|
Cog,
|
||||||
|
ExternalLink,
|
||||||
Info,
|
Info,
|
||||||
Plus,
|
Plus,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
Trash2,
|
Trash2,
|
||||||
UserCircle,
|
UserCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
|
|
@ -60,6 +62,7 @@ import { cn, formatCurrency } from "~/lib/utils";
|
||||||
import { useServizio } from "~/providers/ServizioProvider";
|
import { useServizio } from "~/providers/ServizioProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
|
|
@ -131,9 +134,9 @@ const ServizioInfos = () => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="mb-4 flex w-full flex-col gap-8 rounded-md border bg-card p-2 sm:items-start sm:gap-4">
|
<CollapsibleContent className="mb-4 flex w-full flex-col gap-4 rounded-md border bg-card p-2 sm:items-start sm:gap-6">
|
||||||
<ServizioBasicActions />
|
<ServizioBasicActions />
|
||||||
<div className="flex w-full flex-col gap-8 sm:flex-row sm:items-start">
|
<div className="flex w-full flex-col gap-4 sm:flex-row sm:items-start sm:gap-6">
|
||||||
<div className="flex flex-2 flex-col gap-2 sm:w-1/2">
|
<div className="flex flex-2 flex-col gap-2 sm:w-1/2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
@ -173,7 +176,7 @@ const ServizioBasicActions = ({ className }: { className?: string }) => {
|
||||||
|
|
||||||
if (isAdmin || canUserEdit) {
|
if (isAdmin || canUserEdit) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-wrap items-center gap-3", className)}>
|
<div className={cn("flex flex-wrap items-center gap-2", className)}>
|
||||||
<EditParametri />
|
<EditParametri />
|
||||||
<InterruzioneServizio />
|
<InterruzioneServizio />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -234,6 +237,27 @@ export const ServizioPacksInfos = () => {
|
||||||
<span className="font-medium">{packs.saldo.message}</span>
|
<span className="font-medium">{packs.saldo.message}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{servizio.tipologia === TipologiaPosizioneEnum.Stabile && (
|
||||||
|
<div className="flex flex-1 flex-col gap-2 rounded-md bg-secondary/50 p-2">
|
||||||
|
<>
|
||||||
|
<span className="font-medium">Consulenza</span>
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Il prezzo della consulenza varia in base alla tipologia
|
||||||
|
contrattuale.{" "}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold">
|
||||||
|
<Link
|
||||||
|
className="flex items-center gap-2 underline underline-offset-2"
|
||||||
|
href={"/prezzi#contratti"}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<span>Vedi prezzi contratti</span>
|
||||||
|
<ExternalLink className="size-4 stroke-foreground" />
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -390,7 +414,7 @@ const InterruzioneServizio = () => {
|
||||||
servizio.annunci.some((a) => a.user_confirmed_at !== null))
|
servizio.annunci.some((a) => a.user_confirmed_at !== null))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ClockAlert /> <span>Richiedi Interruzione</span>
|
<ClockAlert /> <span>Interruzione</span>
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
|
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { type Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
|
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
type ControllerProps,
|
|
||||||
type FieldPath,
|
|
||||||
type FieldValues,
|
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
useFormState,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import { Label } from "~/components/ui/label";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
|
|
||||||
const Form = FormProvider;
|
|
||||||
|
|
||||||
type FormFieldContextValue<
|
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
||||||
> = {
|
|
||||||
name: TName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
||||||
{} as FormFieldContextValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
const FormField = <
|
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
||||||
>({
|
|
||||||
...props
|
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
|
||||||
return (
|
|
||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
||||||
<Controller {...props} />
|
|
||||||
</FormFieldContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useFormField = () => {
|
|
||||||
const fieldContext = React.useContext(FormFieldContext);
|
|
||||||
const itemContext = React.useContext(FormItemContext);
|
|
||||||
const { getFieldState } = useFormContext();
|
|
||||||
const formState = useFormState({ name: fieldContext.name });
|
|
||||||
const fieldState = getFieldState(fieldContext.name, formState);
|
|
||||||
|
|
||||||
if (!fieldContext) {
|
|
||||||
throw new Error("useFormField should be used within <FormField>");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = itemContext;
|
|
||||||
|
|
||||||
return {
|
|
||||||
formDescriptionId: `${id}-form-item-description`,
|
|
||||||
formItemId: `${id}-form-item`,
|
|
||||||
formMessageId: `${id}-form-item-message`,
|
|
||||||
id,
|
|
||||||
name: fieldContext.name,
|
|
||||||
...fieldState,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type FormItemContextValue = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
||||||
{} as FormItemContextValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
const id = React.useId();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItemContext.Provider value={{ id }}>
|
|
||||||
<div
|
|
||||||
className={cn("grid gap-2", className)}
|
|
||||||
data-slot="form-item"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</FormItemContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FormLabel({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
||||||
const { error, formItemId } = useFormField();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Label
|
|
||||||
className={cn("data-[error=true]:text-destructive", className)}
|
|
||||||
data-error={!!error}
|
|
||||||
data-slot="form-label"
|
|
||||||
htmlFor={formItemId}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FormControl({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SlotPrimitive.Slot>) {
|
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
|
||||||
useFormField();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SlotPrimitive.Slot
|
|
||||||
aria-describedby={
|
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
|
||||||
data-slot="form-control"
|
|
||||||
id={formItemId}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
||||||
const { formDescriptionId } = useFormField();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
|
||||||
data-slot="form-description"
|
|
||||||
id={formDescriptionId}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|
||||||
const { error, formMessageId } = useFormField();
|
|
||||||
const body = error ? String(error?.message ?? "") : props.children;
|
|
||||||
|
|
||||||
if (!body) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className={cn("text-destructive text-sm", className)}
|
|
||||||
data-slot="form-message"
|
|
||||||
id={formMessageId}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{body}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
useFormField,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
|
||||||
FormField,
|
|
||||||
};
|
|
||||||
|
|
@ -27,7 +27,7 @@ function Slider({
|
||||||
return (
|
return (
|
||||||
<SliderPrimitive.Root
|
<SliderPrimitive.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full touch-none select-none items-center data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col data-[disabled]:opacity-50",
|
"relative flex w-full touch-none select-none items-center data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col data-disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="slider"
|
data-slot="slider"
|
||||||
|
|
@ -39,7 +39,7 @@ function Slider({
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track
|
<SliderPrimitive.Track
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-1.5",
|
"relative grow cursor-pointer overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-1.5",
|
||||||
)}
|
)}
|
||||||
data-slot="slider-track"
|
data-slot="slider-track"
|
||||||
>
|
>
|
||||||
|
|
@ -54,7 +54,7 @@ function Slider({
|
||||||
{Array.from({ length: _values.length }, (_, index) => (
|
{Array.from({ length: _values.length }, (_, index) => (
|
||||||
<SliderPrimitive.Thumb
|
<SliderPrimitive.Thumb
|
||||||
className={cn(
|
className={cn(
|
||||||
"block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:outline-hidden focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50",
|
"block size-4 shrink-0 cursor-pointer rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:outline-hidden focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50",
|
||||||
thumbClassName,
|
thumbClassName,
|
||||||
)}
|
)}
|
||||||
data-slot="slider-thumb"
|
data-slot="slider-thumb"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { useZodForm } from "~/lib/zodForm";
|
||||||
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";
|
||||||
|
import { FieldResetButton } from "./comps";
|
||||||
|
|
||||||
const Schema = z
|
const Schema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -228,13 +229,9 @@ export const FormNewServizio = ({
|
||||||
{t.parametri.dalmese_label}...
|
{t.parametri.dalmese_label}...
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
{field.value !== null && !initialData && (
|
{field.value !== null && !initialData && (
|
||||||
<button
|
<FieldResetButton
|
||||||
className="text-sm"
|
|
||||||
onClick={() => form.setValue("entromese", null)}
|
onClick={() => form.setValue("entromese", null)}
|
||||||
type="button"
|
/>
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
@ -253,6 +250,7 @@ export const FormNewServizio = ({
|
||||||
elementId="select-mesi"
|
elementId="select-mesi"
|
||||||
elementName="entro_mese"
|
elementName="entro_mese"
|
||||||
isMulti={false}
|
isMulti={false}
|
||||||
|
key={field.value}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(
|
||||||
value.value !== "" ? parseInt(value.value) : null,
|
value.value !== "" ? parseInt(value.value) : null,
|
||||||
|
|
@ -274,17 +272,19 @@ export const FormNewServizio = ({
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<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="permanenza">
|
<FormLabel
|
||||||
|
className={cn(
|
||||||
|
tipologia !== TipologiaPosizioneEnum.Transitorio &&
|
||||||
|
"opacity-50",
|
||||||
|
)}
|
||||||
|
htmlFor="permanenza"
|
||||||
|
>
|
||||||
{t.parametri.permanenza_label}
|
{t.parametri.permanenza_label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
{field.value !== null && !initialData && (
|
{field.value !== null && !initialData && (
|
||||||
<button
|
<FieldResetButton
|
||||||
className="text-sm"
|
|
||||||
onClick={() => form.setValue("permanenza", null)}
|
onClick={() => form.setValue("permanenza", null)}
|
||||||
type="button"
|
/>
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -303,6 +303,7 @@ export const FormNewServizio = ({
|
||||||
elementId="permanenza"
|
elementId="permanenza"
|
||||||
elementName="permanenza"
|
elementName="permanenza"
|
||||||
isMulti={false}
|
isMulti={false}
|
||||||
|
key={field.value}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(
|
||||||
value.value !== "" ? parseInt(value.value) : null,
|
value.value !== "" ? parseInt(value.value) : null,
|
||||||
|
|
@ -314,7 +315,11 @@ export const FormNewServizio = ({
|
||||||
placeholder="Seleziona..."
|
placeholder="Seleziona..."
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>{t.parametri.permanenza_desc}</FormDescription>
|
{tipologia === TipologiaPosizioneEnum.Transitorio && (
|
||||||
|
<FormDescription>
|
||||||
|
{t.parametri.permanenza_desc}
|
||||||
|
</FormDescription>
|
||||||
|
)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -639,7 +639,13 @@ export const FormNewServizioAcquisto = ({
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<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="permanenza">
|
<FormLabel
|
||||||
|
className={cn(
|
||||||
|
tipologia !== TipologiaPosizioneEnum.Transitorio &&
|
||||||
|
"opacity-50",
|
||||||
|
)}
|
||||||
|
htmlFor="permanenza"
|
||||||
|
>
|
||||||
{t.parametri.permanenza_label}
|
{t.parametri.permanenza_label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
@ -673,9 +679,11 @@ export const FormNewServizioAcquisto = ({
|
||||||
placeholder="Seleziona..."
|
placeholder="Seleziona..."
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{tipologia === TipologiaPosizioneEnum.Transitorio && (
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t.parametri.permanenza_desc}
|
{t.parametri.permanenza_desc}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ export const ProfileFormAnagrafica = ({
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-[42px] w-full pl-3 text-left font-medium",
|
"h-10.5 w-full pl-3 text-left font-medium",
|
||||||
!field.value && "text-muted-foreground",
|
!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",
|
"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",
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
18
apps/infoalloggi/src/forms/comps.tsx
Normal file
18
apps/infoalloggi/src/forms/comps.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
|
export const FieldResetButton = (
|
||||||
|
props: React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
) => {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...rest}
|
||||||
|
className={cn("flex items-center gap-2 text-sm leading-none", className)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
<RefreshCcw className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue