Clientes y facturas de cliente
This commit is contained in:
parent
8d0c0b88de
commit
7e700bdf22
@ -45,6 +45,7 @@
|
|||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
"@erp/customer-invoices": "workspace:*",
|
"@erp/customer-invoices": "workspace:*",
|
||||||
"@erp/customers": "workspace:*",
|
"@erp/customers": "workspace:*",
|
||||||
|
"@erp/verifactu": "workspace:*",
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cls-rtracer": "^2.6.3",
|
"cls-rtracer": "^2.6.3",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
|
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
|
||||||
import customersAPIModule from "@erp/customers/api";
|
import customersAPIModule from "@erp/customers/api";
|
||||||
import verifactuAPIModule from "@erp/verifactu/api";
|
//import verifactuAPIModule from "@erp/verifactu/api";
|
||||||
|
|
||||||
import { registerModule } from "./lib";
|
import { registerModule } from "./lib";
|
||||||
|
|
||||||
@ -8,5 +8,5 @@ export const registerModules = () => {
|
|||||||
//registerModule(authAPIModule);
|
//registerModule(authAPIModule);
|
||||||
registerModule(customersAPIModule);
|
registerModule(customersAPIModule);
|
||||||
registerModule(customerInvoicesAPIModule);
|
registerModule(customerInvoicesAPIModule);
|
||||||
registerModule(verifactuAPIModule);
|
// registerModule(verifactuAPIModule);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"saving": "Saving...",
|
||||||
"required": "•"
|
"required": "•"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export function TaxesMultiSelectField<TFormValues extends FieldValues>({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className={cn("space-y-0", className)}>
|
<FormItem className={cn("space-y-0", className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<div className='mb-1 flex justify-between gap-2'>
|
<div className='mb-1 flex justify-between'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<FormLabel htmlFor={name} className='m-0'>
|
<FormLabel htmlFor={name} className='m-0'>
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
|
import { XIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -34,7 +35,6 @@ export const CancelFormButton = ({
|
|||||||
|
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = useCallback(async () => {
|
||||||
const ok = requestConfirm ? await requestConfirm() : true;
|
const ok = requestConfirm ? await requestConfirm() : true;
|
||||||
console.log("ok => ", ok);
|
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
if (onCancel) {
|
if (onCancel) {
|
||||||
@ -43,7 +43,6 @@ export const CancelFormButton = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (to) {
|
if (to) {
|
||||||
console.log("navego => ", to);
|
|
||||||
navigate(to);
|
navigate(to);
|
||||||
}
|
}
|
||||||
// si no hay ni onCancel ni to → no hace nada
|
// si no hay ni onCancel ni to → no hace nada
|
||||||
@ -60,6 +59,7 @@ export const CancelFormButton = ({
|
|||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
data-testid={dataTestId}
|
data-testid={dataTestId}
|
||||||
>
|
>
|
||||||
|
<XIcon className='mr-2 h-3 w-3' />
|
||||||
<span>{label ?? defaultLabel}</span>
|
<span>{label ?? defaultLabel}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,16 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import {
|
||||||
|
ArrowLeftIcon,
|
||||||
|
CopyIcon,
|
||||||
|
EyeIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
RotateCcwIcon,
|
||||||
|
Trash2Icon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
import { CancelFormButton, CancelFormButtonProps } from "./cancel-form-button";
|
import { CancelFormButton, CancelFormButtonProps } from "./cancel-form-button";
|
||||||
import { SubmitButtonProps, SubmitFormButton } from "./submit-form-button";
|
import { SubmitButtonProps, SubmitFormButton } from "./submit-form-button";
|
||||||
|
|
||||||
type Align = "start" | "center" | "end" | "between";
|
type Align = "start" | "center" | "end" | "between";
|
||||||
|
|
||||||
|
type GroupSubmitButtonProps = Omit<SubmitButtonProps, "isLoading" | "preventDoubleSubmit">;
|
||||||
|
|
||||||
export type FormCommitButtonGroupProps = {
|
export type FormCommitButtonGroupProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
align?: Align; // default "end"
|
align?: Align; // default "end"
|
||||||
gap?: string; // default "gap-2"
|
gap?: string; // default "gap-2"
|
||||||
reverseOrderOnMobile?: boolean; // default true (Cancel debajo en móvil)
|
reverseOrderOnMobile?: boolean; // default true (Cancel debajo en móvil)
|
||||||
|
|
||||||
|
isLoading?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading
|
||||||
|
|
||||||
cancel?: CancelFormButtonProps & { show?: boolean };
|
cancel?: CancelFormButtonProps & { show?: boolean };
|
||||||
submit?: SubmitButtonProps; // props directas a SubmitButton
|
submit?: GroupSubmitButtonProps; // props directas a SubmitButton
|
||||||
|
|
||||||
|
onReset?: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onPreview?: () => void;
|
||||||
|
onDuplicate?: () => void;
|
||||||
|
onBack?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const alignToJustify: Record<Align, string> = {
|
const alignToJustify: Record<Align, string> = {
|
||||||
@ -25,10 +55,33 @@ export const FormCommitButtonGroup = ({
|
|||||||
align = "end",
|
align = "end",
|
||||||
gap = "gap-2",
|
gap = "gap-2",
|
||||||
reverseOrderOnMobile = true,
|
reverseOrderOnMobile = true,
|
||||||
|
|
||||||
|
isLoading,
|
||||||
|
disabled = false,
|
||||||
|
preventDoubleSubmit = true,
|
||||||
|
|
||||||
cancel,
|
cancel,
|
||||||
submit,
|
submit,
|
||||||
|
|
||||||
|
onReset,
|
||||||
|
onDelete,
|
||||||
|
onPreview,
|
||||||
|
onDuplicate,
|
||||||
|
onBack,
|
||||||
}: FormCommitButtonGroupProps) => {
|
}: FormCommitButtonGroupProps) => {
|
||||||
const showCancel = cancel?.show ?? true;
|
const showCancel = cancel?.show ?? true;
|
||||||
|
const hasSecondaryActions = onReset || onPreview || onDuplicate || onBack || onDelete;
|
||||||
|
|
||||||
|
// ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading
|
||||||
|
let rhfIsSubmitting = false;
|
||||||
|
try {
|
||||||
|
const ctx = useFormContext();
|
||||||
|
rhfIsSubmitting = !!ctx?.formState?.isSubmitting;
|
||||||
|
} catch {
|
||||||
|
// No hay provider de RHF; ignorar
|
||||||
|
}
|
||||||
|
const busy = isLoading ?? rhfIsSubmitting;
|
||||||
|
const computedDisabled = !!(disabled || (preventDoubleSubmit && busy));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -40,8 +93,62 @@ export const FormCommitButtonGroup = ({
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{showCancel && <CancelFormButton {...cancel} />}
|
|
||||||
{submit && <SubmitFormButton {...submit} />}
|
{submit && <SubmitFormButton {...submit} />}
|
||||||
|
{showCancel && <CancelFormButton {...cancel} />}
|
||||||
|
|
||||||
|
{/* Menú de acciones adicionales */}
|
||||||
|
{hasSecondaryActions && (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant='ghost' size='sm' disabled={computedDisabled} className='px-2'>
|
||||||
|
<MoreHorizontalIcon className='h-4 w-4' />
|
||||||
|
<span className='sr-only'>Más acciones</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align='end' className='w-48'>
|
||||||
|
{onReset && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={onReset}
|
||||||
|
disabled={computedDisabled}
|
||||||
|
className='text-muted-foreground'
|
||||||
|
>
|
||||||
|
<RotateCcwIcon className='mr-2 h-4 w-4' />
|
||||||
|
Deshacer cambios
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{onPreview && (
|
||||||
|
<DropdownMenuItem onClick={onPreview} className='text-muted-foreground'>
|
||||||
|
<EyeIcon className='mr-2 h-4 w-4' />
|
||||||
|
Vista previa
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{onDuplicate && (
|
||||||
|
<DropdownMenuItem onClick={onDuplicate} className='text-muted-foreground'>
|
||||||
|
<CopyIcon className='mr-2 h-4 w-4' />
|
||||||
|
Duplicar
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{onBack && (
|
||||||
|
<DropdownMenuItem onClick={onBack} className='text-muted-foreground'>
|
||||||
|
<ArrowLeftIcon className='mr-2 h-4 w-4' />
|
||||||
|
Volver
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{onDelete && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={onDelete}
|
||||||
|
className='text-destructive focus:text-destructive'
|
||||||
|
>
|
||||||
|
<Trash2Icon className='mr-2 h-4 w-4' />
|
||||||
|
Eliminar
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
import { LoaderCircleIcon } from "lucide-react";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { LoaderCircleIcon, SaveIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "../../../i18n.ts";
|
import { useTranslation } from "../../../i18n.ts";
|
||||||
@ -8,12 +9,15 @@ export type SubmitButtonProps = {
|
|||||||
formId?: string;
|
formId?: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
labelIsLoading?: string;
|
||||||
|
|
||||||
variant?: React.ComponentProps<typeof Button>["variant"];
|
variant?: React.ComponentProps<typeof Button>["variant"];
|
||||||
size?: React.ComponentProps<typeof Button>["size"];
|
size?: React.ComponentProps<typeof Button>["size"];
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
||||||
preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading
|
preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading
|
||||||
|
hasChanges?: boolean;
|
||||||
|
|
||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -24,10 +28,12 @@ export const SubmitFormButton = ({
|
|||||||
formId,
|
formId,
|
||||||
isLoading,
|
isLoading,
|
||||||
label,
|
label,
|
||||||
|
labelIsLoading,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
size = "default",
|
size = "default",
|
||||||
className,
|
className,
|
||||||
preventDoubleSubmit = true,
|
preventDoubleSubmit = true,
|
||||||
|
hasChanges = false,
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
children,
|
children,
|
||||||
@ -35,6 +41,7 @@ export const SubmitFormButton = ({
|
|||||||
}: SubmitButtonProps) => {
|
}: SubmitButtonProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const defaultLabel = t ? t("common.save") : "Save";
|
const defaultLabel = t ? t("common.save") : "Save";
|
||||||
|
const defaultLabelIsLoading = t ? t("common.saving") : "Saving...";
|
||||||
|
|
||||||
// ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading
|
// ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading
|
||||||
let rhfIsSubmitting = false;
|
let rhfIsSubmitting = false;
|
||||||
@ -65,20 +72,30 @@ export const SubmitFormButton = ({
|
|||||||
form={formId}
|
form={formId}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={className}
|
|
||||||
disabled={computedDisabled}
|
disabled={computedDisabled}
|
||||||
aria-busy={busy}
|
aria-busy={busy}
|
||||||
aria-disabled={computedDisabled}
|
aria-disabled={computedDisabled}
|
||||||
data-state={dataState}
|
data-state={dataState}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
data-testid={dataTestId}
|
data-testid={dataTestId}
|
||||||
|
className={cn("min-w-[100px] font-medium", hasChanges && "ring-2 ring-primary/20", className)}
|
||||||
>
|
>
|
||||||
{children ? (
|
{children ? (
|
||||||
children
|
children
|
||||||
) : (
|
) : (
|
||||||
<span className='inline-flex items-center gap-2'>
|
<span className='inline-flex items-center gap-2'>
|
||||||
{busy && <LoaderCircleIcon className='h-4 w-4 animate-spin' aria-hidden='true' />}
|
{busy && (
|
||||||
<span>{label ?? defaultLabel}</span>
|
<>
|
||||||
|
<LoaderCircleIcon className='mr-2 h-3 w-3 animate-spin' aria-hidden='true' />
|
||||||
|
<span>{labelIsLoading ?? defaultLabelIsLoading}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!busy && (
|
||||||
|
<>
|
||||||
|
<SaveIcon className='mr-2 h-3 w-3' />
|
||||||
|
<span>{label ?? defaultLabel}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
|
import { Description, Field, FieldGroup, Fieldset, Legend } from "@repo/rdx-ui/components";
|
||||||
|
|
||||||
import { SelectField } from "@repo/rdx-ui/components";
|
import { SelectField } from "@repo/rdx-ui/components";
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { CURRENCY_OPTIONS, LANGUAGE_OPTIONS } from "../../constants";
|
import { CURRENCY_OPTIONS, LANGUAGE_OPTIONS } from "../../constants";
|
||||||
import { useTranslation } from "../../i18n";
|
import { useTranslation } from "../../i18n";
|
||||||
@ -16,15 +11,12 @@ export const CustomerAdditionalConfigFields = () => {
|
|||||||
const { control } = useFormContext<CustomerFormData>();
|
const { control } = useFormContext<CustomerFormData>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='border-0 shadow-none'>
|
<Fieldset>
|
||||||
<CardHeader>
|
<Legend>{t("form_groups.preferences.title")}</Legend>
|
||||||
<CardTitle>{t("form_groups.preferences.title")}</CardTitle>
|
<Description>{t("form_groups.preferences.description")}</Description>
|
||||||
<CardDescription>{t("form_groups.preferences.description")}</CardDescription>
|
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||||
</CardHeader>
|
<Field className='lg:col-span-2'>
|
||||||
<CardContent>
|
|
||||||
<div className='grid grid-cols-1 gap-8 lg:grid-cols-4 mb-12 '>
|
|
||||||
<SelectField
|
<SelectField
|
||||||
className='lg:col-span-2'
|
|
||||||
control={control}
|
control={control}
|
||||||
name='language_code'
|
name='language_code'
|
||||||
required
|
required
|
||||||
@ -33,6 +25,8 @@ export const CustomerAdditionalConfigFields = () => {
|
|||||||
description={t("form_fields.language_code.description")}
|
description={t("form_fields.language_code.description")}
|
||||||
items={[...LANGUAGE_OPTIONS]}
|
items={[...LANGUAGE_OPTIONS]}
|
||||||
/>
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field className='lg:col-span-2'>
|
||||||
<SelectField
|
<SelectField
|
||||||
className='lg:col-span-2'
|
className='lg:col-span-2'
|
||||||
control={control}
|
control={control}
|
||||||
@ -43,8 +37,8 @@ export const CustomerAdditionalConfigFields = () => {
|
|||||||
description={t("form_fields.currency_code.description")}
|
description={t("form_fields.currency_code.description")}
|
||||||
items={[...CURRENCY_OPTIONS]}
|
items={[...CURRENCY_OPTIONS]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Field>
|
||||||
</CardContent>
|
</FieldGroup>
|
||||||
</Card>
|
</Fieldset>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,18 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Description,
|
Description,
|
||||||
|
Field,
|
||||||
FieldGroup,
|
FieldGroup,
|
||||||
Fieldset,
|
Fieldset,
|
||||||
Legend,
|
Legend,
|
||||||
SelectField,
|
SelectField,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@repo/rdx-ui/components";
|
} from "@repo/rdx-ui/components";
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { COUNTRY_OPTIONS } from "../../constants";
|
import { COUNTRY_OPTIONS } from "../../constants";
|
||||||
import { useTranslation } from "../../i18n";
|
import { useTranslation } from "../../i18n";
|
||||||
@ -26,7 +20,7 @@ export const CustomerAddressFields = () => {
|
|||||||
<Fieldset>
|
<Fieldset>
|
||||||
<Legend>{t("form_groups.address.title")}</Legend>
|
<Legend>{t("form_groups.address.title")}</Legend>
|
||||||
<Description>{t("form_groups.address.description")}</Description>
|
<Description>{t("form_groups.address.description")}</Description>
|
||||||
<FieldGroup className='grid grid-cols-1 gap-8 lg:grid-cols-4'>
|
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||||
<TextField
|
<TextField
|
||||||
className='lg:col-span-2'
|
className='lg:col-span-2'
|
||||||
control={control}
|
control={control}
|
||||||
@ -60,77 +54,16 @@ export const CustomerAddressFields = () => {
|
|||||||
description={t("form_fields.postal_code.description")}
|
description={t("form_fields.postal_code.description")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<Field className='lg:col-span-2 lg:col-start-1'>
|
||||||
className='lg:col-span-2 lg:col-start-1'
|
|
||||||
control={control}
|
|
||||||
name='province'
|
|
||||||
label={t("form_fields.province.label")}
|
|
||||||
placeholder={t("form_fields.province.placeholder")}
|
|
||||||
description={t("form_fields.province.description")}
|
|
||||||
/>
|
|
||||||
<SelectField
|
|
||||||
control={control}
|
|
||||||
name='country'
|
|
||||||
required
|
|
||||||
label={t("form_fields.country.label")}
|
|
||||||
placeholder={t("form_fields.country.placeholder")}
|
|
||||||
description={t("form_fields.country.description")}
|
|
||||||
items={[...COUNTRY_OPTIONS]}
|
|
||||||
/>
|
|
||||||
</FieldGroup>
|
|
||||||
</Fieldset>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className='border-0 shadow-none'>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t("form_groups.address.title")}</CardTitle>
|
|
||||||
<CardDescription>{t("form_groups.address.description")}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className='grid grid-cols-1 gap-8 lg:grid-cols-4 mb-6 '>
|
|
||||||
<TextField
|
<TextField
|
||||||
className='lg:col-span-2'
|
|
||||||
control={control}
|
|
||||||
name='street'
|
|
||||||
label={t("form_fields.street.label")}
|
|
||||||
placeholder={t("form_fields.street.placeholder")}
|
|
||||||
description={t("form_fields.street.description")}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
className='lg:col-span-2'
|
|
||||||
control={control}
|
|
||||||
name='street2'
|
|
||||||
label={t("form_fields.street2.label")}
|
|
||||||
placeholder={t("form_fields.street2.placeholder")}
|
|
||||||
description={t("form_fields.street2.description")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
className='lg:col-span-2'
|
|
||||||
control={control}
|
|
||||||
name='city'
|
|
||||||
label={t("form_fields.city.label")}
|
|
||||||
placeholder={t("form_fields.city.placeholder")}
|
|
||||||
description={t("form_fields.city.description")}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
control={control}
|
|
||||||
name='postal_code'
|
|
||||||
label={t("form_fields.postal_code.label")}
|
|
||||||
placeholder={t("form_fields.postal_code.placeholder")}
|
|
||||||
description={t("form_fields.postal_code.description")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='grid grid-cols-1 gap-8 lg:grid-cols-4 mb-0 '>
|
|
||||||
<TextField
|
|
||||||
className='lg:col-span-2'
|
|
||||||
control={control}
|
control={control}
|
||||||
name='province'
|
name='province'
|
||||||
label={t("form_fields.province.label")}
|
label={t("form_fields.province.label")}
|
||||||
placeholder={t("form_fields.province.placeholder")}
|
placeholder={t("form_fields.province.placeholder")}
|
||||||
description={t("form_fields.province.description")}
|
description={t("form_fields.province.description")}
|
||||||
/>
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field className='lg:col-span-2'>
|
||||||
<SelectField
|
<SelectField
|
||||||
control={control}
|
control={control}
|
||||||
name='country'
|
name='country'
|
||||||
@ -140,8 +73,8 @@ export const CustomerAddressFields = () => {
|
|||||||
description={t("form_fields.country.description")}
|
description={t("form_fields.country.description")}
|
||||||
items={[...COUNTRY_OPTIONS]}
|
items={[...COUNTRY_OPTIONS]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Field>
|
||||||
</CardContent>
|
</FieldGroup>
|
||||||
</Card>
|
</Fieldset>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const CustomerBasicInfoFields = () => {
|
|||||||
<Fieldset>
|
<Fieldset>
|
||||||
<Legend>Identificación</Legend>
|
<Legend>Identificación</Legend>
|
||||||
<Description>descripción</Description>
|
<Description>descripción</Description>
|
||||||
<FieldGroup className='grid grid-cols-1 gap-8 lg:grid-cols-4'>
|
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||||
<Field className='lg:col-span-2'>
|
<Field className='lg:col-span-2'>
|
||||||
<TextField
|
<TextField
|
||||||
control={control}
|
control={control}
|
||||||
@ -59,7 +59,7 @@ export const CustomerBasicInfoFields = () => {
|
|||||||
field.onChange(value === "false" ? "false" : "true");
|
field.onChange(value === "false" ? "false" : "true");
|
||||||
}}
|
}}
|
||||||
defaultValue={field.value ? "true" : "false"}
|
defaultValue={field.value ? "true" : "false"}
|
||||||
className='flex items-center gap-8'
|
className='flex items-center gap-6'
|
||||||
>
|
>
|
||||||
<FormItem className='flex items-center space-x-2'>
|
<FormItem className='flex items-center space-x-2'>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -106,16 +106,16 @@ export const CustomerBasicInfoFields = () => {
|
|||||||
placeholder={t("form_fields.reference.placeholder")}
|
placeholder={t("form_fields.reference.placeholder")}
|
||||||
description={t("form_fields.reference.description")}
|
description={t("form_fields.reference.description")}
|
||||||
/>
|
/>
|
||||||
<TaxesMultiSelectField
|
<Field className='lg:col-span-2'>
|
||||||
className='lg:col-span-2'
|
<TaxesMultiSelectField
|
||||||
control={control}
|
control={control}
|
||||||
name='default_taxes'
|
name='default_taxes'
|
||||||
required
|
required
|
||||||
label={t("form_fields.default_taxes.label")}
|
label={t("form_fields.default_taxes.label")}
|
||||||
placeholder={t("form_fields.default_taxes.placeholder")}
|
placeholder={t("form_fields.default_taxes.placeholder")}
|
||||||
description={t("form_fields.default_taxes.description")}
|
description={t("form_fields.default_taxes.description")}
|
||||||
/>
|
/>
|
||||||
|
</Field>
|
||||||
<TextAreaField
|
<TextAreaField
|
||||||
className='lg:col-span-full'
|
className='lg:col-span-full'
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { Description, FieldGroup, Fieldset, Legend, TextField } from "@repo/rdx-ui/components";
|
import {
|
||||||
|
Description,
|
||||||
|
Field,
|
||||||
|
FieldGroup,
|
||||||
|
Fieldset,
|
||||||
|
Legend,
|
||||||
|
TextField,
|
||||||
|
} from "@repo/rdx-ui/components";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@repo/shadcn-ui/components";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
import { ChevronDown, MailIcon, PhoneIcon, SmartphoneIcon } from "lucide-react";
|
import { ChevronDown, MailIcon, PhoneIcon, SmartphoneIcon } from "lucide-react";
|
||||||
@ -15,7 +22,7 @@ export const CustomerContactFields = () => {
|
|||||||
<Fieldset>
|
<Fieldset>
|
||||||
<Legend>{t("form_groups.contact_info.title")}</Legend>
|
<Legend>{t("form_groups.contact_info.title")}</Legend>
|
||||||
<Description>{t("form_groups.contact_info.description")}</Description>
|
<Description>{t("form_groups.contact_info.description")}</Description>
|
||||||
<FieldGroup className='grid grid-cols-1 gap-8 lg:grid-cols-4'>
|
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||||
<TextField
|
<TextField
|
||||||
className='lg:col-span-2'
|
className='lg:col-span-2'
|
||||||
control={control}
|
control={control}
|
||||||
@ -27,6 +34,7 @@ export const CustomerContactFields = () => {
|
|||||||
typePreset='email'
|
typePreset='email'
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
className='lg:col-span-2'
|
className='lg:col-span-2'
|
||||||
control={control}
|
control={control}
|
||||||
@ -90,29 +98,37 @@ export const CustomerContactFields = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Collapsible open={open} onOpenChange={setOpen} className='space-y-4'>
|
<Collapsible
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
className='space-y-8 col-start-1 col-span-full'
|
||||||
|
>
|
||||||
<CollapsibleTrigger className='inline-flex items-center gap-1 text-sm text-primary hover:underline'>
|
<CollapsibleTrigger className='inline-flex items-center gap-1 text-sm text-primary hover:underline'>
|
||||||
{t("common.more_details")}{" "}
|
{t("common.more_details")}{" "}
|
||||||
<ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
|
<ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<FieldGroup className='grid grid-cols-1 gap-8 lg:grid-cols-4'>
|
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||||
<TextField
|
<Field className='lg:col-span-2'>
|
||||||
className='lg:col-span-2'
|
<TextField
|
||||||
control={control}
|
className='lg:col-span-2'
|
||||||
name='website'
|
control={control}
|
||||||
label={t("form_fields.website.label")}
|
name='website'
|
||||||
placeholder={t("form_fields.website.placeholder")}
|
label={t("form_fields.website.label")}
|
||||||
description={t("form_fields.website.description")}
|
placeholder={t("form_fields.website.placeholder")}
|
||||||
/>
|
description={t("form_fields.website.description")}
|
||||||
<TextField
|
/>
|
||||||
className='lg:col-span-2'
|
</Field>
|
||||||
control={control}
|
<Field className='lg:col-span-2'>
|
||||||
name='fax'
|
<TextField
|
||||||
label={t("form_fields.fax.label")}
|
className='lg:col-span-2'
|
||||||
placeholder={t("form_fields.fax.placeholder")}
|
control={control}
|
||||||
description={t("form_fields.fax.description")}
|
name='fax'
|
||||||
/>
|
label={t("form_fields.fax.label")}
|
||||||
|
placeholder={t("form_fields.fax.placeholder")}
|
||||||
|
description={t("form_fields.fax.description")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export const CustomerEditForm = ({ formId, onSubmit, onError }: CustomerFormProp
|
|||||||
<div className='w-full xl:w-6/12'>
|
<div className='w-full xl:w-6/12'>
|
||||||
<FormDebug />
|
<FormDebug />
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full xl:grow'>
|
<div className='w-full xl:grow space-y-6'>
|
||||||
<CustomerBasicInfoFields />
|
<CustomerBasicInfoFields />
|
||||||
<CustomerContactFields />
|
<CustomerContactFields />
|
||||||
<CustomerAddressFields />
|
<CustomerAddressFields />
|
||||||
|
|||||||
@ -54,6 +54,10 @@ export const CustomerCreate = () => {
|
|||||||
// Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario
|
// Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppBreadcrumb />
|
<AppBreadcrumb />
|
||||||
@ -69,14 +73,17 @@ export const CustomerCreate = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FormCommitButtonGroup
|
<FormCommitButtonGroup
|
||||||
|
isLoading={isCreating}
|
||||||
|
disabled={isCreating}
|
||||||
cancel={{
|
cancel={{
|
||||||
to: "/customers/list",
|
to: "/customers/list",
|
||||||
|
disabled: isCreating,
|
||||||
}}
|
}}
|
||||||
submit={{
|
submit={{
|
||||||
formId: "customer-create-form",
|
formId: "customer-create-form",
|
||||||
disabled: isCreating,
|
disabled: isCreating,
|
||||||
isLoading: isCreating,
|
|
||||||
}}
|
}}
|
||||||
|
onBack={() => handleBack()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Alerta de error de actualización (si ha fallado el último intento) */}
|
{/* Alerta de error de actualización (si ha fallado el último intento) */}
|
||||||
|
|||||||
@ -67,6 +67,12 @@ export const CustomerUpdate = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReset = () => form.reset(customerData ?? defaultCustomerFormData);
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
const handleError = (errors: FieldErrors<CustomerFormData>) => {
|
const handleError = (errors: FieldErrors<CustomerFormData>) => {
|
||||||
console.error("Errores en el formulario:", errors);
|
console.error("Errores en el formulario:", errors);
|
||||||
// Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario
|
// Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario
|
||||||
@ -125,14 +131,18 @@ export const CustomerUpdate = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FormCommitButtonGroup
|
<FormCommitButtonGroup
|
||||||
|
isLoading={isUpdating}
|
||||||
|
disabled={isUpdating}
|
||||||
cancel={{
|
cancel={{
|
||||||
to: "/customers/list",
|
to: "/customers/list",
|
||||||
|
disabled: isUpdating,
|
||||||
}}
|
}}
|
||||||
submit={{
|
submit={{
|
||||||
formId: "customer-update-form",
|
formId: "customer-update-form",
|
||||||
disabled: isUpdating,
|
disabled: isUpdating,
|
||||||
isLoading: isUpdating,
|
|
||||||
}}
|
}}
|
||||||
|
onBack={() => handleBack()}
|
||||||
|
onReset={() => handleReset()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Alerta de error de actualización (si ha fallado el último intento) */}
|
{/* Alerta de error de actualización (si ha fallado el último intento) */}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
//export * from "./mappers";
|
//export * from "./mappers";
|
||||||
//export * from "./sequelize";
|
|
||||||
export * from "./express";
|
export * from "./express";
|
||||||
|
export * from "./sequelize";
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@ -166,16 +167,9 @@ export function DatePickerInputField<TFormValues extends FieldValues>({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(inputError || description) && (
|
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||||
<p
|
{description || "\u00A0"}
|
||||||
className={cn(
|
</FormDescription>
|
||||||
"text-xs mt-1",
|
|
||||||
inputError ? "text-destructive" : "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{inputError || description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@ -53,9 +54,9 @@ export function NumberField<TFormValues extends FieldValues>({
|
|||||||
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
|
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||||
{description || "\u00A0"}
|
{description || "\u00A0"}
|
||||||
</p>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className={cn("space-y-0", className)}>
|
<FormItem className={cn("space-y-0", className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<div className='mb-1 flex justify-between gap-2'>
|
<div className='mb-1 flex justify-between'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<FormLabel htmlFor={name} className='m-0'>
|
<FormLabel htmlFor={name} className='m-0'>
|
||||||
{label}
|
{label}
|
||||||
@ -72,7 +72,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
|||||||
)}
|
)}
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={isDisabled}>
|
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={isDisabled}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger className='w-full'>
|
<SelectTrigger className='w-full bg-background h-8'>
|
||||||
<SelectValue placeholder={placeholder} />
|
<SelectValue placeholder={placeholder} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -85,9 +85,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<FormDescription
|
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||||
className={cn("text-xs text-muted-foreground", !description && "invisible")}
|
|
||||||
>
|
|
||||||
{description || "\u00A0"}
|
{description || "\u00A0"}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@ -58,12 +59,17 @@ export function TextAreaField<TFormValues extends FieldValues>({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea disabled={isDisabled} placeholder={placeholder} {...field} />
|
<Textarea
|
||||||
|
disabled={isDisabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={"bg-background"}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||||
{description || "\u00A0"}
|
{description || "\u00A0"}
|
||||||
</p>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@ -393,7 +394,7 @@ export function TextField<TFormValues extends FieldValues>({
|
|||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
{...rest}
|
{...rest}
|
||||||
className={cn(
|
className={cn(
|
||||||
"placeholder:font-normal placeholder:italic",
|
"placeholder:font-normal placeholder:italic bg-background",
|
||||||
inputPadding,
|
inputPadding,
|
||||||
invalid && "border-destructive focus-visible:ring-destructive",
|
invalid && "border-destructive focus-visible:ring-destructive",
|
||||||
valid && showSuccessWhenValid && "border-green-500 focus-visible:ring-green-500",
|
valid && showSuccessWhenValid && "border-green-500 focus-visible:ring-green-500",
|
||||||
@ -448,12 +449,12 @@ export function TextField<TFormValues extends FieldValues>({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<div className='mt-1 flex items-start justify-between'>
|
<div className='mt-1 flex items-start justify-between'>
|
||||||
<p
|
<FormDescription
|
||||||
id={describedById}
|
id={describedById}
|
||||||
className={cn("text-xs text-muted-foreground", !description && "invisible")}
|
className={cn("text-xs truncate", !description && "invisible")}
|
||||||
>
|
>
|
||||||
{description || "\u00A0"}
|
{description || "\u00A0"}
|
||||||
</p>
|
</FormDescription>
|
||||||
|
|
||||||
{showCounter && typeof maxLength === "number" && (
|
{showCounter && typeof maxLength === "number" && (
|
||||||
<p className='text-xs text-muted-foreground'>
|
<p className='text-xs text-muted-foreground'>
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import * as React from "react";
|
|||||||
export const Fieldset = ({ className, children, ...props }: React.ComponentProps<"fieldset">) => (
|
export const Fieldset = ({ className, children, ...props }: React.ComponentProps<"fieldset">) => (
|
||||||
<fieldset
|
<fieldset
|
||||||
data-slot='fieldset'
|
data-slot='fieldset'
|
||||||
className={cn("*:data-[slot=text]:mt-1 [&>*+[data-slot=control]]:mt-6", className)}
|
className={cn(
|
||||||
|
"*:data-[slot=text]:mt-1 [&>*+[data-slot=control]]:mt-6 bg-gray-50/50 rounded-xl p-6",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@ -12,7 +15,7 @@ export const Fieldset = ({ className, children, ...props }: React.ComponentProps
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const FieldGroup = ({ className, children, ...props }: React.ComponentProps<"div">) => (
|
export const FieldGroup = ({ className, children, ...props }: React.ComponentProps<"div">) => (
|
||||||
<div data-slot='control' className={cn("space-y-8", className)} {...props}>
|
<div data-slot='control' className={cn("space-y-6", className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
||||||
|
|
||||||
export function FormContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot='form-content'
|
|
||||||
className={cn("grid grid-cols-1 gap-6 md:grid-cols-4 space-y-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
export * from "./DatePickerField.tsx";
|
export * from "./DatePickerField.tsx";
|
||||||
export * from "./DatePickerInputField.tsx";
|
export * from "./DatePickerInputField.tsx";
|
||||||
export * from "./fieldset.tsx";
|
export * from "./fieldset.tsx";
|
||||||
export * from "./form-content.tsx";
|
|
||||||
export * from "./multi-select-field.tsx";
|
export * from "./multi-select-field.tsx";
|
||||||
export * from "./SelectField.tsx";
|
export * from "./SelectField.tsx";
|
||||||
export * from "./TextAreaField.tsx";
|
export * from "./TextAreaField.tsx";
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
CommandList,
|
CommandList,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
@ -438,12 +439,12 @@ export const MultiSelectFieldInner = React.forwardRef(
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<div className='mt-1 flex items-start justify-between'>
|
<div className='mt-1 flex items-start justify-between'>
|
||||||
<p
|
<FormDescription
|
||||||
id={describedById}
|
id={describedById}
|
||||||
className={cn("text-xs text-muted-foreground", !description && "invisible")}
|
className={cn("text-xs truncate", !description && "invisible")}
|
||||||
>
|
>
|
||||||
{description || "\u00A0"}
|
{description || "\u00A0"}
|
||||||
</p>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormMessage id={errorId} />
|
<FormMessage id={errorId} />
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { type VariantProps, cva } from "class-variance-authority";
|
import { type VariantProps, cva } from "class-variance-authority";
|
||||||
import { CheckIcon, ChevronDown, WandSparkles, XCircle } from "lucide-react";
|
import { CheckIcon, ChevronDown, WandSparkles, XCircleIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -219,7 +219,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
|||||||
{...props}
|
{...props}
|
||||||
onClick={handleTogglePopover}
|
onClick={handleTogglePopover}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
|
"flex w-full -mt-0.5 px-1 py-0.5 rounded-md border min-h-8 h-auto items-center justify-between bg-background hover:bg-inherit [&_svg]:pointer-events-auto",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -260,7 +260,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
|||||||
style={{ animationDuration: `${animation}s` }}
|
style={{ animationDuration: `${animation}s` }}
|
||||||
>
|
>
|
||||||
{`+ ${selectedValues.length - maxCount} more`}
|
{`+ ${selectedValues.length - maxCount} more`}
|
||||||
<XCircle
|
<XCircleIcon
|
||||||
className='ml-2 h-4 w-4 cursor-pointer'
|
className='ml-2 h-4 w-4 cursor-pointer'
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
@ -1,33 +1,31 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
useFormState,
|
|
||||||
type ControllerProps,
|
type ControllerProps,
|
||||||
type FieldPath,
|
type FieldPath,
|
||||||
type FieldValues,
|
type FieldValues,
|
||||||
} from "react-hook-form"
|
FormProvider,
|
||||||
|
useFormContext,
|
||||||
|
useFormState,
|
||||||
|
} from "react-hook-form";
|
||||||
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils"
|
import { Label } from "@repo/shadcn-ui/components/label";
|
||||||
import { Label } from "@repo/shadcn-ui/components/label"
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider;
|
||||||
|
|
||||||
type FormFieldContextValue<
|
type FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
> = {
|
> = {
|
||||||
name: TName
|
name: TName;
|
||||||
}
|
};
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
||||||
{} as FormFieldContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
@ -39,21 +37,21 @@ const FormField = <
|
|||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
<Controller {...props} />
|
<Controller {...props} />
|
||||||
</FormFieldContext.Provider>
|
</FormFieldContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const useFormField = () => {
|
const useFormField = () => {
|
||||||
const fieldContext = React.useContext(FormFieldContext)
|
const fieldContext = React.useContext(FormFieldContext);
|
||||||
const itemContext = React.useContext(FormItemContext)
|
const itemContext = React.useContext(FormItemContext);
|
||||||
const { getFieldState } = useFormContext()
|
const { getFieldState } = useFormContext();
|
||||||
const formState = useFormState({ name: fieldContext.name })
|
const formState = useFormState({ name: fieldContext.name });
|
||||||
const fieldState = getFieldState(fieldContext.name, formState)
|
const fieldState = getFieldState(fieldContext.name, formState);
|
||||||
|
|
||||||
if (!fieldContext) {
|
if (!fieldContext) {
|
||||||
throw new Error("useFormField should be used within <FormField>")
|
throw new Error("useFormField should be used within <FormField>");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = itemContext
|
const { id } = itemContext;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@ -62,106 +60,93 @@ const useFormField = () => {
|
|||||||
formDescriptionId: `${id}-form-item-description`,
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
formMessageId: `${id}-form-item-message`,
|
formMessageId: `${id}-form-item-message`,
|
||||||
...fieldState,
|
...fieldState,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
type FormItemContextValue = {
|
type FormItemContextValue = {
|
||||||
id: string
|
id: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
||||||
{} as FormItemContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const id = React.useId()
|
const id = React.useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItemContext.Provider value={{ id }}>
|
<FormItemContext.Provider value={{ id }}>
|
||||||
<div
|
<div data-slot='form-item' className={cn("grid gap-2", className)} {...props} />
|
||||||
data-slot="form-item"
|
|
||||||
className={cn("grid gap-2", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</FormItemContext.Provider>
|
</FormItemContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormLabel({
|
function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||||
className,
|
const { error, formItemId } = useFormField();
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
||||||
const { error, formItemId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
data-slot="form-label"
|
data-slot='form-label'
|
||||||
data-error={!!error}
|
data-error={!!error}
|
||||||
className={cn("data-[error=true]:text-destructive", className)}
|
className={cn("data-[error=true]:text-destructive", className)}
|
||||||
htmlFor={formItemId}
|
htmlFor={formItemId}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
data-slot="form-control"
|
data-slot='form-control'
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
const { formDescriptionId } = useFormField()
|
const { formDescriptionId } = useFormField();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
data-slot="form-description"
|
data-slot='form-description'
|
||||||
id={formDescriptionId}
|
id={formDescriptionId}
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
const { error, formMessageId } = useFormField()
|
const { error, formMessageId } = useFormField();
|
||||||
const body = error ? String(error?.message ?? "") : props.children
|
const body = error ? String(error?.message ?? "") : props.children;
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
data-slot="form-message"
|
data-slot='form-message'
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn("text-destructive text-sm", className)}
|
className={cn("text-destructive text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</p>
|
</p>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useFormField,
|
|
||||||
Form,
|
Form,
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
FormDescription,
|
||||||
FormMessage,
|
|
||||||
FormField,
|
FormField,
|
||||||
}
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
useFormField,
|
||||||
|
};
|
||||||
|
|||||||
@ -41,6 +41,9 @@ importers:
|
|||||||
'@erp/customers':
|
'@erp/customers':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../modules/customers
|
version: link:../../modules/customers
|
||||||
|
'@erp/verifactu':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../modules/verifactu
|
||||||
'@repo/rdx-logger':
|
'@repo/rdx-logger':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-logger
|
version: link:../../packages/rdx-logger
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user