Facturas de cliente

This commit is contained in:
David Arranz 2025-10-15 11:40:22 +02:00
parent 3da0d3858f
commit dc49094f00
13 changed files with 317 additions and 191 deletions

View File

@ -1 +1 @@
export * from "./customer-invoice-edit-form"; export * from "./invoice-edit-form";

View File

@ -1,5 +1,6 @@
import { FieldErrors, useFormContext } from "react-hook-form"; import { FieldErrors, useFormContext } from "react-hook-form";
import { FormDebug } from '@erp/core/components';
import { cn } from '@repo/shadcn-ui/lib/utils'; import { cn } from '@repo/shadcn-ui/lib/utils';
import { InvoiceFormData } from "../../schemas"; import { InvoiceFormData } from "../../schemas";
import { InvoiceBasicInfoFields } from "./invoice-basic-info-fields"; import { InvoiceBasicInfoFields } from "./invoice-basic-info-fields";
@ -14,7 +15,7 @@ interface CustomerInvoiceFormProps {
className: string; className: string;
} }
export const CustomerInvoiceEditForm = ({ export const InvoiceEditForm = ({
formId, formId,
onSubmit, onSubmit,
onError, onError,
@ -27,18 +28,18 @@ export const CustomerInvoiceEditForm = ({
<section className={cn("space-y-6", className)}> <section className={cn("space-y-6", className)}>
<div className="w-full border p-6 bg-background"> <div className="w-full border p-6 bg-background">
<InvoiceBasicInfoFields className="flex flex-col" /> <InvoiceBasicInfoFields className="flex flex-col" />
<InvoiceRecipient className='lg:col-span-1 border p-6 bg-background' />
</div> </div>
<div className='w-full grid grid-cols-1 lg:grid-cols-4 gap-6'> <div className='w-full gap-6'>
<InvoiceItems className="border p-6 bg-background -p-6" />
<InvoiceItems className="col-start-1 lg:col-span-3 border p-6 bg-background -p-6" />
<InvoiceRecipient className='lg:col-span-1 border p-6 bg-background' />
</div> </div>
<div className="w-full border p-6 bg-background"> <div className="w-full border p-6 bg-background">
<InvoiceTotals /> <InvoiceTotals />
</div> </div>
<div className="w-full border p-6 bg-background">
<FormDebug />
</div>
</section> </section>
</form> </form>

View File

@ -1,7 +1,7 @@
import { Button, Checkbox, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from "@repo/shadcn-ui/components"; import { Button, Checkbox, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from "@repo/shadcn-ui/components";
import { cn } from '@repo/shadcn-ui/lib/utils'; import { cn } from '@repo/shadcn-ui/lib/utils';
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, Trash2Icon } from "lucide-react"; import { ArrowDownIcon, ArrowUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
import { Control, Controller } from "react-hook-form"; import { Control, Controller, FieldValues } from "react-hook-form";
import { useInvoiceContext } from '../../../context'; import { useInvoiceContext } from '../../../context';
import { useTranslation } from '../../../i18n'; import { useTranslation } from '../../../i18n';
import { CustomerInvoiceTaxesMultiSelect } from '../../customer-invoice-taxes-multi-select'; import { CustomerInvoiceTaxesMultiSelect } from '../../customer-invoice-taxes-multi-select';
@ -10,9 +10,9 @@ import { HoverCardTotalsSummary } from './hover-card-total-summary';
import { PercentageInputField } from './percentage-input-field'; import { PercentageInputField } from './percentage-input-field';
import { QuantityInputField } from './quantity-input-field'; import { QuantityInputField } from './quantity-input-field';
export type ItemRowProps = { export type ItemRowProps<TFieldValues extends FieldValues = FieldValues> = {
control: Control, control: Control<TFieldValues>,
rowIndex: number; rowIndex: number;
isSelected: boolean; isSelected: boolean;
isFirst: boolean; isFirst: boolean;
@ -26,8 +26,7 @@ export type ItemRowProps = {
} }
export const ItemRow = ({ export const ItemRow = <TFieldValues extends FieldValues = FieldValues>({
control, control,
rowIndex, rowIndex,
isSelected, isSelected,
@ -38,7 +37,7 @@ export const ItemRow = ({
onDuplicate, onDuplicate,
onMoveUp, onMoveUp,
onMoveDown, onMoveDown,
onRemove, }: ItemRowProps) => { onRemove, }: ItemRowProps<TFieldValues>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currency_code, language_code } = useInvoiceContext(); const { currency_code, language_code } = useInvoiceContext();

View File

@ -5,7 +5,7 @@ import * as z from "zod";
import { CustomerModalSelector } from "@erp/customers/components"; import { CustomerModalSelector } from "@erp/customers/components";
import { DevTool } from "@hookform/devtools"; import { DevTool } from "@hookform/devtools";
import { DatePickerInputField, TextAreaField, TextField } from "@repo/rdx-ui/components"; import { TextAreaField, TextField } from "@repo/rdx-ui/components";
import { import {
Button, Button,
Calendar, Calendar,

View File

@ -10,7 +10,7 @@ import { useMemo } from 'react';
import { FieldErrors, FormProvider } from "react-hook-form"; import { FieldErrors, FormProvider } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { import {
CustomerInvoiceEditForm, InvoiceEditForm,
PageHeader PageHeader
} from "../../components"; } from "../../components";
import { useInvoiceContext } from '../../context'; import { useInvoiceContext } from '../../context';
@ -104,7 +104,7 @@ export const InvoiceUpdateComp = ({
<AppContent> <AppContent>
<FormProvider {...form}> <FormProvider {...form}>
<CustomerInvoiceEditForm <InvoiceEditForm
formId="invoice-update-form" formId="invoice-update-form"
onSubmit={handleSubmit} onSubmit={handleSubmit}
onError={handleError} onError={handleError}

View File

@ -1,7 +1,6 @@
import { ModuleClientParams } from "@erp/core/client"; import { ModuleClientParams } from "@erp/core/client";
import { lazy } from "react"; import { lazy } from "react";
import { Outlet, RouteObject } from "react-router-dom"; import { Outlet, RouteObject } from "react-router-dom";
import { CustomerUpdatePage } from "./pages/update";
// Lazy load components // Lazy load components
const CustomersLayout = lazy(() => const CustomersLayout = lazy(() =>
@ -22,11 +21,11 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
</CustomersLayout> </CustomersLayout>
), ),
children: [ children: [
{ path: "", index: true, element: <CustomersList /> }, // index /*{ path: "", index: true, element: <CustomersList /> }, // index
{ path: "list", element: <CustomersList /> }, { path: "list", element: <CustomersList /> },
{ path: "create", element: <CustomerAdd /> }, { path: "create", element: <CustomerAdd /> },
{ path: ":id", element: <CustomerView /> }, { path: ":id", element: <CustomerView /> },
{ path: ":id/edit", element: <CustomerUpdatePage /> }, { path: ":id/edit", element: <CustomerUpdatePage /> },*/
// //
/*{ path: "create", element: <CustomersList /> }, /*{ path: "create", element: <CustomersList /> },

View File

@ -48,7 +48,7 @@ export function TextAreaField<TFormValues extends FieldValues>({
name={name} name={name}
render={({ field, fieldState }) => { render={({ field, fieldState }) => {
return ( return (
<Field data-invalid={fieldState.invalid} orientation={orientation} className={className}> <Field data-invalid={fieldState.invalid} orientation={orientation} className={cn("gap-1", className)}>
{label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={name}>{label}</FieldLabel>} {label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={name}>{label}</FieldLabel>}
<Textarea <Textarea

View File

@ -56,7 +56,7 @@ export function TextField<TFormValues extends FieldValues>({
name={name} name={name}
render={({ field, fieldState }) => { render={({ field, fieldState }) => {
return ( return (
<Field data-invalid={fieldState.invalid} orientation={orientation} className={className}> <Field data-invalid={fieldState.invalid} orientation={orientation} className={cn("gap-1", className)}>
{label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={name}>{label}</FieldLabel>} {label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={name}>{label}</FieldLabel>}
<Input <Input

View File

@ -0,0 +1,87 @@
import { Button, Input } from '@repo/shadcn-ui/components';
import { cn } from "@repo/shadcn-ui/lib/utils";
// DateInputField.tsx
import { CalendarIcon, LockIcon, XIcon } from "lucide-react";
export function DateInputField({
id,
value,
onChange,
onBlurConfirm,
onClear,
placeholder,
disabled,
readOnly,
required,
hasError,
describedBy,
onOpenRequest,
triggerButton, // ← PopoverTrigger se inyecta aquí
}: {
id: string;
value: string;
onChange: (val: string) => void;
onBlurConfirm: () => void; // valida+normaliza en blur/Enter
onClear: () => void;
placeholder?: string;
disabled: boolean;
readOnly: boolean;
required: boolean;
hasError: boolean;
describedBy?: string;
onOpenRequest?: () => void;
triggerButton?: React.ReactNode;
}) {
return (
<div className="relative">
<Input
id={id}
type="text"
pattern="\d{2}/\d{2}/\d{4}"
value={value}
onChange={(e) => onChange(e.target.value)}
onBlur={onBlurConfirm}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
onBlurConfirm();
}
if ((e.altKey || e.metaKey) && e.key === "ArrowDown") {
onOpenRequest?.();
}
}}
readOnly={readOnly}
disabled={disabled}
aria-invalid={hasError || undefined}
aria-describedby={describedBy}
className={cn(
"text-ellipsis pr-12",
disabled && "bg-muted text-muted-foreground cursor-not-allowed",
readOnly && "bg-muted text-foreground cursor-default",
!disabled && !readOnly && "bg-white text-foreground",
hasError && "border-destructive ring-destructive"
)}
placeholder={placeholder}
/>
<div className="absolute inset-y-0 right-2 flex items-center gap-2 pr-1">
{!readOnly && !required && value && (
<Button
variant={'link'}
type='button'
size={"icon-sm"}
onClick={onClear}
aria-label="Clear date"
className="text-muted-foreground hover:text-destructive transition cursor-pointer -mr-3"
>
<XIcon className="size-4" />
</Button>
)}
{readOnly ? (
<LockIcon className="size-4" />
) : (
triggerButton ?? <CalendarIcon className="size-4" aria-hidden="true" />
)}
</div>
</div>
);
}

View File

@ -1,30 +1,22 @@
import { import {
Button, Button,
Calendar, Calendar,
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
Field, Field,
FieldDescription, FieldDescription,
FieldError, FieldError,
FieldLabel, FieldLabel,
FormControl,
Input,
Popover, Popover,
PopoverContent,
PopoverTrigger PopoverTrigger
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { CalendarIcon, LockIcon, XIcon } from "lucide-react"; import { CalendarIcon } from "lucide-react";
import { cn } from "@repo/shadcn-ui/lib/utils"; import { cn } from "@repo/shadcn-ui/lib/utils";
import { format, isValid, parse } from "date-fns"; import { format, isValid, parse } from "date-fns";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { ControllerFieldState, ControllerRenderProps, FieldValues, Path, UseFormStateReturn } from "react-hook-form"; import { ControllerFieldState, ControllerRenderProps, FieldValues, Path, UseFormStateReturn } from "react-hook-form";
import { useTranslation } from "../../../locales/i18n.ts"; import { useTranslation } from "../../../locales/i18n.ts";
import { DateInputField } from './date-input-field.tsx';
import { DatePopoverCalendar } from './date-popover-calendar.tsx';
export type SUICalendarProps = Omit<React.ComponentProps< export type SUICalendarProps = Omit<React.ComponentProps<
@ -35,8 +27,6 @@ type DatePickerInputCompProps<TFormValues extends FieldValues = FieldValues> = S
fieldState: ControllerFieldState; fieldState: ControllerFieldState;
formState: UseFormStateReturn<TFormValues>; formState: UseFormStateReturn<TFormValues>;
htmlFor: string,
displayDateFormat: string; // e.g. "dd/MM/yyyy" displayDateFormat: string; // e.g. "dd/MM/yyyy"
parseDateFormat: string; // e.g. "yyyy/MM/dd" parseDateFormat: string; // e.g. "yyyy/MM/dd"
@ -54,13 +44,11 @@ type DatePickerInputCompProps<TFormValues extends FieldValues = FieldValues> = S
className?: string; className?: string;
}; };
export function DatePickerInputComp<TFormValues>({ export function DatePickerInputComp<TFormValues extends FieldValues = FieldValues>({
field, field,
fieldState, fieldState,
formState, formState,
htmlFor,
parseDateFormat, parseDateFormat,
displayDateFormat, displayDateFormat,
@ -78,172 +66,138 @@ export function DatePickerInputComp<TFormValues>({
...calendarProps ...calendarProps
}: DatePickerInputCompProps<TFormValues>) { }: DatePickerInputCompProps<TFormValues>) {
const { t } = useTranslation(); const { t } = useTranslation();
const isDisabled = disabled; const [open, setOpen] = useState(false);
const isReadOnly = readOnly && !disabled; const [displayValue, setDisplayValue] = useState("");
const [localError, setLocalError] = useState<string | null>(null);
const [open, setOpen] = useState(false); // Popover const parsedDate = useMemo(() => {
const [displayValue, setDisplayValue] = useState<string>(""); if (!field.value) return null;
const d = parse(field.value, parseDateFormat, new Date());
return isValid(d) ? d : null;
}, [field.value, parseDateFormat]);
// Sync cuando RHF actualiza el valor externamente
useEffect(() => { useEffect(() => {
if (field.value) { setDisplayValue(parsedDate ? format(parsedDate, displayDateFormat) : "");
// field.value ya viene en formato parseDateFormat }, [parsedDate, displayDateFormat]);
const parsed = parse(field.value, parseDateFormat, new Date());
if (isValid(parsed)) {
setDisplayValue(format(parsed, displayDateFormat));
}
} else {
setDisplayValue("");
}
}, [field.value, parseDateFormat, displayDateFormat]);
const [inputError, setInputError] = useState<string | null>(null); const handleClear = useCallback(() => {
field.onChange("");
setDisplayValue("");
setLocalError(null);
}, [field]);
const handleDisplayValueChange = (value: string) => { const validateAndSet = useCallback(() => {
setDisplayValue(value);
setInputError(null);
};
const handleClearDate = () => {
handleDisplayValueChange("");
}
const validateAndSetDate = () => {
const trimmed = displayValue.trim(); const trimmed = displayValue.trim();
if (!trimmed) { if (!trimmed) {
field.onChange(""); // guardar vacío en el form handleClear();
setInputError(null);
return; return;
} }
const d = parse(trimmed, displayDateFormat, new Date());
const parsed = parse(trimmed, displayDateFormat, new Date()); if (isValid(d)) {
if (isValid(parsed)) { field.onChange(format(d, parseDateFormat));
// Guardar en form como string con parseDateFormat setDisplayValue(format(d, displayDateFormat));
const newDateStr = format(parsed, parseDateFormat); setLocalError(null);
field.onChange(newDateStr);
// Asegurar displayValue consistente
handleDisplayValueChange(newDateStr);
} else { } else {
setInputError(t("components.date_picker_input_field.invalid_date")); setLocalError(t("components.date_picker_input_field.invalid_date"));
} }
}; }, [displayValue, displayDateFormat, parseDateFormat, field, t, handleClear]);
const hasError = Boolean(localError || fieldState.error);
const describedById = description ? `${field.name}-desc` : undefined;
const errorId = hasError ? `${field.name}-err` : undefined;
const popoverId = `${field.name}-popover`;
return ( return (
<Field data-invalid={invalid} orientation={orientation} className={className}> <Field data-invalid={invalid} orientation={orientation} className={cn("gap-1", className)}>
{label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={htmlFor}>{label}</FieldLabel>} <div className="flex justify-between gap-2 overflow-hidden">
<div className="flex items-center gap-2 flex-nowrap">
<FieldLabel htmlFor={field.name} className={cn("m-0 text-xs text-muted-foreground text-nowrap text-ellipsis", disabled && "text-muted-foreground")}>
{label}
</FieldLabel>
{required && <span className="text-xs text-destructive">{t("common.required")}</span>}
</div>
{fieldState.isDirty && <span className="text-[10px] text-primary text-ellipsis">{t("common.modified")}</span>}
</div>
<Popover modal={true} open={open} onOpenChange={setOpen}> <Popover modal={true} open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<FormControl>
<div className='relative'> <DateInputField
<Input id={field.name}
type='text' value={displayValue}
value={displayValue} onChange={(v) => {
onChange={(e) => handleDisplayValueChange(e.target.value)} setDisplayValue(v);
onBlur={() => { if (!open) validateAndSetDate(); }} setLocalError(null);
onKeyDown={(e) => { }}
if (e.key === "Enter") { onBlurConfirm={validateAndSet}
e.preventDefault(); onClear={handleClear}
validateAndSetDate(); placeholder={placeholder}
} disabled={disabled}
}} readOnly={readOnly}
readOnly={isReadOnly} required={required}
disabled={isDisabled} hasError={hasError}
className={cn( describedBy={[describedById, errorId].filter(Boolean).join(" ") || undefined}
"w-full rounded-md border px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring placeholder:font-normal placeholder:italic", onOpenRequest={() => setOpen(true)}
isDisabled && "bg-muted text-muted-foreground cursor-not-allowed", triggerButton={
isReadOnly && "bg-muted text-foreground cursor-default", !readOnly && !disabled && (
!isDisabled && !isReadOnly && "bg-white text-foreground", <PopoverTrigger asChild>
inputError && "border-destructive ring-destructive" <Button
)} variant={'link'}
placeholder={placeholder}
/>
<div className='absolute inset-y-0 right-2 flex items-center gap-2 pr-1'>
{!isReadOnly && !required && displayValue && (
<button
type='button' type='button'
onClick={(e) => { size={"icon-sm"}
e.preventDefault(); aria-label={t("components.date_picker_input_field.open_calendar")}
field.onChange(""); // limpiar valor real en el form aria-haspopup="dialog"
setDisplayValue(""); // limpiar input visible aria-expanded={open}
setInputError(null); // limpiar error aria-controls={popoverId}
}} aria-label={t("common.clear_date")} onClick={() => setOpen((o) => !o)}
className='text-muted-foreground hover:text-foreground focus:outline-none' className="text-muted-foreground transition cursor-pointer -mr-3"
> >
<XIcon className='size-4 hover:text-destructive' /> <CalendarIcon className="size-4" />
</button> </Button>
)} </PopoverTrigger>
{isReadOnly ? ( )
<LockIcon className='size-4 text-muted-foreground' /> }
) : ( />
<CalendarIcon className='size-4 text-muted-foreground hover:text-primary hover:cursor-pointer' />
)}
</div>
</div>
</FormControl>
</PopoverTrigger> </PopoverTrigger>
{!isDisabled && !isReadOnly && ( {!disabled && !readOnly && (
<PopoverContent className='w-auto p-0'> <DatePopoverCalendar
<Card className='border-none shadow-none py-6 gap-3'> contentId={popoverId}
<CardHeader className="border-b px-3 [.border-b]:pb-3"> label={label}
<CardTitle>{label}</CardTitle> description={description}
<CardDescription>{description || "\u00A0"}</CardDescription> parsedDate={parsedDate}
<CardAction> onSelect={(date) => {
<Button if (!date) return;
size="sm" field.onChange(format(date, parseDateFormat));
variant="outline" setDisplayValue(format(date, displayDateFormat));
onClick={(e) => { setOpen(false);
e.preventDefault(); }}
const today = format(new Date(), parseDateFormat); onToday={() => {
field.onChange(today); const today = new Date();
handleDisplayValueChange(today); field.onChange(format(today, parseDateFormat));
setOpen(false); setDisplayValue(format(today, displayDateFormat));
}} setOpen(false);
> }}
{t("components.date_picker_input_field.today")} onClose={() => setOpen(false)}
</Button> {...calendarProps}
</CardAction> />
</CardHeader>
<CardContent className='px-0'>
<Calendar
defaultMonth={field.value ? new Date(field.value) : undefined}
{...calendarProps}
mode='single'
selected={field.value ? new Date(field.value) : undefined}
onSelect={(date) => {
const newDateStr = date ? format(date, parseDateFormat) : "";
field.onChange(newDateStr);
handleDisplayValueChange(newDateStr);
setOpen(false);
}}
initialFocus
/>
</CardContent>
<CardFooter className='mx-auto'>
<Button
size="sm"
variant="outline"
onClick={(e) => {
e.preventDefault();
setOpen(false);
}}
>
{t("components.date_picker_input_field.close")}
</Button>
</CardFooter>
</Card>
</PopoverContent>
)} )}
</Popover> </Popover>
{isReadOnly && ( {false && (
<p className='text-xs text-muted-foreground italic mt-1 flex items-center gap-1'> <div className='mt-1 flex items-start justify-between'>
<LockIcon className='w-3 h-3' /> {t("common.read_only") || "Solo lectura"} <FieldDescription
</p> id={describedById}
className={cn("text-xs truncate", !description && "invisible")}
>
{description || "\u00A0"}
</FieldDescription>
</div>
)} )}
{false && <FieldDescription className='text-xs'>{description || "\u00A0"}</FieldDescription>}
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
</Field> </Field>

View File

@ -14,7 +14,7 @@ type DatePickerInputFieldProps<TFormValues extends FieldValues> = SUICalendarPro
required?: boolean; required?: boolean;
readOnly?: boolean; readOnly?: boolean;
displayDateFormat?: string; // e.g. "dd/MM/yyyy" displayDateFormat?: string; // e.g. "dd-MM-yyyy"
parseDateFormat?: string; // e.g. "yyyy-MM-dd" parseDateFormat?: string; // e.g. "yyyy-MM-dd"
orientation?: "vertical" | "horizontal" | "responsive", orientation?: "vertical" | "horizontal" | "responsive",
}; };
@ -22,7 +22,7 @@ type DatePickerInputFieldProps<TFormValues extends FieldValues> = SUICalendarPro
export function DatePickerInputField<TFormValues extends FieldValues>({ export function DatePickerInputField<TFormValues extends FieldValues>({
control, control,
name, name,
displayDateFormat = "dd-MM-y1qyyy", displayDateFormat = "dd-MM-yyyy",
parseDateFormat = "yyyy-MM-dd", parseDateFormat = "yyyy-MM-dd",
...props ...props
}: DatePickerInputFieldProps<TFormValues>) { }: DatePickerInputFieldProps<TFormValues>) {
@ -35,8 +35,6 @@ export function DatePickerInputField<TFormValues extends FieldValues>({
field={field} field={field}
fieldState={fieldState} fieldState={fieldState}
formState={formState} formState={formState}
htmlFor={name}
displayDateFormat={displayDateFormat} displayDateFormat={displayDateFormat}
parseDateFormat={parseDateFormat} parseDateFormat={parseDateFormat}

View File

@ -0,0 +1,76 @@
// DatePopoverCalendar.tsx
import {
Button,
Calendar,
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
PopoverContent,
} from "@repo/shadcn-ui/components";
import { useTranslation } from "../../../locales/i18n.ts";
export function DatePopoverCalendar({
contentId,
label,
description,
parsedDate,
onSelect,
onToday,
onClose,
...calendarProps
}: {
contentId: string;
label?: string;
description?: string;
parsedDate: Date | null;
onSelect: (date: Date | undefined) => void;
onToday: () => void;
onClose: () => void;
} & Omit<React.ComponentProps<typeof Calendar>, "mode" | "selected" | "onSelect">) {
const { t } = useTranslation();
return (
<PopoverContent id={contentId} className="w-auto p-0">
<Card className="border-none shadow-none">
<CardHeader className="border-b">
<CardTitle>{label}</CardTitle>
<CardDescription>{description || "\u00A0"}</CardDescription>
<CardAction>
<Button
size="sm"
variant="outline"
onClick={onToday}
>
{t("components.date_picker_input_field.today")}
</Button>
</CardAction>
</CardHeader>
<CardContent className='px-0'>
<Calendar
selected={parsedDate ?? undefined}
defaultMonth={parsedDate ?? undefined}
onSelect={onSelect}
mode='single'
numberOfMonths={2}
/>
</CardContent>
<CardFooter className='mx-auto'>
<Button
size="sm"
variant="outline"
onClick={onClose}>
{t("components.date_picker_input_field.close")}
</Button>
</CardFooter>
</Card>
</PopoverContent >
);
}

View File

@ -1069,8 +1069,8 @@ importers:
specifier: ^19.1.0 specifier: ^19.1.0
version: 19.2.0 version: 19.2.0
react-day-picker: react-day-picker:
specifier: 8.10.1 specifier: 9.11.1
version: 8.10.1(date-fns@4.1.0)(react@19.2.0) version: 9.11.1(react@19.2.0)
react-dom: react-dom:
specifier: ^19.1.0 specifier: ^19.1.0
version: 19.2.0(react@19.2.0) version: 19.2.0(react@19.2.0)
@ -1302,6 +1302,9 @@ packages:
'@dabh/diagnostics@2.0.8': '@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
'@date-fns/tz@1.4.1':
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
'@dnd-kit/accessibility@3.1.1': '@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies: peerDependencies:
@ -3607,6 +3610,9 @@ packages:
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
date-fns-jalali@4.1.0-0:
resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
date-fns@4.1.0: date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
@ -5304,11 +5310,11 @@ packages:
react: '>= 17.0.0' react: '>= 17.0.0'
styled-components: '>= 5.0.0' styled-components: '>= 5.0.0'
react-day-picker@8.10.1: react-day-picker@9.11.1:
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} resolution: {integrity: sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==}
engines: {node: '>=18'}
peerDependencies: peerDependencies:
date-fns: ^2.28.0 || ^3.0.0 react: '>=16.8.0'
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom@19.2.0: react-dom@19.2.0:
resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
@ -6567,6 +6573,8 @@ snapshots:
enabled: 2.0.0 enabled: 2.0.0
kuler: 2.0.0 kuler: 2.0.0
'@date-fns/tz@1.4.1': {}
'@dnd-kit/accessibility@3.1.1(react@19.2.0)': '@dnd-kit/accessibility@3.1.1(react@19.2.0)':
dependencies: dependencies:
react: 19.2.0 react: 19.2.0
@ -8897,6 +8905,8 @@ snapshots:
data-uri-to-buffer@6.0.2: {} data-uri-to-buffer@6.0.2: {}
date-fns-jalali@4.1.0-0: {}
date-fns@4.1.0: {} date-fns@4.1.0: {}
debug@2.6.9: debug@2.6.9:
@ -10687,9 +10697,11 @@ snapshots:
react: 19.2.0 react: 19.2.0
styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-day-picker@8.10.1(date-fns@4.1.0)(react@19.2.0): react-day-picker@9.11.1(react@19.2.0):
dependencies: dependencies:
'@date-fns/tz': 1.4.1
date-fns: 4.1.0 date-fns: 4.1.0
date-fns-jalali: 4.1.0-0
react: 19.2.0 react: 19.2.0
react-dom@19.2.0(react@19.2.0): react-dom@19.2.0(react@19.2.0):