Facturas de cliente

This commit is contained in:
David Arranz 2025-07-09 19:56:15 +02:00
parent a5af168e6b
commit 1d1f412e6c
21 changed files with 500 additions and 98 deletions

View File

@ -57,6 +57,11 @@
"placeholder": "Select a date",
"description": "Invoice issue date"
},
"invoice_series": {
"label": "Serie",
"placeholder": "",
"description": ""
},
"operation_date": {
"label": "Operation date",
"placeholder": "Select a date",

View File

@ -57,6 +57,11 @@
"placeholder": "Seleccionar una fecha",
"description": "Fecha de emisión de la factura"
},
"invoice_series": {
"label": "Serie",
"placeholder": "",
"description": ""
},
"operation_date": {
"label": "Intervención",
"placeholder": "Seleccionar una fecha",

View File

@ -1,8 +1,7 @@
import { Button } from "@repo/shadcn-ui/components";
import { PlusCircleIcon } from "lucide-react";
import { JSX, forwardRef } from "react";
import { useTranslation } from "react-i18next";
import { MODULE_NAME } from "../../manifest";
import { useTranslation } from "../../i18n";
export interface AppendEmptyRowButtonProps extends React.ComponentProps<typeof Button> {
label?: string;
@ -11,7 +10,7 @@ export interface AppendEmptyRowButtonProps extends React.ComponentProps<typeof B
export const AppendEmptyRowButton = forwardRef<HTMLButtonElement, AppendEmptyRowButtonProps>(
({ label, className, ...props }: AppendEmptyRowButtonProps, ref): JSX.Element => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const _label = label || t("common.append_empty_row");
return (

View File

@ -1,8 +1,7 @@
import { Badge } from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { forwardRef } from "react";
import { useTranslation } from "react-i18next";
import { MODULE_NAME } from "../manifest";
import { useTranslation } from "../i18n";
export type CustomerInvoiceStatus = "draft" | "emitted" | "sent" | "received" | "rejected";
@ -42,7 +41,7 @@ export const CustomerInvoiceStatusBadge = forwardRef<
HTMLDivElement,
CustomerInvoiceStatusBadgeProps
>(({ status, className, ...props }, ref) => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const normalizedStatus = status.toLowerCase() as CustomerInvoiceStatus;
const config = statusColorConfig[normalizedStatus];
const commonClassName = "transition-colors duration-200 cursor-pointer shadow-none rounded-full";

View File

@ -11,14 +11,12 @@ import { MoneyDTO } from "@erp/core";
import { formatDate, formatMoney } from "@erp/core/client";
// Core CSS
import { AgGridReact } from "ag-grid-react";
import { useTranslation } from "react-i18next";
import { useCustomerInvoicesQuery } from "../hooks";
import { MODULE_NAME } from "../manifest";
import { CustomerInvoiceStatusBadge } from "./customer-invoice-status-badge";
// Create new GridExample component
export const CustomerInvoicesListGrid = () => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const { data, isLoading, isPending, isError, error } = useCustomerInvoicesQuery({});
// Column Definitions: Defines & controls grid columns.

View File

@ -1,19 +1,12 @@
import {
FormControl,
FormField,
FormItem,
FormMessage,
Input,
Textarea,
} from "@repo/shadcn-ui/components";
import { FormControl, FormField, FormItem, FormMessage, Input } from "@repo/shadcn-ui/components";
import { TextAreaField } from "@repo/rdx-ui/components";
import { ColumnDef } from "@tanstack/react-table";
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
import { useState } from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useDetailColumns } from "../../hooks";
import { MODULE_NAME } from "../../manifest";
import { useTranslation } from "../../i18n";
import { formatCurrency } from "../../pages/create/utils";
import {
CustomerInvoiceItemsSortableDataTable,
@ -29,7 +22,7 @@ export const CustomerInvoiceItemsCardEditor = ({
//language: Language;
defaultValues: Readonly<{ [x: string]: any }> | undefined;
}) => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const { control, watch, getValues } = useFormContext();
@ -78,20 +71,11 @@ export const CustomerInvoiceItemsCardEditor = ({
accessorKey: "description",
header: t("form_fields.items.description.label"),
cell: ({ row: { index, original } }) => (
<FormField
<TextAreaField
control={control}
name={`items.${index}.description`}
render={({ field }) => (
<FormItem className='md:col-span-2'>
<FormControl>
<Textarea
placeholder={t("form_fields.items.description.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
placeholder={t("form_fields.items.description.placeholder")}
className='md:col-span-2'
/>
),
minSize: 200,

View File

@ -0,0 +1,28 @@
import { useEffect } from "react";
import { useTranslation as useI18NextTranslation } from "react-i18next";
import enResources from "../common/locales/en.json";
import esResources from "../common/locales/es.json";
import { MODULE_NAME } from "./manifest";
const addMissingBundles = (i18n: any) => {
const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME);
const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME);
if (needsEn) {
i18n.addResourceBundle("en", MODULE_NAME, enResources, true, true);
}
if (needsEs) {
i18n.addResourceBundle("es", MODULE_NAME, esResources, true, true);
}
};
export const useTranslation = () => {
const { i18n } = useI18NextTranslation();
useEffect(() => {
addMissingBundles(i18n);
}, [i18n]);
return useI18NextTranslation(MODULE_NAME);
};

View File

@ -1,13 +1,12 @@
import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
import { Button } from "@repo/shadcn-ui/components";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { useCreateCustomerInvoiceMutation } from "../../hooks";
import { MODULE_NAME } from "../../manifest";
import { useTranslation } from "../../i18n";
import { CustomerInvoiceEditForm } from "./customer-invoice-edit-form";
export const CustomerInvoiceCreate = () => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const navigate = useNavigate();
const { mutate, isPending, isError, error } = useCreateCustomerInvoiceMutation();

View File

@ -4,7 +4,7 @@ import * as z from "zod";
import { ClientSelector } from "@erp/customers/components";
import { DatePickerField } from "@repo/rdx-ui/components";
import { DatePickerInputField, TextAreaField, TextField } from "@repo/rdx-ui/components";
import {
Button,
Calendar,
@ -35,9 +35,8 @@ import {
import { format } from "date-fns";
import { es } from "date-fns/locale";
import { CalendarIcon, PlusIcon, Save, Trash2Icon, X } from "lucide-react";
import { useTranslation } from "react-i18next";
import { CustomerInvoiceItemsCardEditor } from "../../components/items";
import { MODULE_NAME } from "../../manifest";
import { useTranslation } from "../../i18n";
import { CustomerInvoiceData } from "./customer-invoice.schema";
import { formatCurrency } from "./utils";
@ -219,7 +218,7 @@ export const CustomerInvoiceEditForm = ({
onSubmit,
isPending,
}: InvoiceFormProps) => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const form = useForm<CustomerInvoiceData>({
resolver: zodResolver(invoiceSchema),
@ -271,18 +270,13 @@ export const CustomerInvoiceEditForm = ({
</CardHeader>
<CardContent className='grid grid-cols-1 gap-4 space-y-6'>
<ClientSelector />
<FormField
<TextField
control={form.control}
name='customer_id'
render={({ field }) => (
<FormItem>
<FormLabel>ID Cliente</FormLabel>
<FormControl>
<Input placeholder='ID del cliente' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
required
label={t("form_fields.customer_id.label")}
placeholder={t("form_fields.customer_id.placeholder")}
description={t("form_fields.customer_id.description")}
/>
</CardContent>
</Card>
@ -293,22 +287,19 @@ export const CustomerInvoiceEditForm = ({
<CardTitle>Información Básica</CardTitle>
<CardDescription>Detalles generales de la factura</CardDescription>
</CardHeader>
<CardContent className='grid gap-6 md:grid-cols-6'>
<FormField
<CardContent className='grid gap-y-6 gap-x-8 md:grid-cols-4'>
<TextField
control={form.control}
name='invoice_number'
render={({ field }) => (
<FormItem>
<FormLabel>{t("form_fields.invoice_number.label")}</FormLabel>
<FormControl>
<Input placeholder={t("form_fields.invoice_number.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
required
disabled
readOnly
label={t("form_fields.invoice_number.label")}
placeholder={t("form_fields.invoice_number.placeholder")}
description={t("form_fields.invoice_number.description")}
/>
<DatePickerField
<DatePickerInputField
control={form.control}
name='issue_date'
required
@ -317,18 +308,13 @@ export const CustomerInvoiceEditForm = ({
description={t("form_fields.issue_date.description")}
/>
<FormField
<TextField
control={form.control}
name='invoice_series'
render={({ field }) => (
<FormItem>
<FormLabel>Serie</FormLabel>
<FormControl>
<Input placeholder='A' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
required
label={t("form_fields.invoice_series.label")}
placeholder={t("form_fields.invoice_series.placeholder")}
description={t("form_fields.invoice_series.description")}
/>
</CardContent>
</Card>
@ -391,6 +377,14 @@ export const CustomerInvoiceEditForm = ({
)}
/>
<TextAreaField
control={form.control}
name={`items.${index}.description`}
label={t("form_fields.items.description.label")}
placeholder={t("form_fields.items.description.placeholder")}
description={t("form_fields.items.description.description")}
/>
<FormField
control={form.control}
name={`items.${index}.quantity.amount`}

View File

@ -2,13 +2,12 @@ import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
import { Button } from "@repo/shadcn-ui/components";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { CustomerInvoicesListGrid } from "../components";
import { MODULE_NAME } from "../manifest";
import { useTranslation } from "../i18n";
export const CustomerInvoicesList = () => {
const { t } = useTranslation(MODULE_NAME);
const { t } = useTranslation();
const navigate = useNavigate();
const [status, setStatus] = useState("all");

View File

@ -1,3 +1,4 @@
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
import {
Button,
DropdownMenu,
@ -9,7 +10,6 @@ import {
DropdownMenuTrigger,
} from "@repo/shadcn-ui/components";
import { CellContext } from "@tanstack/react-table";
import { t } from "i18next";
import { MoreVerticalIcon } from "lucide-react";
import { ReactElement } from "react";
@ -34,6 +34,8 @@ export function DataTableRowActions<TData = any, TValue = unknown>({
actions,
rowContext,
}: DataTableRowActionsProps<TData, TValue>) {
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>

View File

@ -12,21 +12,22 @@ import {
PopoverContent,
PopoverTrigger,
} from "@repo/shadcn-ui/components";
import { CalendarIcon } from "lucide-react";
import { CalendarIcon, LockIcon } from "lucide-react";
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { format } from "date-fns";
import { Control, FieldPath, FieldValues } from "react-hook-form";
import { useTranslation } from "../../locales/i18n.ts";
type DatePickerFieldProps<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: FieldPath<TFormValues>;
label: string;
label?: string;
placeholder?: string;
description?: string;
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
className?: string;
formatDateFn?: (iso: string) => string;
};
@ -39,32 +40,43 @@ export function DatePickerField<TFormValues extends FieldValues>({
description,
disabled = false,
required = false,
readOnly = false,
className,
formatDateFn = (iso) => format(new Date(iso), "dd/MM/yyyy"),
}: DatePickerFieldProps<TFormValues>) {
const { t } = useTranslation();
const isDisabled = disabled || readOnly;
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem className={cn("space-y-2", className)}>
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
</div>{" "}
{label && (
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
</div>
)}
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant='outline'
disabled={disabled}
disabled={isDisabled}
className={cn(
"w-full justify-start text-left font-normal",
!field.value && "text-muted-foreground"
!field.value && "text-muted-foreground",
disabled && "bg-muted text-muted-foreground cursor-not-allowed",
readOnly && !disabled && "bg-muted text-foreground cursor-default"
)}
>
<CalendarIcon className='mr-2 h-4 w-4' />
{readOnly ? (
<LockIcon className='mr-2 h-4 w-4 opacity-70' />
) : (
<CalendarIcon className='mr-2 h-4 w-4' />
)}
{field.value ? formatDateFn(field.value) : placeholder}
</Button>
</FormControl>
@ -73,11 +85,16 @@ export function DatePickerField<TFormValues extends FieldValues>({
<Calendar
mode='single'
selected={field.value ? new Date(field.value) : undefined}
onSelect={(date) => field.onChange(date?.toISOString())}
onSelect={(date) => {
if (!readOnly) {
field.onChange(date?.toISOString());
}
}}
initialFocus
/>
</PopoverContent>
</Popover>
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
{description || "\u00A0"}
</p>

View File

@ -0,0 +1,155 @@
import {
Calendar,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Popover,
PopoverContent,
PopoverTrigger,
} from "@repo/shadcn-ui/components";
import { CalendarIcon, LockIcon } from "lucide-react";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { format, isValid, parse } from "date-fns";
import { useState } from "react";
import { Control, FieldPath, FieldValues } from "react-hook-form";
import { useTranslation } from "../../locales/i18n.ts";
type DatePickerInputFieldProps<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: FieldPath<TFormValues>;
label: string;
placeholder?: string;
description?: string;
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
className?: string;
formatDateFn?: (iso: string) => string;
parseDateFormat?: string; // e.g. "dd/MM/yyyy"
};
export function DatePickerInputField<TFormValues extends FieldValues>({
control,
name,
label,
placeholder,
description,
disabled = false,
required = false,
readOnly = false,
className,
formatDateFn = (iso) => format(new Date(iso), "dd/MM/yyyy"),
parseDateFormat = "dd/MM/yyyy",
}: DatePickerInputFieldProps<TFormValues>) {
const { t } = useTranslation();
const isDisabled = disabled;
const isReadOnly = readOnly && !disabled;
return (
<FormField
control={control}
name={name}
render={({ field }) => {
const [inputValue, setInputValue] = useState<string>(
field.value ? formatDateFn(field.value) : ""
);
const [inputError, setInputError] = useState<string | null>(null);
const handleInputChange = (value: string) => {
setInputValue(value);
setInputError(null); // Reset error on typing
};
const validateAndSetDate = () => {
const parsed = parse(inputValue, parseDateFormat, new Date());
if (isValid(parsed)) {
field.onChange(parsed.toISOString());
setInputError(null);
} else {
setInputError(t("common.invalid_date") || "Fecha no válida");
}
};
return (
<FormItem className={cn("space-y-2", className)}>
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
</div>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<div className='relative'>
<input
type='text'
value={inputValue}
onChange={(e) => handleInputChange(e.target.value)}
onBlur={validateAndSetDate}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
validateAndSetDate();
}
}}
readOnly={isReadOnly}
disabled={isDisabled}
className={cn(
"w-full rounded-md border px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring",
isDisabled && "bg-muted text-muted-foreground cursor-not-allowed",
isReadOnly && "bg-muted text-foreground cursor-default",
!isDisabled && !isReadOnly && "bg-white text-foreground",
inputError && "border-destructive ring-destructive"
)}
placeholder={placeholder}
/>
<div className='absolute inset-y-0 right-2 flex items-center pointer-events-none'>
{isReadOnly ? (
<LockIcon className='h-4 w-4 text-muted-foreground' />
) : (
<CalendarIcon className='h-4 w-4 text-muted-foreground' />
)}
</div>
</div>
</FormControl>
</PopoverTrigger>
{!isDisabled && !isReadOnly && (
<PopoverContent className='w-auto p-0'>
<Calendar
mode='single'
selected={field.value ? new Date(field.value) : undefined}
onSelect={(date) => {
if (date) {
const iso = date.toISOString();
field.onChange(iso);
setInputValue(formatDateFn(iso));
setInputError(null);
}
}}
initialFocus
/>
</PopoverContent>
)}
</Popover>
<p
className={cn(
"text-xs",
inputError ? "text-destructive" : "text-muted-foreground",
!description && !inputError && "invisible"
)}
>
{inputError || description || "\u00A0"}
</p>
<FormMessage />
</FormItem>
);
}}
/>
);
}

View File

@ -0,0 +1,64 @@
// DatePickerField.tsx
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
} from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { Control, FieldPath, FieldValues } from "react-hook-form";
import { useTranslation } from "../../locales/i18n.ts";
type NumberFieldProps<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: FieldPath<TFormValues>;
label: string;
placeholder?: string;
description?: string;
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
className?: string;
};
export function NumberField<TFormValues extends FieldValues>({
control,
name,
label,
placeholder,
description,
disabled = false,
required = false,
readOnly = false,
className,
}: NumberFieldProps<TFormValues>) {
const { t } = useTranslation();
const isDisabled = disabled || readOnly;
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem className={cn("space-y-2", className)}>
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
</div>
<FormControl>
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
</FormControl>
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
{description || "\u00A0"}
</p>
<FormMessage />
</FormItem>
)}
/>
);
}

View File

@ -0,0 +1,66 @@
// DatePickerField.tsx
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Textarea,
} from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { Control, FieldPath, FieldValues } from "react-hook-form";
import { useTranslation } from "../../locales/i18n.ts";
type TextAreaFieldProps<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: FieldPath<TFormValues>;
label?: string;
placeholder?: string;
description?: string;
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
className?: string;
};
export function TextAreaField<TFormValues extends FieldValues>({
control,
name,
label,
placeholder,
description,
disabled = false,
required = false,
readOnly = false,
className,
}: TextAreaFieldProps<TFormValues>) {
const { t } = useTranslation();
const isDisabled = disabled || readOnly;
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem className={cn("space-y-2", className)}>
{label && (
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
</div>
)}
<FormControl>
<Textarea disabled={isDisabled} placeholder={placeholder} {...field} />
</FormControl>
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
{description || "\u00A0"}
</p>
<FormMessage />
</FormItem>
)}
/>
);
}

View File

@ -0,0 +1,66 @@
// DatePickerField.tsx
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
} from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { Control, FieldPath, FieldValues } from "react-hook-form";
import { useTranslation } from "../../locales/i18n.ts";
type TextFieldProps<TFormValues extends FieldValues> = {
control: Control<TFormValues>;
name: FieldPath<TFormValues>;
label?: string;
placeholder?: string;
description?: string;
disabled?: boolean;
required?: boolean;
readOnly?: boolean;
className?: string;
};
export function TextField<TFormValues extends FieldValues>({
control,
name,
label,
placeholder,
description,
disabled = false,
required = false,
readOnly = false,
className,
}: TextFieldProps<TFormValues>) {
const { t } = useTranslation();
const isDisabled = disabled || readOnly;
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem className={cn("space-y-2", className)}>
{label && (
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
</div>
)}
<FormControl>
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
</FormControl>
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
{description || "\u00A0"}
</p>
<FormMessage />
</FormItem>
)}
/>
);
}

View File

@ -1 +1,4 @@
export * from "./DatePickerField.tsx";
export * from "./DatePickerInputField.tsx";
export * from "./TextAreaField.tsx";
export * from "./TextField.tsx";

View File

@ -1,5 +1,7 @@
{
"common": {
"actions": "Actions",
"invalid_date": "Invalid date",
"required": "required"
},
"components": {

View File

@ -1,5 +1,7 @@
{
"common": {
"actions": "Actions",
"invalid_date": "Fecha incorrecta o no válida",
"required": "obligatorio"
},
"components": {

View File

@ -1,14 +1,28 @@
import i18next from "i18next";
import { useTranslation as useTrans } from "react-i18next";
import { useEffect } from "react";
import { useTranslation as useI18NextTranslation } from "react-i18next";
import { PACKAGE_NAME } from "../index.ts";
import enResources from "./en.json" with { type: "json" };
import esResources from "./es.json" with { type: "json" };
export const useTranslation = () => {
if (!i18next.hasLoadedNamespace(PACKAGE_NAME)) {
i18next.addResourceBundle("en", PACKAGE_NAME, enResources, true, true);
i18next.addResourceBundle("es", PACKAGE_NAME, esResources, true, true);
const addMissingBundles = (i18n: any) => {
const needsEn = !i18n.hasResourceBundle("en", PACKAGE_NAME);
const needsEs = !i18n.hasResourceBundle("es", PACKAGE_NAME);
if (needsEn) {
i18n.addResourceBundle("en", PACKAGE_NAME, enResources, true, true);
}
return useTrans(PACKAGE_NAME);
if (needsEs) {
i18n.addResourceBundle("es", PACKAGE_NAME, esResources, true, true);
}
};
export const useTranslation = () => {
const { i18n } = useI18NextTranslation();
useEffect(() => {
addMissingBundles(i18n);
}, [i18n]);
return useI18NextTranslation(PACKAGE_NAME);
};

View File

@ -1,13 +1,14 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@repo/shadcn-ui/lib/utils"
import { cn } from "@repo/shadcn-ui/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
data-slot='input'
className={cn(
"bg-background text-foreground",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@ -15,7 +16,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
)}
{...props}
/>
)
);
}
export { Input }
export { Input };