Compare commits
2 Commits
8d0c0b88de
...
327756413d
| Author | SHA1 | Date | |
|---|---|---|---|
| 327756413d | |||
| 7e700bdf22 |
@ -45,6 +45,7 @@
|
||||
"@erp/core": "workspace:*",
|
||||
"@erp/customer-invoices": "workspace:*",
|
||||
"@erp/customers": "workspace:*",
|
||||
"@erp/verifactu": "workspace:*",
|
||||
"@repo/rdx-logger": "workspace:*",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cls-rtracer": "^2.6.3",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
|
||||
import customersAPIModule from "@erp/customers/api";
|
||||
import verifactuAPIModule from "@erp/verifactu/api";
|
||||
//import verifactuAPIModule from "@erp/verifactu/api";
|
||||
|
||||
import { registerModule } from "./lib";
|
||||
|
||||
@ -8,5 +8,5 @@ export const registerModules = () => {
|
||||
//registerModule(authAPIModule);
|
||||
registerModule(customersAPIModule);
|
||||
registerModule(customerInvoicesAPIModule);
|
||||
registerModule(verifactuAPIModule);
|
||||
//registerModule(verifactuAPIModule);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
"common": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"required": "•"
|
||||
},
|
||||
"components": {
|
||||
|
||||
@ -47,7 +47,7 @@ export function TaxesMultiSelectField<TFormValues extends FieldValues>({
|
||||
render={({ field }) => (
|
||||
<FormItem className={cn("space-y-0", className)}>
|
||||
{label && (
|
||||
<div className='mb-1 flex justify-between gap-2'>
|
||||
<div className='mb-1 flex justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<FormLabel htmlFor={name} className='m-0'>
|
||||
{label}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { XIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -34,7 +35,6 @@ export const CancelFormButton = ({
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const ok = requestConfirm ? await requestConfirm() : true;
|
||||
console.log("ok => ", ok);
|
||||
if (!ok) return;
|
||||
|
||||
if (onCancel) {
|
||||
@ -43,7 +43,6 @@ export const CancelFormButton = ({
|
||||
}
|
||||
|
||||
if (to) {
|
||||
console.log("navego => ", to);
|
||||
navigate(to);
|
||||
}
|
||||
// si no hay ni onCancel ni to → no hace nada
|
||||
@ -60,6 +59,7 @@ export const CancelFormButton = ({
|
||||
aria-disabled={disabled}
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
<XIcon className='mr-2 h-3 w-3' />
|
||||
<span>{label ?? defaultLabel}</span>
|
||||
</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 {
|
||||
ArrowLeftIcon,
|
||||
CopyIcon,
|
||||
EyeIcon,
|
||||
MoreHorizontalIcon,
|
||||
RotateCcwIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { CancelFormButton, CancelFormButtonProps } from "./cancel-form-button";
|
||||
import { SubmitButtonProps, SubmitFormButton } from "./submit-form-button";
|
||||
|
||||
type Align = "start" | "center" | "end" | "between";
|
||||
|
||||
type GroupSubmitButtonProps = Omit<SubmitButtonProps, "isLoading" | "preventDoubleSubmit">;
|
||||
|
||||
export type FormCommitButtonGroupProps = {
|
||||
className?: string;
|
||||
align?: Align; // default "end"
|
||||
gap?: string; // default "gap-2"
|
||||
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 };
|
||||
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> = {
|
||||
@ -25,10 +55,33 @@ export const FormCommitButtonGroup = ({
|
||||
align = "end",
|
||||
gap = "gap-2",
|
||||
reverseOrderOnMobile = true,
|
||||
|
||||
isLoading,
|
||||
disabled = false,
|
||||
preventDoubleSubmit = true,
|
||||
|
||||
cancel,
|
||||
submit,
|
||||
|
||||
onReset,
|
||||
onDelete,
|
||||
onPreview,
|
||||
onDuplicate,
|
||||
onBack,
|
||||
}: FormCommitButtonGroupProps) => {
|
||||
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 (
|
||||
<div
|
||||
@ -40,8 +93,62 @@ export const FormCommitButtonGroup = ({
|
||||
className
|
||||
)}
|
||||
>
|
||||
{showCancel && <CancelFormButton {...cancel} />}
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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 { useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "../../../i18n.ts";
|
||||
@ -8,12 +9,15 @@ export type SubmitButtonProps = {
|
||||
formId?: string;
|
||||
isLoading?: boolean;
|
||||
label?: string;
|
||||
labelIsLoading?: string;
|
||||
|
||||
variant?: React.ComponentProps<typeof Button>["variant"];
|
||||
size?: React.ComponentProps<typeof Button>["size"];
|
||||
className?: string;
|
||||
|
||||
preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading
|
||||
hasChanges?: boolean;
|
||||
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
@ -24,10 +28,12 @@ export const SubmitFormButton = ({
|
||||
formId,
|
||||
isLoading,
|
||||
label,
|
||||
labelIsLoading,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
className,
|
||||
preventDoubleSubmit = true,
|
||||
hasChanges = false,
|
||||
onClick,
|
||||
disabled,
|
||||
children,
|
||||
@ -35,6 +41,7 @@ export const SubmitFormButton = ({
|
||||
}: SubmitButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultLabel = t ? t("common.save") : "Save";
|
||||
const defaultLabelIsLoading = t ? t("common.saving") : "Saving...";
|
||||
|
||||
// ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading
|
||||
let rhfIsSubmitting = false;
|
||||
@ -65,20 +72,30 @@ export const SubmitFormButton = ({
|
||||
form={formId}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={className}
|
||||
disabled={computedDisabled}
|
||||
aria-busy={busy}
|
||||
aria-disabled={computedDisabled}
|
||||
data-state={dataState}
|
||||
onClick={handleClick}
|
||||
data-testid={dataTestId}
|
||||
className={cn("min-w-[100px] font-medium", hasChanges && "ring-2 ring-primary/20", className)}
|
||||
>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className='inline-flex items-center gap-2'>
|
||||
{busy && <LoaderCircleIcon className='h-4 w-4 animate-spin' aria-hidden='true' />}
|
||||
<span>{label ?? defaultLabel}</span>
|
||||
{busy && (
|
||||
<>
|
||||
<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>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
|
||||
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
|
||||
import { ModuleParams, validateRequest } from "@erp/core/api";
|
||||
import { ILogger } from "@repo/rdx-logger";
|
||||
import { Application, NextFunction, Request, Response, Router } from "express";
|
||||
import { Sequelize } from "sequelize";
|
||||
import {
|
||||
@ -115,5 +116,5 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
||||
}
|
||||
);
|
||||
|
||||
app.use(`${baseRoutePath}/proforma-invoices`, router);
|
||||
app.use(`${baseRoutePath}/customer-invoices`, router);
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@ import { useMemo, useState } from "react";
|
||||
|
||||
import { MoneyDTO } from "@erp/core";
|
||||
import { formatDate, formatMoney } from "@erp/core/client";
|
||||
import { ErrorOverlay } from "@repo/rdx-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
@ -23,7 +24,6 @@ ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
// Create new GridExample component
|
||||
export const CustomerInvoicesListGrid = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
@ -141,6 +141,19 @@ export const CustomerInvoicesListGrid = () => {
|
||||
[autoSizeStrategy, colDefs]
|
||||
);
|
||||
|
||||
if (isLoadError) {
|
||||
return (
|
||||
<>
|
||||
<ErrorOverlay
|
||||
errorMessage={
|
||||
(loadError as Error)?.message ??
|
||||
t("pages.update.loadErrorMsg", "Inténtalo de nuevo más tarde.")
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Container: Defines the grid's theme & dimensions.
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -15,25 +15,6 @@ const CustomerInvoiceAdd = lazy(() =>
|
||||
import("./pages").then((m) => ({ default: m.CustomerInvoiceCreate }))
|
||||
);
|
||||
|
||||
//const LogoutPage = lazy(() => import("./app").then((m) => ({ default: m.LogoutPage })));
|
||||
|
||||
/*const DealerLayout = lazy(() => import("./app").then((m) => ({ default: m.DealerLayout })));
|
||||
const DealersList = lazy(() => import("./app").then((m) => ({ default: m.DealersList })));
|
||||
|
||||
const LoginPageWithLanguageSelector = lazy(() =>
|
||||
import("./app").then((m) => ({ default: m.LoginPageWithLanguageSelector }))
|
||||
);
|
||||
|
||||
|
||||
const CustomerInvoiceEdit = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoiceEdit })));
|
||||
const SettingsEditor = lazy(() => import("./app").then((m) => ({ default: m.SettingsEditor })));
|
||||
const SettingsLayout = lazy(() => import("./app").then((m) => ({ default: m.SettingsLayout })));
|
||||
const CatalogLayout = lazy(() => import("./app").then((m) => ({ default: m.CatalogLayout })));
|
||||
const CatalogList = lazy(() => import("./app").then((m) => ({ default: m.CatalogList })));
|
||||
const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.DashboardPage })));
|
||||
const CustomerInvoicesLayout = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoicesLayout })));
|
||||
const CustomerInvoicesList = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoicesList })));*/
|
||||
|
||||
export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
return [
|
||||
{
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
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 { useNavigate } from "react-router-dom";
|
||||
import { CustomerInvoicesListGrid } from "../components";
|
||||
import { useTranslation } from "../i18n";
|
||||
@ -9,17 +8,6 @@ import { useTranslation } from "../i18n";
|
||||
export const CustomerInvoicesList = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [status, setStatus] = useState("all");
|
||||
|
||||
/*const CustomerInvoiceStatuses = [
|
||||
{ value: "all", label: t("customerInvoices.list.tabs.all") },
|
||||
{ value: "draft", label: t("customerInvoices.list.tabs.draft") },
|
||||
{ value: "ready", label: t("customerInvoices.list.tabs.ready") },
|
||||
{ value: "delivered", label: t("customerInvoices.list.tabs.delivered") },
|
||||
{ value: "accepted", label: t("customerInvoices.list.tabs.accepted") },
|
||||
{ value: "rejected", label: t("customerInvoices.list.tabs.rejected") },
|
||||
{ value: "archived", label: t("customerInvoices.list.tabs.archived") },
|
||||
];*/
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./create";
|
||||
export * from "./list";
|
||||
export * from "./customer-invoices-list";
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
//import * as z from "zod/v4";
|
||||
|
||||
import { ListCustomerInvoicesResponseDTO } from "@erp/customer-invoices/common";
|
||||
|
||||
/*export const CustomerCreateSchema = CreateCustomerRequestSchema;
|
||||
export const CustomerUpdateSchema = UpdateCustomerByIdRequestSchema;
|
||||
export const CustomerSchema = GetCustomerByIdResponseSchema.omit({
|
||||
metadata: true,
|
||||
});
|
||||
|
||||
export type CustomerData = z.infer<typeof CustomerSchema>;*/
|
||||
|
||||
export type CustomerInvoicesListData = ListCustomerInvoicesResponseDTO;
|
||||
@ -0,0 +1 @@
|
||||
|
||||
2
modules/customer-invoices/src/web/schemas/index.ts
Normal file
2
modules/customer-invoices/src/web/schemas/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./customer-invoices.api.schema";
|
||||
export * from "./customer-invoices.form.schema";
|
||||
@ -1,6 +1,7 @@
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerListDTO } from "../../infrastructure/mappers";
|
||||
import { Customer } from "../aggregates";
|
||||
|
||||
/**
|
||||
@ -42,7 +43,7 @@ export interface ICustomerRepository {
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: any
|
||||
): Promise<Result<Collection<CustomerListDTO>>>;
|
||||
): Promise<Result<Collection<CustomerListDTO>, Error>>;
|
||||
|
||||
/**
|
||||
* Elimina un Customer por su ID, dentro de una empresa.
|
||||
|
||||
@ -14,10 +14,10 @@ export class CustomerAddressType extends ValueObject<ICustomerAddressTypeProps>
|
||||
private static readonly ALLOWED_TYPES = ["shipping", "billing"];
|
||||
|
||||
static create(value: string): Result<CustomerAddressType, Error> {
|
||||
if (!this.ALLOWED_TYPES.includes(value)) {
|
||||
if (!CustomerAddressType.ALLOWED_TYPES.includes(value)) {
|
||||
return Result.fail(
|
||||
new Error(
|
||||
`Invalid address type: ${value}. Allowed types are: ${this.ALLOWED_TYPES.join(", ")}`
|
||||
`Invalid address type: ${value}. Allowed types are: ${CustomerAddressType.ALLOWED_TYPES.join(", ")}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DomainValidationError } from "@erp/core/api";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { DomainValidationError, ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DomainValidationError } from "@erp/core/api";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { DomainValidationError, ValueObject } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DomainValidationError } from "@erp/core/api";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { DomainValidationError, ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
interface ICustomerStatusProps {
|
||||
|
||||
@ -5,6 +5,12 @@
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"catalog": {
|
||||
"status": {
|
||||
"active": "active",
|
||||
"inactive": "inactive"
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"title": "Customers",
|
||||
"description": "Manage your customers",
|
||||
|
||||
@ -5,6 +5,12 @@
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar"
|
||||
},
|
||||
"catalog": {
|
||||
"status": {
|
||||
"active": "activo",
|
||||
"inactive": "inactivo"
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"title": "Clientes",
|
||||
"description": "Gestiona tus clientes",
|
||||
@ -96,6 +102,7 @@
|
||||
"placeholder": "Ingrese el correo electrónico",
|
||||
"description": "La dirección de correo electrónico principal del cliente"
|
||||
},
|
||||
|
||||
"email_secondary": {
|
||||
"label": "Email secundario",
|
||||
"placeholder": "Ingrese el correo electrónico",
|
||||
@ -107,6 +114,7 @@
|
||||
"placeholder": "Ingrese el número de teléfono",
|
||||
"description": "El número de teléfono del cliente"
|
||||
},
|
||||
|
||||
"phone_secondary": {
|
||||
"label": "Teléfono secundario",
|
||||
"placeholder": "Ingrese el número de teléfono secundario",
|
||||
@ -118,6 +126,7 @@
|
||||
"placeholder": "Ingrese el número de teléfono",
|
||||
"description": "El número de teléfono del cliente"
|
||||
},
|
||||
|
||||
"mobile_secondary": {
|
||||
"label": "Teléfono secundario",
|
||||
"placeholder": "Ingrese el número de teléfono secundario",
|
||||
@ -129,21 +138,25 @@
|
||||
"placeholder": "Ingrese el número de fax",
|
||||
"description": "El número de fax del cliente"
|
||||
},
|
||||
|
||||
"website": {
|
||||
"label": "Sitio web",
|
||||
"placeholder": "Ingrese la URL del sitio web",
|
||||
"description": "El sitio web del cliente"
|
||||
},
|
||||
|
||||
"default_taxes": {
|
||||
"label": "Impuesto por defecto",
|
||||
"placeholder": "Seleccione el impuesto por defecto",
|
||||
"description": "La tasa de impuesto por defecto para el cliente"
|
||||
},
|
||||
|
||||
"language_code": {
|
||||
"label": "Idioma",
|
||||
"placeholder": "Seleccione el idioma",
|
||||
"description": "El idioma preferido del cliente"
|
||||
},
|
||||
|
||||
"currency_code": {
|
||||
"label": "Moneda",
|
||||
"placeholder": "Seleccione la moneda",
|
||||
|
||||
@ -42,7 +42,7 @@ export const CustomerStatusBadge = forwardRef<HTMLDivElement, CustomerStatusBadg
|
||||
return (
|
||||
<Badge className={cn(commonClassName, config.badge, className)} {...props}>
|
||||
<div className={cn("h-1.5 w-1.5 rounded-full mr-2", config.dot)} />
|
||||
{t(`status.${status}`)}
|
||||
{t(`catalog.status.${status}`)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ export const CustomersListGrid = () => {
|
||||
{
|
||||
field: "status",
|
||||
headerName: t("pages.list.grid_columns.status"),
|
||||
maxWidth: 125,
|
||||
maxWidth: 135,
|
||||
cellRenderer: (params: ValueFormatterParams) => {
|
||||
return <CustomerStatusBadge status={params.value} />;
|
||||
},
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
import { Description, Field, FieldGroup, Fieldset, Legend } 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 { CURRENCY_OPTIONS, LANGUAGE_OPTIONS } from "../../constants";
|
||||
import { useTranslation } from "../../i18n";
|
||||
@ -16,15 +11,12 @@ export const CustomerAdditionalConfigFields = () => {
|
||||
const { control } = useFormContext<CustomerFormData>();
|
||||
|
||||
return (
|
||||
<Card className='border-0 shadow-none'>
|
||||
<CardHeader>
|
||||
<CardTitle>{t("form_groups.preferences.title")}</CardTitle>
|
||||
<CardDescription>{t("form_groups.preferences.description")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-1 gap-8 lg:grid-cols-4 mb-12 '>
|
||||
<Fieldset>
|
||||
<Legend>{t("form_groups.preferences.title")}</Legend>
|
||||
<Description>{t("form_groups.preferences.description")}</Description>
|
||||
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||
<Field className='lg:col-span-2'>
|
||||
<SelectField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
name='language_code'
|
||||
required
|
||||
@ -33,6 +25,8 @@ export const CustomerAdditionalConfigFields = () => {
|
||||
description={t("form_fields.language_code.description")}
|
||||
items={[...LANGUAGE_OPTIONS]}
|
||||
/>
|
||||
</Field>
|
||||
<Field className='lg:col-span-2'>
|
||||
<SelectField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
@ -43,8 +37,8 @@ export const CustomerAdditionalConfigFields = () => {
|
||||
description={t("form_fields.currency_code.description")}
|
||||
items={[...CURRENCY_OPTIONS]}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Fieldset>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
import {
|
||||
Description,
|
||||
Field,
|
||||
FieldGroup,
|
||||
Fieldset,
|
||||
Legend,
|
||||
SelectField,
|
||||
TextField,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { COUNTRY_OPTIONS } from "../../constants";
|
||||
import { useTranslation } from "../../i18n";
|
||||
@ -26,7 +20,7 @@ export const CustomerAddressFields = () => {
|
||||
<Fieldset>
|
||||
<Legend>{t("form_groups.address.title")}</Legend>
|
||||
<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
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
@ -60,77 +54,16 @@ export const CustomerAddressFields = () => {
|
||||
description={t("form_fields.postal_code.description")}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
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 '>
|
||||
<Field className='lg:col-span-2 lg:col-start-1'>
|
||||
<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}
|
||||
name='province'
|
||||
label={t("form_fields.province.label")}
|
||||
placeholder={t("form_fields.province.placeholder")}
|
||||
description={t("form_fields.province.description")}
|
||||
/>
|
||||
</Field>
|
||||
<Field className='lg:col-span-2'>
|
||||
<SelectField
|
||||
control={control}
|
||||
name='country'
|
||||
@ -140,8 +73,8 @@ export const CustomerAddressFields = () => {
|
||||
description={t("form_fields.country.description")}
|
||||
items={[...COUNTRY_OPTIONS]}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Fieldset>
|
||||
);
|
||||
};
|
||||
|
||||
@ -33,9 +33,9 @@ export const CustomerBasicInfoFields = () => {
|
||||
|
||||
return (
|
||||
<Fieldset>
|
||||
<Legend>Identificación</Legend>
|
||||
<Description>descripción</Description>
|
||||
<FieldGroup className='grid grid-cols-1 gap-8 lg:grid-cols-4'>
|
||||
<Legend>{t("form_groups.basic_info.title")}</Legend>
|
||||
<Description>{t("form_groups.basic_info.description")}</Description>
|
||||
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||
<Field className='lg:col-span-2'>
|
||||
<TextField
|
||||
control={control}
|
||||
@ -59,7 +59,7 @@ export const CustomerBasicInfoFields = () => {
|
||||
field.onChange(value === "false" ? "false" : "true");
|
||||
}}
|
||||
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'>
|
||||
<FormControl>
|
||||
@ -106,16 +106,16 @@ export const CustomerBasicInfoFields = () => {
|
||||
placeholder={t("form_fields.reference.placeholder")}
|
||||
description={t("form_fields.reference.description")}
|
||||
/>
|
||||
<TaxesMultiSelectField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
name='default_taxes'
|
||||
required
|
||||
label={t("form_fields.default_taxes.label")}
|
||||
placeholder={t("form_fields.default_taxes.placeholder")}
|
||||
description={t("form_fields.default_taxes.description")}
|
||||
/>
|
||||
|
||||
<Field className='lg:col-span-2'>
|
||||
<TaxesMultiSelectField
|
||||
control={control}
|
||||
name='default_taxes'
|
||||
required
|
||||
label={t("form_fields.default_taxes.label")}
|
||||
placeholder={t("form_fields.default_taxes.placeholder")}
|
||||
description={t("form_fields.default_taxes.description")}
|
||||
/>
|
||||
</Field>
|
||||
<TextAreaField
|
||||
className='lg:col-span-full'
|
||||
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 { ChevronDown, MailIcon, PhoneIcon, SmartphoneIcon } from "lucide-react";
|
||||
@ -15,7 +22,7 @@ export const CustomerContactFields = () => {
|
||||
<Fieldset>
|
||||
<Legend>{t("form_groups.contact_info.title")}</Legend>
|
||||
<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
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
@ -27,6 +34,7 @@ export const CustomerContactFields = () => {
|
||||
typePreset='email'
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className='lg:col-span-2'
|
||||
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'>
|
||||
{t("common.more_details")}{" "}
|
||||
<ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<FieldGroup className='grid grid-cols-1 gap-8 lg:grid-cols-4'>
|
||||
<TextField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
name='website'
|
||||
label={t("form_fields.website.label")}
|
||||
placeholder={t("form_fields.website.placeholder")}
|
||||
description={t("form_fields.website.description")}
|
||||
/>
|
||||
<TextField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
name='fax'
|
||||
label={t("form_fields.fax.label")}
|
||||
placeholder={t("form_fields.fax.placeholder")}
|
||||
description={t("form_fields.fax.description")}
|
||||
/>
|
||||
<FieldGroup className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
||||
<Field className='lg:col-span-2'>
|
||||
<TextField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
name='website'
|
||||
label={t("form_fields.website.label")}
|
||||
placeholder={t("form_fields.website.placeholder")}
|
||||
description={t("form_fields.website.description")}
|
||||
/>
|
||||
</Field>
|
||||
<Field className='lg:col-span-2'>
|
||||
<TextField
|
||||
className='lg:col-span-2'
|
||||
control={control}
|
||||
name='fax'
|
||||
label={t("form_fields.fax.label")}
|
||||
placeholder={t("form_fields.fax.placeholder")}
|
||||
description={t("form_fields.fax.description")}
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
@ -22,7 +22,7 @@ export const CustomerEditForm = ({ formId, onSubmit, onError }: CustomerFormProp
|
||||
<div className='w-full xl:w-6/12'>
|
||||
<FormDebug />
|
||||
</div>
|
||||
<div className='w-full xl:grow'>
|
||||
<div className='w-full xl:grow space-y-6'>
|
||||
<CustomerBasicInfoFields />
|
||||
<CustomerContactFields />
|
||||
<CustomerAddressFields />
|
||||
|
||||
@ -12,25 +12,6 @@ const CustomersList = lazy(() => import("./pages").then((m) => ({ default: m.Cus
|
||||
|
||||
const CustomerAdd = lazy(() => import("./pages").then((m) => ({ default: m.CustomerCreate })));
|
||||
|
||||
//const LogoutPage = lazy(() => import("./app").then((m) => ({ default: m.LogoutPage })));
|
||||
|
||||
/*const DealerLayout = lazy(() => import("./app").then((m) => ({ default: m.DealerLayout })));
|
||||
const DealersList = lazy(() => import("./app").then((m) => ({ default: m.DealersList })));
|
||||
|
||||
const LoginPageWithLanguageSelector = lazy(() =>
|
||||
import("./app").then((m) => ({ default: m.LoginPageWithLanguageSelector }))
|
||||
);
|
||||
|
||||
|
||||
const CustomerEdit = lazy(() => import("./app").then((m) => ({ default: m.CustomerEdit })));
|
||||
const SettingsEditor = lazy(() => import("./app").then((m) => ({ default: m.SettingsEditor })));
|
||||
const SettingsLayout = lazy(() => import("./app").then((m) => ({ default: m.SettingsLayout })));
|
||||
const CatalogLayout = lazy(() => import("./app").then((m) => ({ default: m.CatalogLayout })));
|
||||
const CatalogList = lazy(() => import("./app").then((m) => ({ default: m.CatalogList })));
|
||||
const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.DashboardPage })));
|
||||
const CustomersLayout = lazy(() => import("./app").then((m) => ({ default: m.CustomersLayout })));
|
||||
const CustomersList = lazy(() => import("./app").then((m) => ({ default: m.CustomersList })));*/
|
||||
|
||||
export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
return [
|
||||
{
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useDataSource, useQueryKey } from "@erp/core/hooks";
|
||||
import { ListCustomersResponseDTO } from "@erp/customer-invoices/common";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { CustomersListData } from "../schemas";
|
||||
|
||||
// Obtener todas las facturas
|
||||
export const useCustomersQuery = (params?: any) => {
|
||||
const dataSource = useDataSource();
|
||||
const keys = useQueryKey();
|
||||
|
||||
return useQuery<ListCustomersResponseDTO>({
|
||||
return useQuery<CustomersListData>({
|
||||
queryKey: keys().data().resource("customers").action("list").params(params).get(),
|
||||
queryFn: async (context) => {
|
||||
const { signal } = context;
|
||||
@ -16,7 +16,7 @@ export const useCustomersQuery = (params?: any) => {
|
||||
...params,
|
||||
});
|
||||
|
||||
return customers as ListCustomersResponseDTO;
|
||||
return customers as CustomersListData;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -54,12 +54,16 @@ export const CustomerCreate = () => {
|
||||
// Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBreadcrumb />
|
||||
<AppContent>
|
||||
<UnsavedChangesProvider isDirty={form.formState.isDirty}>
|
||||
<div className='flex items-center justify-between space-y-4 px-6'>
|
||||
<div className='flex items-center justify-between space-y-6'>
|
||||
<div className='space-y-2'>
|
||||
<h2 className='text-2xl font-bold tracking-tight text-balance scroll-m-2'>
|
||||
{t("pages.create.title")}
|
||||
@ -69,14 +73,17 @@ export const CustomerCreate = () => {
|
||||
</p>
|
||||
</div>
|
||||
<FormCommitButtonGroup
|
||||
isLoading={isCreating}
|
||||
disabled={isCreating}
|
||||
cancel={{
|
||||
to: "/customers/list",
|
||||
disabled: isCreating,
|
||||
}}
|
||||
submit={{
|
||||
formId: "customer-create-form",
|
||||
disabled: isCreating,
|
||||
isLoading: isCreating,
|
||||
}}
|
||||
onBack={() => handleBack()}
|
||||
/>
|
||||
</div>
|
||||
{/* Alerta de error de actualización (si ha fallado el último intento) */}
|
||||
@ -90,15 +97,13 @@ export const CustomerCreate = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='flex flex-1 flex-col gap-4 p-4'>
|
||||
<FormProvider {...form}>
|
||||
<CustomerEditForm
|
||||
formId='customer-create-form'
|
||||
onSubmit={handleSubmit}
|
||||
onError={handleError}
|
||||
/>
|
||||
</FormProvider>
|
||||
</div>
|
||||
<FormProvider {...form}>
|
||||
<CustomerEditForm
|
||||
formId='customer-create-form'
|
||||
onSubmit={handleSubmit}
|
||||
onError={handleError}
|
||||
/>
|
||||
</FormProvider>
|
||||
</UnsavedChangesProvider>
|
||||
</AppContent>
|
||||
</>
|
||||
|
||||
@ -13,7 +13,7 @@ export const CustomersList = () => {
|
||||
<>
|
||||
<AppBreadcrumb />
|
||||
<AppContent>
|
||||
<div className='flex items-center justify-between space-y-2'>
|
||||
<div className='flex items-center justify-between space-y-6'>
|
||||
<div>
|
||||
<h2 className='text-2xl font-bold tracking-tight'>{t("pages.list.title")}</h2>
|
||||
<p className='text-muted-foreground'>{t("pages.list.description")}</p>
|
||||
@ -21,7 +21,7 @@ export const CustomersList = () => {
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button onClick={() => navigate("/customers/create")} className='cursor-pointer'>
|
||||
<PlusIcon className='w-4 h-4 mr-2' />
|
||||
{t("pages.create.title")}
|
||||
{t("pages.list.title")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./create";
|
||||
export * from "./list";
|
||||
export * from "./customer-list";
|
||||
|
||||
@ -67,6 +67,12 @@ export const CustomerUpdate = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleReset = () => form.reset(customerData ?? defaultCustomerFormData);
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const handleError = (errors: FieldErrors<CustomerFormData>) => {
|
||||
console.error("Errores en el formulario:", errors);
|
||||
// Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario
|
||||
@ -115,7 +121,7 @@ export const CustomerUpdate = () => {
|
||||
<AppBreadcrumb />
|
||||
<AppContent>
|
||||
<UnsavedChangesProvider isDirty={form.formState.isDirty}>
|
||||
<div className='flex items-center justify-between space-y-4 px-6'>
|
||||
<div className='flex items-center justify-between space-y-6'>
|
||||
<div className='space-y-2'>
|
||||
<h2 className='text-2xl font-bold tracking-tight text-balance scroll-m-2'>
|
||||
{t("pages.update.title")}
|
||||
@ -125,14 +131,18 @@ export const CustomerUpdate = () => {
|
||||
</p>
|
||||
</div>
|
||||
<FormCommitButtonGroup
|
||||
isLoading={isUpdating}
|
||||
disabled={isUpdating}
|
||||
cancel={{
|
||||
to: "/customers/list",
|
||||
disabled: isUpdating,
|
||||
}}
|
||||
submit={{
|
||||
formId: "customer-update-form",
|
||||
disabled: isUpdating,
|
||||
isLoading: isUpdating,
|
||||
}}
|
||||
onBack={() => handleBack()}
|
||||
onReset={() => handleReset()}
|
||||
/>
|
||||
</div>
|
||||
{/* Alerta de error de actualización (si ha fallado el último intento) */}
|
||||
@ -146,15 +156,13 @@ export const CustomerUpdate = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='flex flex-1 flex-col gap-4 p-4'>
|
||||
<FormProvider {...form}>
|
||||
<CustomerEditForm
|
||||
formId={"customer-update-form"} // para que el botón del header pueda hacer submit
|
||||
onSubmit={handleSubmit}
|
||||
onError={handleError}
|
||||
/>
|
||||
</FormProvider>
|
||||
</div>
|
||||
<FormProvider {...form}>
|
||||
<CustomerEditForm
|
||||
formId={"customer-update-form"} // para que el botón del header pueda hacer submit
|
||||
onSubmit={handleSubmit}
|
||||
onError={handleError}
|
||||
/>
|
||||
</FormProvider>
|
||||
</UnsavedChangesProvider>
|
||||
</AppContent>
|
||||
</>
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import * as z from "zod/v4";
|
||||
import { CustomerFormSchema } from "./customer.form.schema";
|
||||
|
||||
export const UpdateCustomerFormSchema = CustomerFormSchema.extend({
|
||||
is_company: CustomerFormSchema.shape.is_company.optional(),
|
||||
name: CustomerFormSchema.shape.name.optional(),
|
||||
default_taxes: z.array(z.string()).optional(),
|
||||
|
||||
country: CustomerFormSchema.shape.country.optional(),
|
||||
|
||||
language_code: CustomerFormSchema.shape.language_code.optional(),
|
||||
currency_code: CustomerFormSchema.shape.currency_code.optional(),
|
||||
});
|
||||
|
||||
export type UpdateCustomerFormData = z.infer<typeof UpdateCustomerFormSchema>;
|
||||
@ -3,6 +3,7 @@ import * as z from "zod/v4";
|
||||
import {
|
||||
CreateCustomerRequestSchema,
|
||||
GetCustomerByIdResponseSchema,
|
||||
ListCustomersResponseDTO,
|
||||
UpdateCustomerByIdRequestSchema,
|
||||
} from "@erp/customers";
|
||||
|
||||
@ -13,3 +14,5 @@ export const CustomerSchema = GetCustomerByIdResponseSchema.omit({
|
||||
});
|
||||
|
||||
export type CustomerData = z.infer<typeof CustomerSchema>;
|
||||
|
||||
export type CustomersListData = ListCustomersResponseDTO;
|
||||
|
||||
@ -3,12 +3,14 @@
|
||||
"version": "0.0.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"exports": {},
|
||||
"exports": { "./api": "./src/api/index.ts" },
|
||||
"peerDependencies": {
|
||||
"sequelize": "^6.37.5"
|
||||
"sequelize": "^6.37.5",
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"devDependencies": { "@types/express": "^4.17.21" },
|
||||
"dependencies": {
|
||||
"@erp/auth": "workspace:*",
|
||||
"@erp/core": "workspace:*",
|
||||
"@repo/rdx-ddd": "workspace:*",
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
|
||||
@ -8,6 +8,7 @@ export const verifactuAPIModule: IModuleServer = {
|
||||
|
||||
async init(params: ModuleParams) {
|
||||
// const contacts = getService<ContactsService>("contacts");
|
||||
console.log("111111111111111111111111111A>>>>>>>>>>>>>>>>>>>");
|
||||
const { logger } = params;
|
||||
verifactuRouter(params);
|
||||
logger.info("🚀 Verifactu module initialized", { label: this.name });
|
||||
|
||||
@ -14,6 +14,7 @@ export const verifactuRouter = (params: ModuleParams) => {
|
||||
logger: ILogger;
|
||||
};
|
||||
|
||||
console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>>>>>>>>>>>>>>>>>>>");
|
||||
const deps = buildVerifactuDependencies(params);
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
//export * from "./mappers";
|
||||
//export * from "./sequelize";
|
||||
export * from "./express";
|
||||
export * from "./sequelize";
|
||||
|
||||
@ -20,7 +20,7 @@ interface ErrorOverlayProps {
|
||||
|
||||
export const ErrorOverlay = ({
|
||||
title = "Se ha producido un error",
|
||||
subtitle = undefined,
|
||||
subtitle = "Inténtalo de nuevo más tarde",
|
||||
description = undefined,
|
||||
errorMessage = undefined,
|
||||
}: //errorStatusCode = undefined,
|
||||
@ -32,7 +32,7 @@ ErrorOverlayProps): JSX.Element => {
|
||||
: _DrawByStatusCode['0'];*/
|
||||
|
||||
return (
|
||||
<div className='grid h-screen place-items-center '>
|
||||
<div className='h-fit place-items-center-safe mt-10 '>
|
||||
<div className='text-center'>
|
||||
<h2 className='mt-2 text-xl font-semibold text-center text-slate-900'>{title}</h2>
|
||||
<p className='mt-1 font-medium text-slate-500'>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
Calendar,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@ -166,16 +167,9 @@ export function DatePickerInputField<TFormValues extends FieldValues>({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{(inputError || description) && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-xs mt-1",
|
||||
inputError ? "text-destructive" : "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{inputError || description}
|
||||
</p>
|
||||
)}
|
||||
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||
{description || "\u00A0"}
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@ -53,9 +54,9 @@ export function NumberField<TFormValues extends FieldValues>({
|
||||
<Input disabled={isDisabled} placeholder={placeholder} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
||||
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||
{description || "\u00A0"}
|
||||
</p>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@ -55,7 +55,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
||||
render={({ field }) => (
|
||||
<FormItem className={cn("space-y-0", className)}>
|
||||
{label && (
|
||||
<div className='mb-1 flex justify-between gap-2'>
|
||||
<div className='mb-1 flex justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<FormLabel htmlFor={name} className='m-0'>
|
||||
{label}
|
||||
@ -72,7 +72,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
||||
)}
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={isDisabled}>
|
||||
<FormControl>
|
||||
<SelectTrigger className='w-full'>
|
||||
<SelectTrigger className='w-full bg-background h-8'>
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
@ -85,9 +85,7 @@ export function SelectField<TFormValues extends FieldValues>({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<FormDescription
|
||||
className={cn("text-xs text-muted-foreground", !description && "invisible")}
|
||||
>
|
||||
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||
{description || "\u00A0"}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@ -58,12 +59,17 @@ export function TextAreaField<TFormValues extends FieldValues>({
|
||||
</div>
|
||||
)}
|
||||
<FormControl>
|
||||
<Textarea disabled={isDisabled} placeholder={placeholder} {...field} />
|
||||
<Textarea
|
||||
disabled={isDisabled}
|
||||
placeholder={placeholder}
|
||||
className={"bg-background"}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<p className={cn("text-xs text-muted-foreground", !description && "invisible")}>
|
||||
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
||||
{description || "\u00A0"}
|
||||
</p>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@ -393,7 +394,7 @@ export function TextField<TFormValues extends FieldValues>({
|
||||
maxLength={maxLength}
|
||||
{...rest}
|
||||
className={cn(
|
||||
"placeholder:font-normal placeholder:italic",
|
||||
"placeholder:font-normal placeholder:italic bg-background",
|
||||
inputPadding,
|
||||
invalid && "border-destructive focus-visible:ring-destructive",
|
||||
valid && showSuccessWhenValid && "border-green-500 focus-visible:ring-green-500",
|
||||
@ -448,12 +449,12 @@ export function TextField<TFormValues extends FieldValues>({
|
||||
</FormControl>
|
||||
|
||||
<div className='mt-1 flex items-start justify-between'>
|
||||
<p
|
||||
<FormDescription
|
||||
id={describedById}
|
||||
className={cn("text-xs text-muted-foreground", !description && "invisible")}
|
||||
className={cn("text-xs truncate", !description && "invisible")}
|
||||
>
|
||||
{description || "\u00A0"}
|
||||
</p>
|
||||
</FormDescription>
|
||||
|
||||
{showCounter && typeof maxLength === "number" && (
|
||||
<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">) => (
|
||||
<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}
|
||||
>
|
||||
{children}
|
||||
@ -12,7 +15,7 @@ export const Fieldset = ({ className, children, ...props }: React.ComponentProps
|
||||
);
|
||||
|
||||
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}
|
||||
</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 "./DatePickerInputField.tsx";
|
||||
export * from "./fieldset.tsx";
|
||||
export * from "./form-content.tsx";
|
||||
export * from "./multi-select-field.tsx";
|
||||
export * from "./SelectField.tsx";
|
||||
export * from "./TextAreaField.tsx";
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
@ -438,12 +439,12 @@ export const MultiSelectFieldInner = React.forwardRef(
|
||||
</FormControl>
|
||||
|
||||
<div className='mt-1 flex items-start justify-between'>
|
||||
<p
|
||||
<FormDescription
|
||||
id={describedById}
|
||||
className={cn("text-xs text-muted-foreground", !description && "invisible")}
|
||||
className={cn("text-xs truncate", !description && "invisible")}
|
||||
>
|
||||
{description || "\u00A0"}
|
||||
</p>
|
||||
</FormDescription>
|
||||
</div>
|
||||
|
||||
<FormMessage id={errorId} />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 {
|
||||
@ -219,7 +219,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
||||
{...props}
|
||||
onClick={handleTogglePopover}
|
||||
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
|
||||
)}
|
||||
>
|
||||
@ -260,7 +260,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
||||
style={{ animationDuration: `${animation}s` }}
|
||||
>
|
||||
{`+ ${selectedValues.length - maxCount} more`}
|
||||
<XCircle
|
||||
<XCircleIcon
|
||||
className='ml-2 h-4 w-4 cursor-pointer'
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
@ -1,33 +1,31 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
useFormState,
|
||||
type ControllerProps,
|
||||
type FieldPath,
|
||||
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<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
@ -39,21 +37,21 @@ const FormField = <
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState } = useFormContext()
|
||||
const formState = useFormState({ name: fieldContext.name })
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState } = useFormContext();
|
||||
const formState = useFormState({ name: fieldContext.name });
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
@ -62,106 +60,93 @@ const useFormField = () => {
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
||||
|
||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||
const id = React.useId()
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div
|
||||
data-slot="form-item"
|
||||
className={cn("grid gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
<div data-slot='form-item' className={cn("grid gap-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function FormLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
const { error, formItemId } = useFormField()
|
||||
function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
data-slot='form-label'
|
||||
data-error={!!error}
|
||||
className={cn("data-[error=true]:text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
data-slot="form-control"
|
||||
data-slot='form-control'
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { formDescriptionId } = useFormField()
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-description"
|
||||
data-slot='form-description'
|
||||
id={formDescriptionId}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message ?? "") : props.children
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message ?? "") : props.children;
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-message"
|
||||
data-slot='form-message'
|
||||
id={formMessageId}
|
||||
className={cn("text-destructive text-sm", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useFormField,
|
||||
};
|
||||
|
||||
@ -41,6 +41,9 @@ importers:
|
||||
'@erp/customers':
|
||||
specifier: workspace:*
|
||||
version: link:../../modules/customers
|
||||
'@erp/verifactu':
|
||||
specifier: workspace:*
|
||||
version: link:../../modules/verifactu
|
||||
'@repo/rdx-logger':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-logger
|
||||
@ -659,6 +662,9 @@ importers:
|
||||
|
||||
modules/verifactu:
|
||||
dependencies:
|
||||
'@erp/auth':
|
||||
specifier: workspace:*
|
||||
version: link:../auth
|
||||
'@erp/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
@ -671,9 +677,16 @@ importers:
|
||||
'@repo/rdx-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-utils
|
||||
express:
|
||||
specifier: ^4.18.2
|
||||
version: 4.21.2
|
||||
sequelize:
|
||||
specifier: ^6.37.5
|
||||
version: 6.37.7(mysql2@3.14.1)
|
||||
devDependencies:
|
||||
'@types/express':
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.23
|
||||
|
||||
packages/rdx-criteria:
|
||||
dependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user