From c260f6400758b19db1200f968e82dfeeeefc4542 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 23 Aug 2025 13:57:48 +0200 Subject: [PATCH] Facturas de cliente y clientes --- apps/web/package.json | 8 - apps/web/src/app.tsx | 7 +- apps/web/src/register-modules.tsx | 10 +- modules/auth/src/web/services/auth-service.ts | 2 +- modules/auth/src/web/services/me-service.ts | 2 +- modules/auth/src/web/services/user-service.ts | 2 +- modules/core/package.json | 22 +- .../express/middlewares/validate-request.ts | 4 +- modules/core/src/common/locales/en.json | 11 + modules/core/src/common/locales/es.json | 11 + modules/core/src/web/components/form/index.ts | 1 + .../form/taxes-multi-select-field.tsx | 69 +++ modules/core/src/web/components/index.ts | 2 + modules/core/src/web/components/index.tsx | 1 - .../web/components/taxes-multi-select.tsx} | 8 +- modules/core/src/web/hooks/index.ts | 1 + .../use-datasource}/datasource-context.tsx | 2 +- .../src/web/hooks/use-datasource/index.ts | 1 + .../src/web/hooks/use-datasource/use-list.ts | 2 +- modules/core/src/web/i18n.ts | 25 + modules/core/src/web/index.ts | 2 - modules/core/src/web/lib/data-source/index.ts | 1 - modules/core/src/web/manifest.ts | 20 + modules/customer-invoices/package.json | 17 +- .../assembler/list-invoices.assembler.ts | 4 +- .../list-customer-invoices.use-case.ts | 1 + .../customer-invoice-criteria-whitelist.ts | 167 ++++++ .../sequelize/customer-invoice-item.model.ts | 4 +- .../sequelize/customer-invoice.repository.ts | 33 ++ .../use-create-customer-invoice-mutation.ts | 8 +- .../web/hooks/use-customer-invoices-query.tsx | 2 +- .../src/web/hooks/use-customer-invoices.bak | 2 +- modules/customer-invoices/src/web/i18n.ts | 9 +- modules/customer-invoices/src/web/manifest.ts | 4 +- .../create/customer-invoice-edit-form.tsx | 4 +- .../pages/create/customer-invoice.schema.ts | 4 +- .../src/web/pages/{index.tsx => index.ts} | 0 modules/customers/package.json | 26 +- ...assembler.ts => get-customer.assembler.ts} | 20 +- .../assembler/list-customers.assembler.ts | 30 +- modules/customers/src/common/locales/en.json | 130 +++++ modules/customers/src/common/locales/es.json | 130 +++++ .../src/web/components/client-selector.tsx | 4 +- .../web/components/customer-prices-card.tsx | 88 --- .../web/components/customer-status-badge.tsx | 20 +- .../web/components/customers-list-grid.tsx | 27 +- modules/customers/src/web/components/index.ts | 2 + .../src/web/context/customers-context.tsx | 2 +- modules/customers/src/web/context/index.ts | 1 + modules/customers/src/web/globals.css | 17 - .../web/hooks/use-create-customer-mutation.ts | 6 +- .../src/web/hooks/use-customers-query.tsx | 2 +- .../customers/src/web/hooks/use-customers.bak | 2 +- modules/customers/src/web/i18n.ts | 9 +- modules/customers/src/web/manifest.ts | 10 +- .../customers/src/web/pages/create/create.tsx | 73 +++ .../web/pages/create/customer-edit-form.tsx | 319 +++++++++++ .../src/web/pages/create/customer.schema.ts | 6 + .../customers/src/web/pages/create/index.ts | 1 + .../customers/src/web/pages/create/utils.ts | 41 ++ modules/customers/src/web/pages/index.ts | 2 + modules/customers/src/web/pages/list.tsx | 34 ++ package.json | 8 +- .../src/components/form/SelectField.tsx | 84 +++ .../rdx-ui/src/components/form/TextField.tsx | 2 - packages/rdx-ui/src/components/form/index.tsx | 1 + .../loading-overlay/loading-indicator.tsx | 15 +- packages/rdx-ui/src/locales/en.json | 3 +- packages/rdx-ui/src/locales/es.json | 5 +- pnpm-lock.yaml | 535 +++++------------- 70 files changed, 1447 insertions(+), 681 deletions(-) create mode 100644 modules/core/src/common/locales/en.json create mode 100644 modules/core/src/common/locales/es.json create mode 100644 modules/core/src/web/components/form/index.ts create mode 100644 modules/core/src/web/components/form/taxes-multi-select-field.tsx create mode 100644 modules/core/src/web/components/index.ts delete mode 100644 modules/core/src/web/components/index.tsx rename modules/{customers/src/web/components/customer-taxes-multi-select.tsx => core/src/web/components/taxes-multi-select.tsx} (90%) rename modules/core/src/web/{lib/data-source => hooks/use-datasource}/datasource-context.tsx (88%) create mode 100644 modules/core/src/web/i18n.ts delete mode 100644 modules/core/src/web/index.ts create mode 100644 modules/core/src/web/manifest.ts create mode 100644 modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts rename modules/customer-invoices/src/web/pages/{index.tsx => index.ts} (100%) rename modules/customers/src/api/application/get-customer/assembler/{get-invoice.assembler.ts => get-customer.assembler.ts} (73%) delete mode 100644 modules/customers/src/web/components/customer-prices-card.tsx create mode 100644 modules/customers/src/web/context/index.ts create mode 100644 modules/customers/src/web/pages/create/create.tsx create mode 100644 modules/customers/src/web/pages/create/customer-edit-form.tsx create mode 100644 modules/customers/src/web/pages/create/customer.schema.ts create mode 100644 modules/customers/src/web/pages/create/index.ts create mode 100644 modules/customers/src/web/pages/create/utils.ts create mode 100644 modules/customers/src/web/pages/index.ts create mode 100644 modules/customers/src/web/pages/list.tsx create mode 100644 packages/rdx-ui/src/components/form/SelectField.tsx diff --git a/apps/web/package.json b/apps/web/package.json index 93d452b2..95328225 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,17 +14,12 @@ }, "devDependencies": { "@biomejs/biome": "1.9.4", - "@hookform/devtools": "^4.4.0", - "@repo/typescript-config": "workspace:*", - "@tailwindcss/postcss": "^4.1.5", "@tanstack/react-query-devtools": "^5.74.11", "@types/dinero.js": "^1.9.4", "@types/node": "^22.15.12", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.3", "@vitejs/plugin-react": "^4.4.1", - "autoprefixer": "^10.4.20", - "globals": "^16.0.0", "typescript": "~5.8.3", "vite": "^6.3.5" }, @@ -33,7 +28,6 @@ "@erp/core": "workspace:*", "@erp/customer-invoices": "workspace:*", "@erp/customers": "workspace:*", - "@repo/rdx-criteria": "workspace:*", "@repo/rdx-ui": "workspace:*", "@repo/shadcn-ui": "workspace:*", "@tailwindcss/vite": "^4.1.11", @@ -45,12 +39,10 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.56.4", - "react-hook-form-persist": "^3.0.0", "react-i18next": "^15.0.1", "react-router-dom": "^6.26.0", "react-secure-storage": "^1.3.2", "sequelize": "^6.37.5", - "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.10", "tw-animate-css": "^1.2.9", "vite-plugin-html": "^3.2.2" diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index afbaa3e8..01d596ae 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -7,8 +7,8 @@ import { UnsavedWarnProvider } from "@/lib/hooks"; import { i18n } from "@/locales"; import { AuthProvider, createAuthService } from "@erp/auth/client"; -import { DataSourceProvider, createAxiosDataSource, createAxiosInstance } from "@erp/core/client"; - +import { createAxiosDataSource, createAxiosInstance } from "@erp/core/client"; +import { DataSourceProvider } from "@erp/core/hooks"; import DineroFactory from "dinero.js"; import "./app.css"; import { clearAccessToken, getAccessToken, setAccessToken } from "./lib"; @@ -30,8 +30,9 @@ export const App = () => { baseURL: import.meta.env.VITE_API_SERVER_URL, getAccessToken, onAuthError: () => { + console.error("Error de autenticación"); clearAccessToken(); - window.location.href = "/login"; // o usar navegación programática + //window.location.href = "/login"; // o usar navegación programática }, }); diff --git a/apps/web/src/register-modules.tsx b/apps/web/src/register-modules.tsx index 297d9852..67852b1a 100644 --- a/apps/web/src/register-modules.tsx +++ b/apps/web/src/register-modules.tsx @@ -1,5 +1,11 @@ import { AuthModuleManifiest } from "@erp/auth/client"; -import { IModuleClient } from "@erp/core/client"; +import CoreModuleManifiest, { IModuleClient } from "@erp/core/client"; import { CustomerInvoicesModuleManifiest } from "@erp/customer-invoices/client"; +import { CustomersModuleManifiest } from "@erp/customers/client"; -export const modules: IModuleClient[] = [AuthModuleManifiest, CustomerInvoicesModuleManifiest]; +export const modules: IModuleClient[] = [ + AuthModuleManifiest, + CoreModuleManifiest, + CustomersModuleManifiest, + CustomerInvoicesModuleManifiest, +]; diff --git a/modules/auth/src/web/services/auth-service.ts b/modules/auth/src/web/services/auth-service.ts index e2fcaa1c..eebc24bf 100644 --- a/modules/auth/src/web/services/auth-service.ts +++ b/modules/auth/src/web/services/auth-service.ts @@ -1,4 +1,4 @@ -import { useDataSource } from "@erp/core/client"; +import { useDataSource } from "@erp/core/hooks"; import { ILoginRequestDTO, ILoginResponseDTO } from "../../common"; export interface IAuthService { diff --git a/modules/auth/src/web/services/me-service.ts b/modules/auth/src/web/services/me-service.ts index d66efd03..d08a2bc8 100644 --- a/modules/auth/src/web/services/me-service.ts +++ b/modules/auth/src/web/services/me-service.ts @@ -1,4 +1,4 @@ -import { useDataSource } from "@erp/core/client"; +import { useDataSource } from "@erp/core/hooks"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../hooks"; import { User } from "./types"; diff --git a/modules/auth/src/web/services/user-service.ts b/modules/auth/src/web/services/user-service.ts index 35d488fa..e90980f4 100644 --- a/modules/auth/src/web/services/user-service.ts +++ b/modules/auth/src/web/services/user-service.ts @@ -1,4 +1,4 @@ -import { useDataSource } from "@erp/core/client"; +import { useDataSource } from "@erp/core/hooks"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { User } from "./types"; diff --git a/modules/core/package.json b/modules/core/package.json index c5a456b7..98b92ebb 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -4,32 +4,34 @@ "exports": { ".": "./src/common/index.ts", "./api": "./src/api/index.ts", - "./client": "./src/web/index.ts" + "./client": "./src/web/manifest.ts", + "./components": "./src/web/components/index.ts", + "./hooks": "./src/web/hooks/index.ts" }, "peerDependencies": { - "dinero.js": "^1.9.1" + "dinero.js": "^1.9.1", + "react": "^19.1.0" }, "devDependencies": { - "@biomejs/biome": "1.9.4", "@types/axios": "^0.14.4", "@types/dinero.js": "^1.9.4", "@types/express": "^4.17.21", - "@types/jest": "29.5.14", "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.3", - "ts-to-zod": "^3.15.0", - "typescript": "^5.8.3", - "zod-to-ts": "^1.2.0" + "typescript": "^5.8.3" }, "dependencies": { "@repo/rdx-criteria": "workspace:*", "@repo/rdx-ddd": "workspace:*", "@repo/rdx-utils": "workspace:*", + "@repo/rdx-ui": "workspace:*", + "@repo/shadcn-ui": "workspace:*", "@tanstack/react-query": "^5.75.4", "axios": "^1.9.0", + "express": "^4.18.2", "http-status": "^2.1.0", - "joi": "^17.13.3", - "libphonenumber-js": "^1.11.20", + "i18next": "^25.1.1", + "react-hook-form": "^7.58.1", + "react-i18next": "^15.5.1", "react-router-dom": "^6.26.0", "sequelize": "^6.37.5", "zod": "^3.25.67" diff --git a/modules/core/src/api/infrastructure/express/middlewares/validate-request.ts b/modules/core/src/api/infrastructure/express/middlewares/validate-request.ts index e2ed9ac3..5a1cd881 100644 --- a/modules/core/src/api/infrastructure/express/middlewares/validate-request.ts +++ b/modules/core/src/api/infrastructure/express/middlewares/validate-request.ts @@ -1,7 +1,7 @@ -// src/common/middlewares/validate-dto.ts -import { ExpressController, InternalApiError, ValidationApiError } from "@erp/core/api"; import { RequestHandler } from "express"; import { ZodSchema } from "zod/v4"; +import { InternalApiError, ValidationApiError } from "../../../errors"; +import { ExpressController } from "../express-controller"; /** * Middleware genérico para validar un objeto de Express diff --git a/modules/core/src/common/locales/en.json b/modules/core/src/common/locales/en.json new file mode 100644 index 00000000..80b90eb8 --- /dev/null +++ b/modules/core/src/common/locales/en.json @@ -0,0 +1,11 @@ +{ + "common": {}, + "components": { + "taxes_multi_select": { + "label": "Taxes", + "placeholder": "Select taxes", + "description": "Select the taxes to apply to the invoice items", + "invalid_tax_selection": "Invalid tax selection. Please select a valid tax." + } + } +} diff --git a/modules/core/src/common/locales/es.json b/modules/core/src/common/locales/es.json new file mode 100644 index 00000000..22dafa53 --- /dev/null +++ b/modules/core/src/common/locales/es.json @@ -0,0 +1,11 @@ +{ + "common": {}, + "components": { + "taxes_multi_select": { + "label": "Impuestos", + "placeholder": "Seleccionar impuestos", + "description": "Seleccionar los impuestos a aplicar a los artículos de la factura", + "invalid_tax_selection": "Selección de impuestos no válida. Por favor, seleccione un impuesto válido." + } + } +} diff --git a/modules/core/src/web/components/form/index.ts b/modules/core/src/web/components/form/index.ts new file mode 100644 index 00000000..90f1bc4a --- /dev/null +++ b/modules/core/src/web/components/form/index.ts @@ -0,0 +1 @@ +export * from "./taxes-multi-select-field"; diff --git a/modules/core/src/web/components/form/taxes-multi-select-field.tsx b/modules/core/src/web/components/form/taxes-multi-select-field.tsx new file mode 100644 index 00000000..a629de84 --- /dev/null +++ b/modules/core/src/web/components/form/taxes-multi-select-field.tsx @@ -0,0 +1,69 @@ +// DatePickerField.tsx + +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@repo/shadcn-ui/components"; + +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { Control, FieldPath, FieldValues } from "react-hook-form"; +import { useTranslation } from "../../i18n.ts"; +import { TaxesMultiSelect } from "../taxes-multi-select.tsx"; + +type TaxesMultiSelectFieldProps = { + control: Control; + name: FieldPath; + label?: string; + placeholder?: string; + description?: string; + disabled?: boolean; + required?: boolean; + readOnly?: boolean; + className?: string; +}; + +export function TaxesMultiSelectField({ + control, + name, + label, + placeholder, + description, + disabled = false, + required = false, + readOnly = false, + className, +}: TaxesMultiSelectFieldProps) { + const { t } = useTranslation(); + const isDisabled = disabled || readOnly; + + return ( + ( + + {label && ( +
+ {label} + {required && {t("common.required")}} +
+ )} + + + + + + {description || "\u00A0"} + + +
+ )} + /> + ); +} diff --git a/modules/core/src/web/components/index.ts b/modules/core/src/web/components/index.ts new file mode 100644 index 00000000..71b467ad --- /dev/null +++ b/modules/core/src/web/components/index.ts @@ -0,0 +1,2 @@ +export * from "./form"; +export * from "./taxes-multi-select"; diff --git a/modules/core/src/web/components/index.tsx b/modules/core/src/web/components/index.tsx deleted file mode 100644 index 8337712e..00000000 --- a/modules/core/src/web/components/index.tsx +++ /dev/null @@ -1 +0,0 @@ -// diff --git a/modules/customers/src/web/components/customer-taxes-multi-select.tsx b/modules/core/src/web/components/taxes-multi-select.tsx similarity index 90% rename from modules/customers/src/web/components/customer-taxes-multi-select.tsx rename to modules/core/src/web/components/taxes-multi-select.tsx index 2282ae5a..d7671e63 100644 --- a/modules/customers/src/web/components/customer-taxes-multi-select.tsx +++ b/modules/core/src/web/components/taxes-multi-select.tsx @@ -33,13 +33,13 @@ const taxesList = [ { label: "REC 0%", value: "rec_0", group: "Recargo de equivalencia" }, ]; -interface CustomerTaxesMultiSelect { +interface TaxesMultiSelect { value: string[]; onChange: (selectedValues: string[]) => void; [key: string]: any; // Allow other props to be passed } -export const CustomerTaxesMultiSelect = (props: CustomerTaxesMultiSelect) => { +export const TaxesMultiSelect = (props: TaxesMultiSelect) => { const { value, onChange, ...otherProps } = props; const { t } = useTranslation(); @@ -50,7 +50,7 @@ export const CustomerTaxesMultiSelect = (props: CustomerTaxesMultiSelect) => { const handleValidateOption = (candidateValue: string) => { const exists = (value || []).some((item) => item.startsWith(candidateValue.substring(0, 3))); if (exists) { - alert(t("components.customer_invoice_taxes_multi_select.invalid_tax_selection")); + alert(t("components.taxes_multi_select.invalid_tax_selection")); } return exists === false; }; @@ -62,7 +62,7 @@ export const CustomerTaxesMultiSelect = (props: CustomerTaxesMultiSelect) => { onValueChange={handleOnChange} onValidateOption={handleValidateOption} defaultValue={value} - placeholder={t("components.customer_invoice_taxes_multi_select.placeholder")} + placeholder={t("components.taxes_multi_select.placeholder")} variant='inverted' animation={0} maxCount={3} diff --git a/modules/core/src/web/hooks/index.ts b/modules/core/src/web/hooks/index.ts index a14f76fd..42765c58 100644 --- a/modules/core/src/web/hooks/index.ts +++ b/modules/core/src/web/hooks/index.ts @@ -1,3 +1,4 @@ +export * from "./use-datasource"; export * from "./use-pagination"; export * from "./use-query-key"; export * from "./use-toggle"; diff --git a/modules/core/src/web/lib/data-source/datasource-context.tsx b/modules/core/src/web/hooks/use-datasource/datasource-context.tsx similarity index 88% rename from modules/core/src/web/lib/data-source/datasource-context.tsx rename to modules/core/src/web/hooks/use-datasource/datasource-context.tsx index 3e2d8129..4a1dd6ea 100644 --- a/modules/core/src/web/lib/data-source/datasource-context.tsx +++ b/modules/core/src/web/hooks/use-datasource/datasource-context.tsx @@ -1,5 +1,5 @@ import { createContext, useContext } from "react"; -import { IDataSource } from "./datasource.interface"; +import { IDataSource } from "../../lib/data-source/datasource.interface"; const DataSourceContext = createContext(null); diff --git a/modules/core/src/web/hooks/use-datasource/index.ts b/modules/core/src/web/hooks/use-datasource/index.ts index e69de29b..8844e491 100644 --- a/modules/core/src/web/hooks/use-datasource/index.ts +++ b/modules/core/src/web/hooks/use-datasource/index.ts @@ -0,0 +1 @@ +export * from "./datasource-context"; diff --git a/modules/core/src/web/hooks/use-datasource/use-list.ts b/modules/core/src/web/hooks/use-datasource/use-list.ts index e6aa7890..95a109cd 100644 --- a/modules/core/src/web/hooks/use-datasource/use-list.ts +++ b/modules/core/src/web/hooks/use-datasource/use-list.ts @@ -7,7 +7,7 @@ import { useQuery, } from "@tanstack/react-query"; -import { isResponseAListDTO } from "@erp/core/common/dto"; +import { isResponseAListDTO } from "../../../common/dto"; import { UseLoadingOvertimeOptionsProps, UseLoadingOvertimeReturnType, diff --git a/modules/core/src/web/i18n.ts b/modules/core/src/web/i18n.ts new file mode 100644 index 00000000..109e9359 --- /dev/null +++ b/modules/core/src/web/i18n.ts @@ -0,0 +1,25 @@ +import { i18n } from "i18next"; +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: i18n) => { + 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(); + addMissingBundles(i18n); + + return useI18NextTranslation(MODULE_NAME); +}; diff --git a/modules/core/src/web/index.ts b/modules/core/src/web/index.ts deleted file mode 100644 index 86f26f6b..00000000 --- a/modules/core/src/web/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./hooks"; -export * from "./lib"; diff --git a/modules/core/src/web/lib/data-source/index.ts b/modules/core/src/web/lib/data-source/index.ts index bd310951..2f1724fa 100644 --- a/modules/core/src/web/lib/data-source/index.ts +++ b/modules/core/src/web/lib/data-source/index.ts @@ -1,4 +1,3 @@ export * from "./axios"; export * from "./build-text-filters"; -export * from "./datasource-context"; export * from "./datasource.interface"; diff --git a/modules/core/src/web/manifest.ts b/modules/core/src/web/manifest.ts new file mode 100644 index 00000000..7d3fd59a --- /dev/null +++ b/modules/core/src/web/manifest.ts @@ -0,0 +1,20 @@ +import { IModuleClient, ModuleClientParams } from "./lib"; + +export const MODULE_NAME = "Core"; +const MODULE_VERSION = "1.0.0"; + +export const CoreModuleManifiest: IModuleClient = { + name: MODULE_NAME, + version: MODULE_VERSION, + dependencies: ["core"], + protected: true, + layout: "app", + + routes: (params: ModuleClientParams) => { + return []; + }, +}; + +export default CoreModuleManifiest; + +export * from "./lib"; diff --git a/modules/customer-invoices/package.json b/modules/customer-invoices/package.json index 8d1de0ff..c59c6806 100644 --- a/modules/customer-invoices/package.json +++ b/modules/customer-invoices/package.json @@ -10,15 +10,17 @@ "./globals.css": "./src/web/globals.css" }, "peerDependencies": { + "@tanstack/react-query": "^5.74.11", "dinero.js": "^1.9.1", "express": "^4.18.2", + "i18next": "^25.1.1", + "react-hook-form": "^7.58.1", + "react-i18next": "^15.5.1", "sequelize": "^6.37.5", "zod": "^3.25.67" }, "devDependencies": { - "@biomejs/biome": "1.9.4", "@hookform/devtools": "^4.4.0", - "@types/dinero.js": "^1.9.4", "@types/express": "^4.17.21", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.3", @@ -39,21 +41,14 @@ "@repo/rdx-ui": "workspace:*", "@repo/rdx-utils": "workspace:*", "@repo/shadcn-ui": "workspace:*", - "@tanstack/react-query": "^5.74.11", "@tanstack/react-table": "^8.21.3", "ag-grid-community": "^33.3.0", "ag-grid-react": "^33.3.0", "date-fns": "^4.1.0", - "i18next": "^25.1.1", + "libphonenumber-js": "^1.12.7", "lucide-react": "^0.503.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-hook-form": "^7.58.1", - "react-i18next": "^15.5.1", - "react-router-dom": "^6.26.0", - "slugify": "^1.6.6", - "sonner": "^2.0.5", - "tailwindcss": "^4.1.11", - "tw-animate-css": "^1.3.4" + "react-router-dom": "^6.26.0" } } diff --git a/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts b/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts index 5660558a..60273b19 100644 --- a/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts +++ b/modules/customer-invoices/src/api/application/list-customer-invoices/assembler/list-invoices.assembler.ts @@ -1,7 +1,7 @@ -import { CustomerInvoice } from "@erp/customer-invoices/api/domain"; -import { CustomerInvoiceListResponseDTO } from "@erp/customer-invoices/common/dto"; import { Criteria } from "@repo/rdx-criteria/server"; import { Collection } from "@repo/rdx-utils"; +import { CustomerInvoiceListResponseDTO } from "../../../../common/dto"; +import { CustomerInvoice } from "../../../domain"; export class ListCustomerInvoicesAssembler { toDTO( diff --git a/modules/customer-invoices/src/api/application/list-customer-invoices/list-customer-invoices.use-case.ts b/modules/customer-invoices/src/api/application/list-customer-invoices/list-customer-invoices.use-case.ts index b05fb503..36e548cb 100644 --- a/modules/customer-invoices/src/api/application/list-customer-invoices/list-customer-invoices.use-case.ts +++ b/modules/customer-invoices/src/api/application/list-customer-invoices/list-customer-invoices.use-case.ts @@ -25,6 +25,7 @@ export class ListCustomerInvoicesUseCase { return this.transactionManager.complete(async (transaction: Transaction) => { try { + //const { rows, total, limit, offset } = await this.repo.searchInCompany(criteria, tenantId); const result = await this.customerInvoiceService.findByCriteria(criteria, transaction); if (result.isFailure) { diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts new file mode 100644 index 00000000..f50d8112 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-criteria-whitelist.ts @@ -0,0 +1,167 @@ +import type { Criteria } from "@repo/rdx-criteria/server"; +import { Op, OrderItem, WhereOptions, literal } from "sequelize"; + +// Campos físicos (DB) que permitimos filtrar/ordenar +const ALLOWED_FILTERS = { + id: "id", + customerId: "customer_id", + invoiceSeries: "invoice_series", + invoiceNumber: "invoice_number", + issueDate: "issue_date", + status: "status", + currencyCode: "currency_code", + // Rango por total (en unidades menores) + totalAmountValue: "total_amount_value", +} as const; + +const ALLOWED_SORT: Record = { + // Sort "issueDate" realmente ordena por (issue_date DESC, invoice_series ASC, invoice_number DESC, id DESC) + issueDate: ["issue_date"], + invoiceNumber: ["invoice_number"], + invoiceSeries: ["invoice_series"], + status: ["status"], + createdAt: ["created_at"], + updatedAt: ["updated_at"], +}; + +// Proyección mínima para el listado (evita N+1 y payloads grandes) +export const DEFAULT_LIST_ATTRIBUTES = [ + "id", + "company_id", + "customer_id", + "invoice_series", + "invoice_number", + "issue_date", + "status", + "total_amount_value", + "total_amount_scale", + "currency_code", + // Agregamos itemsCount por subconsulta (no es columna real) +] as const; + +type Sanitized = { + where: WhereOptions; + order: OrderItem[]; + limit: number; + offset: number; + attributes: (string | any)[]; + // keyset opcional + keyset?: { + after?: { issueDate: string; invoiceSeries: string; invoiceNumber: number; id: string }; + }; +}; + +const MAX_LIMIT = 100; +const DEFAULT_LIMIT = 25; + +export function sanitizeListCriteria(criteria: Criteria): Sanitized { + const { filters = {}, sort = [], pagination = {}, search } = (criteria ?? {}) as any; + + // LIMIT/OFFSET + const rawLimit = Number(pagination?.limit ?? DEFAULT_LIMIT); + const rawOffset = Number(pagination?.offset ?? 0); + const limit = Number.isFinite(rawLimit) + ? Math.max(1, Math.min(MAX_LIMIT, rawLimit)) + : DEFAULT_LIMIT; + const offset = Number.isFinite(rawOffset) && rawOffset >= 0 ? rawOffset : 0; + + // WHERE base siempre por seguridad a nivel superior (company en repo) + const where: WhereOptions = {}; + + // Búsqueda libre (q) → aplicar sobre campos concretos (series/number/status) + if (typeof search?.q === "string" && search.q.trim() !== "") { + const q = `%${search.q.trim()}%`; + Object.assign(where, { + [Op.or]: [ + { invoice_series: { [Op.like]: q } }, + { status: { [Op.like]: q } }, + // Buscar por número como texto + literal(`CAST(invoice_number AS CHAR) LIKE ${escapeLikeLiteral(q)}`), + ], + }); + } + + // Filtros permitidos + for (const [key, value] of Object.entries(filters)) { + const column = (ALLOWED_FILTERS as any)[key]; + if (!column) { + throw forbiddenFilter(key); + } + if (value == null) continue; + + // Operadores sencillos permitidos + if (typeof value === "object" && !Array.isArray(value)) { + const v = value as any; + const ops: any = {}; + if (v.eq !== undefined) ops[Op.eq] = v.eq; + if (v.ne !== undefined) ops[Op.ne] = v.ne; + if (v.in !== undefined && Array.isArray(v.in)) ops[Op.in] = v.in; + if (v.like !== undefined) ops[Op.like] = `%${String(v.like)}%`; + if (v.gte !== undefined) ops[Op.gte] = v.gte; + if (v.lte !== undefined) ops[Op.lte] = v.lte; + if (Object.keys(ops).length === 0) continue; + (where as any)[column] = ops; + } else { + (where as any)[column] = value; + } + } + + // ORDER (determinista) + const order: OrderItem[] = []; + const sortArray = Array.isArray(sort) + ? sort + : String(sort || "") + .split(",") + .filter(Boolean); + if (sortArray.length === 0) { + // orden por defecto: issue_date desc, invoice_series asc, invoice_number desc, id desc + order.push( + ["issue_date", "DESC"], + ["invoice_series", "ASC"], + ["invoice_number", "DESC"], + ["id", "DESC"] + ); + } else { + for (const part of sortArray) { + const desc = String(part).startsWith("-"); + const key = desc ? String(part).slice(1) : String(part); + const allowed = ALLOWED_SORT[key]; + if (!allowed) throw forbiddenSort(key); + const cols = Array.isArray(allowed) ? allowed : [allowed]; + for (const c of cols) { + order.push([c, desc ? "DESC" : "ASC"]); + } + } + // tiebreaker final + order.push(["id", "DESC"]); + } + + // Atributos (proyección) + const attributes: any[] = [...DEFAULT_LIST_ATTRIBUTES]; + + // itemsCount por subconsulta (evitamos include) + attributes.push([ + literal( + "(SELECT COUNT(1) FROM customer_invoice_items it WHERE it.invoice_id = customer_invoices.id AND it.deleted_at IS NULL)" + ), + "itemsCount", + ]); + + return { where, order, limit, offset, attributes }; +} + +// Helpers +function forbiddenFilter(field: string) { + const e = new Error(`Filter "${field}" is not allowed`); + (e as any).code = "FORBIDDEN_FILTER"; + return e; +} +function forbiddenSort(field: string) { + const e = new Error(`Sort "${field}" is not allowed`); + (e as any).code = "FORBIDDEN_SORT"; + return e; +} +function escapeLikeLiteral(v: string) { + // Simple escapado para usar en literal; asume conexión confiable. Para máxima seguridad usa replacements. + return `'${v.replace(/'/g, "''")}'`; +} diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts index 8fa5426e..ea553dc7 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts @@ -45,9 +45,9 @@ export class CustomerInvoiceItemModel extends Model< declare invoice: NonAttribute; static associate(database: Sequelize) { - const { Invoice_Model, CustomerInvoiceItem_Model } = connection.models; + const { CustomerInvoiceModel, CustomerInvoiceItemModel } = database.models; - CustomerInvoiceItem_Model.belongsTo(Invoice_Model, { + CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, { as: "customerInvoice", targetKey: "id", foreignKey: "invoice_id", diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts index d89acbf9..f66e62d8 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts @@ -19,6 +19,39 @@ export class CustomerInvoiceRepository this.mapper = mapper; } + // Listado por tenant con criteria saneada + /* async searchInCompany(criteria: any, companyId: string): Promise<{ + rows: InvoiceListRow[]; + total: number; + limit: number; + offset: number; + }> { + const { where, order, limit, offset, attributes } = sanitizeListCriteria(criteria); + + // WHERE con scope de company + const scopedWhere = { ...where, company_id: companyId }; + + const options: FindAndCountOptions = { + where: scopedWhere, + order, + limit, + offset, + attributes, + raw: true, // devolvemos objetos planos -> más rápido + nest: false, + distinct: true // por si en el futuro añadimos includes no duplicar count + }; + + const { rows, count } = await CustomerInvoiceModel.findAndCountAll(options); + + return { + rows: rows as unknown as InvoiceListRow[], + total: typeof count === "number" ? count : (count as any[]).length, + limit, + offset, + }; + } */ + async existsById(id: UniqueID, transaction?: Transaction): Promise> { try { const result = await this._exists(CustomerInvoiceModel, "id", id.toString(), transaction); diff --git a/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts b/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts index 5a6892b6..0a7a3366 100644 --- a/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts +++ b/modules/customer-invoices/src/web/hooks/use-create-customer-invoice-mutation.ts @@ -1,6 +1,6 @@ -import { useDataSource, useQueryKey } from "@erp/core/client"; +import { useDataSource } from "@erp/core/hooks"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { ICreateCustomerInvoiceRequestDTO } from "../../common/dto"; +import { CreateCustomerInvoiceRequestDTO } from "../../common/dto"; export const useCreateCustomerInvoiceMutation = () => { const queryClient = useQueryClient(); @@ -8,9 +8,9 @@ export const useCreateCustomerInvoiceMutation = () => { const keys = useQueryKey(); return useMutation< - ICreateCustomerInvoiceRequestDTO, + CreateCustomerInvoiceRequestDTO, Error, - Partial + Partial >({ mutationFn: (data) => { console.log(data); diff --git a/modules/customer-invoices/src/web/hooks/use-customer-invoices-query.tsx b/modules/customer-invoices/src/web/hooks/use-customer-invoices-query.tsx index eaa8d98c..cc91ed1a 100644 --- a/modules/customer-invoices/src/web/hooks/use-customer-invoices-query.tsx +++ b/modules/customer-invoices/src/web/hooks/use-customer-invoices-query.tsx @@ -1,4 +1,4 @@ -import { useDataSource, useQueryKey } from "@erp/core/client"; +import { useDataSource, useQueryKey } from "@erp/core/hooks"; import { useQuery } from "@tanstack/react-query"; import { CustomerInvoiceListResponseDTO } from "../../common/dto"; diff --git a/modules/customer-invoices/src/web/hooks/use-customer-invoices.bak b/modules/customer-invoices/src/web/hooks/use-customer-invoices.bak index 53a06278..f85879b9 100644 --- a/modules/customer-invoices/src/web/hooks/use-customer-invoices.bak +++ b/modules/customer-invoices/src/web/hooks/use-customer-invoices.bak @@ -1,4 +1,4 @@ -import { useDataSource, useQueryKey } from "@erp/core/client"; +import { useDataSource, useQueryKey } from "@erp/core/hooks"; import { IListCustomerInvoicesResponseDTO } from "@erp/customerInvoices/common/dto"; export type UseCustomerInvoicesListParams = Omit & { diff --git a/modules/customer-invoices/src/web/i18n.ts b/modules/customer-invoices/src/web/i18n.ts index 01028045..109e9359 100644 --- a/modules/customer-invoices/src/web/i18n.ts +++ b/modules/customer-invoices/src/web/i18n.ts @@ -1,10 +1,10 @@ -import { useEffect } from "react"; +import { i18n } from "i18next"; 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 addMissingBundles = (i18n: i18n) => { const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME); const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME); @@ -19,10 +19,7 @@ const addMissingBundles = (i18n: any) => { export const useTranslation = () => { const { i18n } = useI18NextTranslation(); - - useEffect(() => { - addMissingBundles(i18n); - }, [i18n]); + addMissingBundles(i18n); return useI18NextTranslation(MODULE_NAME); }; diff --git a/modules/customer-invoices/src/web/manifest.ts b/modules/customer-invoices/src/web/manifest.ts index 6b2844fe..173447bb 100644 --- a/modules/customer-invoices/src/web/manifest.ts +++ b/modules/customer-invoices/src/web/manifest.ts @@ -7,13 +7,11 @@ const MODULE_VERSION = "1.0.0"; export const CustomerInvoicesModuleManifiest: IModuleClient = { name: MODULE_NAME, version: MODULE_VERSION, - dependencies: ["auth", "Customers"], + dependencies: ["auth", "Core", "Customers"], protected: true, layout: "app", routes: (params: ModuleClientParams) => { - // i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true); - // i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true); return CustomerInvoiceRoutes(params); }, }; diff --git a/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx b/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx index 411fd3ab..4c3d9be8 100644 --- a/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx +++ b/modules/customer-invoices/src/web/pages/create/customer-invoice-edit-form.tsx @@ -44,7 +44,7 @@ import { useTranslation } from "../../i18n"; import { CustomerInvoiceData } from "./customer-invoice.schema"; import { formatCurrency } from "./utils"; -const invoiceSchema = z.object({ +const invoiceFormSchema = z.object({ id: z.string(), invoice_status: z.string(), invoice_number: z.string().min(1, "Número de factura requerido"), @@ -237,7 +237,7 @@ export const CustomerInvoiceEditForm = ({ const { t } = useTranslation(); const form = useForm({ - resolver: zodResolver(invoiceSchema), + resolver: zodResolver(invoiceFormSchema), defaultValues: initialData, }); diff --git a/modules/customer-invoices/src/web/pages/create/customer-invoice.schema.ts b/modules/customer-invoices/src/web/pages/create/customer-invoice.schema.ts index 21305c62..6ccb1f68 100644 --- a/modules/customer-invoices/src/web/pages/create/customer-invoice.schema.ts +++ b/modules/customer-invoices/src/web/pages/create/customer-invoice.schema.ts @@ -1,7 +1,7 @@ -import { CreateCustomerInvoiceCommandSchema } from "@erp/customer-invoices/common/dto"; import * as z from "zod/v4"; +import { CreateCustomerInvoiceRequestSchema } from "../../../common/dto"; -export const CustomerInvoiceItemDataFormSchema = CreateCustomerInvoiceCommandSchema.extend({ +export const CustomerInvoiceItemDataFormSchema = CreateCustomerInvoiceRequestSchema.extend({ subtotal_price: z.object({ amount: z.number().nullable(), scale: z.number(), diff --git a/modules/customer-invoices/src/web/pages/index.tsx b/modules/customer-invoices/src/web/pages/index.ts similarity index 100% rename from modules/customer-invoices/src/web/pages/index.tsx rename to modules/customer-invoices/src/web/pages/index.ts diff --git a/modules/customers/package.json b/modules/customers/package.json index b0803dae..a1d59604 100644 --- a/modules/customers/package.json +++ b/modules/customers/package.json @@ -11,50 +11,38 @@ "./components": "./src/web/components/index.ts" }, "peerDependencies": { - "@erp/core": "workspace:*", + "@tanstack/react-query": "^5.74.11", "dinero.js": "^1.9.1", "express": "^4.18.2", + "i18next": "^25.1.1", + "react-hook-form": "^7.58.1", + "react-i18next": "^15.5.1", "sequelize": "^6.37.5", "zod": "^3.25.67" }, "devDependencies": { - "@biomejs/biome": "1.9.4", - "@hookform/devtools": "^4.4.0", - "@types/dinero.js": "^1.9.4", "@types/express": "^4.17.21", "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.3", "@types/react-i18next": "^8.1.0", "typescript": "^5.8.3" }, "dependencies": { "@ag-grid-community/locale": "34.0.0", - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", "@erp/auth": "workspace:*", - "@erp/customers": "workspace:*", + "@erp/core": "workspace:*", "@hookform/resolvers": "^5.0.1", "@repo/rdx-criteria": "workspace:*", "@repo/rdx-ddd": "workspace:*", "@repo/rdx-ui": "workspace:*", "@repo/rdx-utils": "workspace:*", "@repo/shadcn-ui": "workspace:*", - "@tanstack/react-query": "^5.74.11", - "@tanstack/react-table": "^8.21.3", "ag-grid-community": "^33.3.0", "ag-grid-react": "^33.3.0", - "date-fns": "^4.1.0", - "i18next": "^25.1.1", "lucide-react": "^0.503.0", "react": "^19.1.0", + "react-data-table-component": "^7.7.0", "react-dom": "^19.1.0", - "react-hook-form": "^7.58.1", - "react-i18next": "^15.5.1", "react-router-dom": "^6.26.0", - "slugify": "^1.6.6", - "sonner": "^2.0.5", - "tailwindcss": "^4.1.11", - "tw-animate-css": "^1.3.4" + "use-debounce": "^10.0.5" } } diff --git a/modules/customers/src/api/application/get-customer/assembler/get-invoice.assembler.ts b/modules/customers/src/api/application/get-customer/assembler/get-customer.assembler.ts similarity index 73% rename from modules/customers/src/api/application/get-customer/assembler/get-invoice.assembler.ts rename to modules/customers/src/api/application/get-customer/assembler/get-customer.assembler.ts index 69f3ba91..5e081600 100644 --- a/modules/customers/src/api/application/get-customer/assembler/get-invoice.assembler.ts +++ b/modules/customers/src/api/application/get-customer/assembler/get-customer.assembler.ts @@ -1,21 +1,21 @@ -import { GetCustomerByIdResultDTO } from "../../../../common/dto"; +import { GetCustomerByIdResponseDTO } from "../../../../common/dto"; import { Customer } from "../../../domain"; export class GetCustomerAssembler { - toDTO(customer: Customer): GetCustomerByIdResultDTO { + toDTO(customer: Customer): GetCustomerByIdResponseDTO { return { id: customer.id.toPrimitive(), + reference: customer.reference, - customer_status: customer.status.toString(), - customer_number: customer.customerNumber.toString(), - customer_series: customer.customerSeries.toString(), - issue_date: customer.issueDate.toDateString(), - operation_date: customer.operationDate.toDateString(), - language_code: "ES", - currency: customer.currency, + is_freelancer: customer.isFreelancer, + name: customer.name, + trade_name: customer.tradeName.getOrUndefined(), + tin: customer.tin.toPrimitive(), metadata: { - entity: "customers", + entity: "customer", + //updated_at: customer.updatedAt.toDateString(), + //created_at: customer.createdAt.toDateString(), }, //subtotal: customer.calculateSubtotal().toPrimitive(), diff --git a/modules/customers/src/api/application/list-customers/assembler/list-customers.assembler.ts b/modules/customers/src/api/application/list-customers/assembler/list-customers.assembler.ts index 622948d3..6bd623d1 100644 --- a/modules/customers/src/api/application/list-customers/assembler/list-customers.assembler.ts +++ b/modules/customers/src/api/application/list-customers/assembler/list-customers.assembler.ts @@ -14,7 +14,7 @@ export class ListCustomersAssembler { is_freelancer: customer.isFreelancer, name: customer.name, - trade_name: customer.tradeName.getOrUndefined(), + trade_name: customer.tradeName.getOrUndefined() || "", tin: customer.tin.toString(), street: address.street, @@ -25,32 +25,28 @@ export class ListCustomersAssembler { email: customer.email.getValue(), phone: customer.phone.getValue(), - fax: customer.fax.getOrUndefined(), - website: customer.website.getOrUndefined(), + fax: customer.fax.isSome() ? customer.fax.getOrUndefined()!.getValue() : "", + website: customer.website.getOrUndefined() || "", legal_record: customer.legalRecord, default_tax: customer.defaultTax, - status: customer.isActive ? 'active' : 'inactive', + status: customer.isActive ? "active" : "inactive", lang_code: customer.langCode, currency_code: customer.currencyCode, - metadata: { - entity: "customer", - id: customer.id.toPrimitive(), - //created_at: customer.createdAt.toPrimitive(), - //updated_at: customer.updatedAt.toPrimitive() - } + metadata: { + entity: "customer", + id: customer.id.toPrimitive(), + //created_at: customer.createdAt.toPrimitive(), + //updated_at: customer.updatedAt.toPrimitive() + }, }; }); - + const totalItems = customers.total(); - - -const totalItems = customers.total(); - -return { + return { page: criteria.pageNumber, per_page: criteria.pageSize, total_pages: Math.ceil(totalItems / criteria.pageSize), @@ -66,5 +62,5 @@ return { //}, }, }; -}, + } } diff --git a/modules/customers/src/common/locales/en.json b/modules/customers/src/common/locales/en.json index e13fe01d..d060c7cc 100644 --- a/modules/customers/src/common/locales/en.json +++ b/modules/customers/src/common/locales/en.json @@ -1,5 +1,135 @@ { "common": {}, + "pages": { + "title": "Customers", + "description": "Manage your customers", + "list": { + "title": "Customer list", + "description": "List all customers", + "grid_columns": { + "name": "Name", + "trade_name": "Trade name", + "status": "Status", + "email": "Email" + } + }, + "create": { + "title": "New customer", + "description": "Create a new customer", + "back_to_list": "Back to the list" + } + }, + "form_fields": { + "customer_type": { + "label": "Customer type", + "description": "Select the type of customer", + "company": "Company", + "individual": "Individual" + }, + "name": { + "label": "Name", + "placeholder": "Enter customer name", + "description": "The full name of the customer" + }, + "trade_name": { + "label": "Trade name", + "placeholder": "Enter trade name", + "description": "The trade name of the customer" + }, + "tin": { + "label": "Tax Identification Number", + "placeholder": "Enter TIN", + "description": "The tax identification number of the customer" + }, + "reference": { + "label": "Reference", + "placeholder": "Enter reference", + "description": "A reference for the customer" + }, + "street": { + "label": "Street", + "placeholder": "Enter street", + "description": "The street address of the customer" + }, + "city": { + "label": "City", + "placeholder": "Enter city", + "description": "The city of the customer" + }, + "postal_code": { + "label": "Postal code", + "placeholder": "Enter postal code", + "description": "The postal code of the customer" + }, + "state": { + "label": "State", + "placeholder": "Enter state", + "description": "The state of the customer" + }, + "country": { + "label": "Country", + "placeholder": "Select country", + "description": "The country of the customer" + }, + "email": { + "label": "Email", + "placeholder": "Enter email", + "description": "The email address of the customer" + }, + "phone": { + "label": "Phone", + "placeholder": "Enter phone number", + "description": "The phone number of the customer" + }, + "fax": { + "label": "Fax", + "placeholder": "Enter fax number", + "description": "The fax number of the customer" + }, + "website": { + "label": "Website", + "placeholder": "Enter website URL", + "description": "The website of the customer" + }, + "default_tax": { + "label": "Default tax", + "placeholder": "Select default tax", + "description": "The default tax rate for the customer" + }, + "lang_code": { + "label": "Language", + "placeholder": "Select language", + "description": "The preferred language of the customer" + }, + "currency_code": { + "label": "Currency", + "placeholder": "Select currency", + "description": "The preferred currency of the customer" + }, + "legal_record": { + "label": "Legal record", + "placeholder": "Enter legal record", + "description": "The legal record of the customer" + } + }, + "form_groups": { + "basic_info": { + "title": "Basic information", + "description": "General customer details" + }, + "address": { + "title": "Address", + "description": "Customer location" + }, + "contact_info": { + "title": "Contact information", + "description": "Customer contact details" + }, + "additional_config": { + "title": "Additional settings", + "description": "Additional customer configurations" + } + }, "components": { "entity_selector": { "close": "Close", diff --git a/modules/customers/src/common/locales/es.json b/modules/customers/src/common/locales/es.json index 907f5d3e..0bc29da1 100644 --- a/modules/customers/src/common/locales/es.json +++ b/modules/customers/src/common/locales/es.json @@ -1,5 +1,135 @@ { "common": {}, + "pages": { + "title": "Clientes", + "description": "Gestiona tus clientes", + "list": { + "title": "Lista de clientes", + "description": "Lista todos los clientes", + "grid_columns": { + "name": "Nombre", + "trade_name": "Nombre comercial", + "status": "Estado", + "email": "Correo electrónico" + } + }, + "create": { + "title": "Nuevo cliente", + "description": "Crear un nuevo cliente", + "back_to_list": "Volver a la lista" + } + }, + "form_fields": { + "customer_type": { + "label": "Tipo de cliente", + "description": "Seleccione el tipo de cliente", + "company": "Empresa", + "individual": "Persona física" + }, + "name": { + "label": "Nombre", + "placeholder": "Ingrese el nombre del cliente", + "description": "El nombre completo del cliente" + }, + "trade_name": { + "label": "Nombre comercial", + "placeholder": "Ingrese el nombre comercial", + "description": "El nombre comercial del cliente" + }, + "tin": { + "label": "Número de Identificación Fiscal", + "placeholder": "Ingrese el NIF", + "description": "El número de identificación fiscal del cliente" + }, + "reference": { + "label": "Referencia", + "placeholder": "Ingrese la referencia", + "description": "Una referencia interna para el cliente" + }, + "street": { + "label": "Calle", + "placeholder": "Ingrese la calle", + "description": "La dirección de la calle del cliente" + }, + "city": { + "label": "Ciudad", + "placeholder": "Ingrese la ciudad", + "description": "La ciudad del cliente" + }, + "postal_code": { + "label": "Código postal", + "placeholder": "Ingrese el código postal", + "description": "El código postal del cliente" + }, + "state": { + "label": "Estado", + "placeholder": "Ingrese el estado", + "description": "El estado del cliente" + }, + "country": { + "label": "País", + "placeholder": "Seleccione el país", + "description": "El país del cliente" + }, + "email": { + "label": "Correo electrónico", + "placeholder": "Ingrese el correo electrónico", + "description": "La dirección de correo electrónico del cliente" + }, + "phone": { + "label": "Teléfono", + "placeholder": "Ingrese el número de teléfono", + "description": "El número de teléfono del cliente" + }, + "fax": { + "label": "Fax", + "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_tax": { + "label": "Impuesto por defecto", + "placeholder": "Seleccione el impuesto por defecto", + "description": "La tasa de impuesto por defecto para el cliente" + }, + "lang_code": { + "label": "Idioma", + "placeholder": "Seleccione el idioma", + "description": "El idioma preferido del cliente" + }, + "currency_code": { + "label": "Moneda", + "placeholder": "Seleccione la moneda", + "description": "La moneda preferida del cliente" + }, + "legal_record": { + "label": "Registro legal", + "placeholder": "Ingrese el registro legal", + "description": "El registro legal del cliente" + } + }, + "form_groups": { + "basic_info": { + "title": "Información básica", + "description": "Detalles generales del cliente" + }, + "address": { + "title": "Dirección", + "description": "Ubicación del cliente" + }, + "contact_info": { + "title": "Información de contacto", + "description": "Detalles de contacto del cliente" + }, + "additional_config": { + "title": "Configuración adicional", + "description": "Configuraciones adicionales del cliente" + } + }, "components": { "entity_selector": { "close": "Cerrar", diff --git a/modules/customers/src/web/components/client-selector.tsx b/modules/customers/src/web/components/client-selector.tsx index 8ce76a78..09ede470 100644 --- a/modules/customers/src/web/components/client-selector.tsx +++ b/modules/customers/src/web/components/client-selector.tsx @@ -3,7 +3,6 @@ import DataTable, { TableColumn } from "react-data-table-component"; import { useDebounce } from "use-debounce"; import { buildTextFilters } from "@erp/core/client"; -import { ListCustomersResultDTO } from "@erp/customers/common/dto"; import { Badge, Button, @@ -19,9 +18,10 @@ import { } from "@repo/shadcn-ui/components"; import { Building, Calendar, Mail, MapPin, Phone, Plus, User } from "lucide-react"; import { useState } from "react"; +import { CustomerListResponsetDTO } from "../../common"; import { useCustomersQuery } from "../hooks"; -type Customer = ListCustomersResultDTO["items"][number]; +type Customer = CustomerListResponsetDTO["items"][number]; const columns: TableColumn[] = [ { diff --git a/modules/customers/src/web/components/customer-prices-card.tsx b/modules/customers/src/web/components/customer-prices-card.tsx deleted file mode 100644 index 24ae0e55..00000000 --- a/modules/customers/src/web/components/customer-prices-card.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Separator, -} from "@repo/shadcn-ui/components"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "../i18n"; -import { formatCurrency } from "../pages/create/utils"; - -export const CustomerPricesCard = () => { - const { t } = useTranslation(); - const { register, formState, control, watch } = useFormContext(); - - /*const pricesWatch = useWatch({ control, name: ["subtotal_price", "discount", "tax"] }); - - const totals = calculateQuoteTotals(pricesWatch); - - const subtotal_price = formatNumber(totals.subtotalPrice); - const discount_price = formatNumber(totals.discountPrice); - const tax_price = formatNumber(totals.taxesPrice); - const total_price = formatNumber(totals.totalPrice);*/ - - const currency_symbol = watch("currency"); - - return ( - - - Impuestos y Totales - Configuración de impuestos y resumen de totales - - - -
-
- - {t("form_fields.subtotal_price.label")} - - - {formatCurrency(watch("subtotal_price.amount"), 2, watch("currency"))} - -
-
- -
-
- {t("form_fields.discount.label")} -
-
- - {t("form_fields.discount_price.label")} - - - {"-"} {formatCurrency(watch("discount_price.amount"), 2, watch("currency"))} - -
-
- -
-
- {t("form_fields.tax.label")} -
-
- - {t("form_fields.tax_price.label")} - - - {formatCurrency(watch("tax_price.amount"), 2, watch("currency"))} - -
-
{" "} - -
-
- - {t("form_fields.total_price.label")} - - - {formatCurrency(watch("total_price.amount"), 2, watch("currency"))} - -
-
-
-
- ); -}; diff --git a/modules/customers/src/web/components/customer-status-badge.tsx b/modules/customers/src/web/components/customer-status-badge.tsx index ffb33b5f..d91e0549 100644 --- a/modules/customers/src/web/components/customer-status-badge.tsx +++ b/modules/customers/src/web/components/customer-status-badge.tsx @@ -3,7 +3,7 @@ import { cn } from "@repo/shadcn-ui/lib/utils"; import { forwardRef } from "react"; import { useTranslation } from "../i18n"; -export type CustomerStatus = "draft" | "emitted" | "sent" | "received" | "rejected"; +export type CustomerStatus = "active" | "inactive"; export type CustomerStatusBadgeProps = { status: string; // permitir cualquier valor @@ -11,30 +11,16 @@ export type CustomerStatusBadgeProps = { }; const statusColorConfig: Record = { - draft: { + inactive: { badge: "bg-gray-600/10 dark:bg-gray-600/20 hover:bg-gray-600/10 text-gray-500 border-gray-600/60", dot: "bg-gray-500", }, - emitted: { - badge: - "bg-amber-600/10 dark:bg-amber-600/20 hover:bg-amber-600/10 text-amber-500 border-amber-600/60", - dot: "bg-amber-500", - }, - sent: { - badge: - "bg-cyan-600/10 dark:bg-cyan-600/20 hover:bg-cyan-600/10 text-cyan-500 border-cyan-600/60 shadow-none rounded-full", - dot: "bg-cyan-500", - }, - received: { + active: { badge: "bg-emerald-600/10 dark:bg-emerald-600/20 hover:bg-emerald-600/10 text-emerald-500 border-emerald-600/60", dot: "bg-emerald-500", }, - rejected: { - badge: "bg-red-600/10 dark:bg-red-600/20 hover:bg-red-600/10 text-red-500 border-red-600/60", - dot: "bg-red-500", - }, }; export const CustomerStatusBadge = forwardRef( diff --git a/modules/customers/src/web/components/customers-list-grid.tsx b/modules/customers/src/web/components/customers-list-grid.tsx index 2e24eb68..d85d08af 100644 --- a/modules/customers/src/web/components/customers-list-grid.tsx +++ b/modules/customers/src/web/components/customers-list-grid.tsx @@ -7,8 +7,6 @@ import { AllCommunityModule, ModuleRegistry } from "ag-grid-community"; ModuleRegistry.registerModules([AllCommunityModule]); -import { MoneyDTO } from "@erp/core"; -import { formatDate, formatMoney } from "@erp/core/client"; // Core CSS import { AgGridReact } from "ag-grid-react"; import { useCustomersQuery } from "../hooks"; @@ -23,31 +21,20 @@ export const CustomersListGrid = () => { // Column Definitions: Defines & controls grid columns. const [colDefs] = useState([ { - field: "invoice_status", - filter: true, - headerName: t("pages.list.grid_columns.invoice_status"), + field: "status", + + headerName: t("pages.list.grid_columns.status"), cellRenderer: (params: ValueFormatterParams) => { return ; }, }, - { field: "invoice_number", headerName: t("pages.list.grid_columns.invoice_number") }, - { field: "invoice_series", headerName: t("pages.list.grid_columns.invoice_series") }, + { field: "name", headerName: t("pages.list.grid_columns.name") }, + { field: "trade_name", headerName: t("pages.list.grid_columns.trade_name") }, { - field: "issue_date", - headerName: t("pages.list.grid_columns.issue_date"), - valueFormatter: (params: ValueFormatterParams) => { - return formatDate(params.value); - }, - }, - { - field: "total_price", - headerName: t("pages.list.grid_columns.total_price"), - valueFormatter: (params: ValueFormatterParams) => { - const rawValue: MoneyDTO = params.value; - return formatMoney(rawValue); - }, + field: "email", + headerName: t("pages.list.grid_columns.email"), }, ]); diff --git a/modules/customers/src/web/components/index.ts b/modules/customers/src/web/components/index.ts index e006c3ec..5c3ca390 100644 --- a/modules/customers/src/web/components/index.ts +++ b/modules/customers/src/web/components/index.ts @@ -1 +1,3 @@ export * from "./client-selector"; +export * from "./customers-layout"; +export * from "./customers-list-grid"; diff --git a/modules/customers/src/web/context/customers-context.tsx b/modules/customers/src/web/context/customers-context.tsx index 445d4f4a..9d452f05 100644 --- a/modules/customers/src/web/context/customers-context.tsx +++ b/modules/customers/src/web/context/customers-context.tsx @@ -2,7 +2,7 @@ import { PropsWithChildren, createContext } from "react"; /** * ──────────────────────────────────────────────────────────────────────────────── - * 💡 Posibles usos del InvoicingContext + * 💡 Posibles usos del Context * ──────────────────────────────────────────────────────────────────────────────── * Este contexto se diseña para encapsular estado y lógica compartida dentro del * bounded context de facturación (facturas), proporcionando acceso global a datos diff --git a/modules/customers/src/web/context/index.ts b/modules/customers/src/web/context/index.ts new file mode 100644 index 00000000..f93b7e8e --- /dev/null +++ b/modules/customers/src/web/context/index.ts @@ -0,0 +1 @@ +export * from "./customers-context"; diff --git a/modules/customers/src/web/globals.css b/modules/customers/src/web/globals.css index 8af59cfa..c407c45a 100644 --- a/modules/customers/src/web/globals.css +++ b/modules/customers/src/web/globals.css @@ -1,19 +1,2 @@ @source "./components"; @source "./pages"; - -.custom-dialog-lg { - max-width: 1024px !important; - width: 100% !important; -} -.custom-dialog-xl { - max-width: 1280px !important; - width: 100% !important; -} -.custom-dialog-2xl { - max-width: 1536px !important; - width: 100% !important; -} -.custom-dialog-3xl { - max-width: 1920px !important; - width: 100% !important; -} diff --git a/modules/customers/src/web/hooks/use-create-customer-mutation.ts b/modules/customers/src/web/hooks/use-create-customer-mutation.ts index 30de0b0c..237b0075 100644 --- a/modules/customers/src/web/hooks/use-create-customer-mutation.ts +++ b/modules/customers/src/web/hooks/use-create-customer-mutation.ts @@ -1,13 +1,13 @@ -import { useDataSource, useQueryKey } from "@erp/core/client"; +import { useDataSource, useQueryKey } from "@erp/core/hooks"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { ICreateCustomerRequestDTO } from "../../common/dto"; +import { CreateCustomerRequestDTO } from "../../common/dto"; export const useCreateCustomerMutation = () => { const queryClient = useQueryClient(); const dataSource = useDataSource(); const keys = useQueryKey(); - return useMutation>({ + return useMutation>({ mutationFn: (data) => { console.log(data); return dataSource.createOne("customers", data); diff --git a/modules/customers/src/web/hooks/use-customers-query.tsx b/modules/customers/src/web/hooks/use-customers-query.tsx index 70b71194..e0281d8c 100644 --- a/modules/customers/src/web/hooks/use-customers-query.tsx +++ b/modules/customers/src/web/hooks/use-customers-query.tsx @@ -1,4 +1,4 @@ -import { useDataSource, useQueryKey } from "@erp/core/client"; +import { useDataSource, useQueryKey } from "@erp/core/hooks"; import { useQuery } from "@tanstack/react-query"; import { CustomerListResponsetDTO } from "../../common/dto"; diff --git a/modules/customers/src/web/hooks/use-customers.bak b/modules/customers/src/web/hooks/use-customers.bak index b7459ab6..b4bfb751 100644 --- a/modules/customers/src/web/hooks/use-customers.bak +++ b/modules/customers/src/web/hooks/use-customers.bak @@ -1,4 +1,4 @@ -import { useDataSource, useQueryKey } from "@erp/core/client"; +import { useDataSource, useQueryKey } from "@erp/core/hooks"; import { IListCustomersResponseDTO } from "@erp/customers/common/dto"; export type UseCustomersListParams = Omit & { diff --git a/modules/customers/src/web/i18n.ts b/modules/customers/src/web/i18n.ts index 01028045..109e9359 100644 --- a/modules/customers/src/web/i18n.ts +++ b/modules/customers/src/web/i18n.ts @@ -1,10 +1,10 @@ -import { useEffect } from "react"; +import { i18n } from "i18next"; 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 addMissingBundles = (i18n: i18n) => { const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME); const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME); @@ -19,10 +19,7 @@ const addMissingBundles = (i18n: any) => { export const useTranslation = () => { const { i18n } = useI18NextTranslation(); - - useEffect(() => { - addMissingBundles(i18n); - }, [i18n]); + addMissingBundles(i18n); return useI18NextTranslation(MODULE_NAME); }; diff --git a/modules/customers/src/web/manifest.ts b/modules/customers/src/web/manifest.ts index b9e46c59..7860d586 100644 --- a/modules/customers/src/web/manifest.ts +++ b/modules/customers/src/web/manifest.ts @@ -1,6 +1,5 @@ import { IModuleClient, ModuleClientParams } from "@erp/core/client"; -//import enResources from "../common/locales/en.json"; -//import esResources from "../common/locales/es.json"; +import { CustomerRoutes } from "./customer-routes"; export const MODULE_NAME = "Customers"; const MODULE_VERSION = "1.0.0"; @@ -8,15 +7,12 @@ const MODULE_VERSION = "1.0.0"; export const CustomersModuleManifiest: IModuleClient = { name: MODULE_NAME, version: MODULE_VERSION, - dependencies: ["auth"], + dependencies: ["auth", "Core"], protected: true, layout: "app", routes: (params: ModuleClientParams) => { - //i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true); - //i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true); - //return CustomerRoutes(params); - return []; + return CustomerRoutes(params); }, }; diff --git a/modules/customers/src/web/pages/create/create.tsx b/modules/customers/src/web/pages/create/create.tsx new file mode 100644 index 00000000..ff844792 --- /dev/null +++ b/modules/customers/src/web/pages/create/create.tsx @@ -0,0 +1,73 @@ +import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; +import { useNavigate } from "react-router-dom"; + +import { useCreateCustomerMutation } from "../../hooks/use-create-customer-mutation"; +import { useTranslation } from "../../i18n"; +import { CustomerEditForm } from "./customer-edit-form"; + +export const CustomerCreate = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const { mutate, isPending, isError, error } = useCreateCustomerMutation(); + + const handleSubmit = (data: any) => { + // Handle form submission logic here + console.log("Form submitted with data:", data); + mutate(data); + + // Navigate to the list page after submission + navigate("/customers/list"); + }; + + if (isError) { + console.error("Error creating customer:", error); + // Optionally, you can show an error message to the user + } + + // Render the component + // You can also handle loading state if needed + // For example, you can disable the submit button while the mutation is in progress + // const isLoading = useCreateCustomerMutation().isLoading; + + // Return the JSX for the component + // You can customize the form and its fields as needed + // For example, you can use a form library like react-hook-form or Formik to handle form state and validation + // Here, we are using a simple form with a submit button + + // Note: Make sure to replace the form fields with your actual invoice fields + // and handle validation as needed. + // This is just a basic example to demonstrate the structure of the component. + + // If you are using a form library, you can pass the handleSubmit function to the form's onSubmit prop + // and use the form library's methods to handle form state and validation. + + // Example of a simple form submission handler + // You can replace this with your actual form handling logic + // const handleSubmit = (event: React.FormEvent) => { + // event.preventDefault(); + // const formData = new FormData(event.currentTarget); + + return ( + <> + + +
+
+

{t("pages.create.title")}

+

{t("pages.create.description")}

+
+
+ +
+
+
+ +
+
+ + ); +}; diff --git a/modules/customers/src/web/pages/create/customer-edit-form.tsx b/modules/customers/src/web/pages/create/customer-edit-form.tsx new file mode 100644 index 00000000..41cb5b8d --- /dev/null +++ b/modules/customers/src/web/pages/create/customer-edit-form.tsx @@ -0,0 +1,319 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; + +import { TaxesMultiSelectField } from "@erp/core/components"; +import { SelectField, TextAreaField, TextField } from "@repo/rdx-ui/components"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Form, + Label, + RadioGroup, + RadioGroupItem, +} from "@repo/shadcn-ui/components"; +import { useTranslation } from "../../i18n"; +import { CustomerData, CustomerDataFormSchema } from "./customer.schema"; + +const defaultCustomerData = { + id: "5e4dc5b3-96b9-4968-9490-14bd032fec5f", + status: "active", + name: "1", + language_code: "ES", + currency: "EUR", +}; + +interface CustomerFormProps { + initialData?: CustomerData; + isPending?: boolean; + /** + * Callback function to handle form submission. + * @param data - The customer data submitted by the form. + */ + onSubmit?: (data: CustomerData) => void; +} + +export const CustomerEditForm = ({ + initialData = defaultCustomerData, + onSubmit, + isPending, +}: CustomerFormProps) => { + const { t } = useTranslation(); + + const form = useForm({ + resolver: zodResolver(CustomerDataFormSchema), + defaultValues: initialData, + }); + + const handleSubmit = (data: CustomerData) => { + console.log("Datos del formulario:", data); + onSubmit?.(data); + }; + + const handleError = (errors: any) => { + console.error("Errores en el formulario:", errors); + // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario + }; + + const handleCancel = () => { + form.reset(initialData); + }; + + return ( +
+ +
+ {/* Información básica */} + + + {t("form_groups.basic_info.title")} + {t("form_groups.basic_info.description")} + + +
+ + { + // Usar setValue del form + form.setValue("customer_type", value); + }} + className='flex gap-6' + > +
+ + +
+
+ + +
+
+
+ + + + + + + + +
+
+ + {/* Dirección */} + + + {t("form_groups.address.title")} + {t("form_groups.address.description")} + + + + + + + + + + + + + + + {/* Contacto */} + + + {t("form_groups.contact_info.title")} + {t("form_groups.contact_info.description")} + + + + + + + + + + + + {/* Configuraciones Adicionales */} + + + {t("form_groups.additional_config.title")} + {t("form_groups.additional_config.description")} + + + + + + + + + + + +
+
+ + ); +}; diff --git a/modules/customers/src/web/pages/create/customer.schema.ts b/modules/customers/src/web/pages/create/customer.schema.ts new file mode 100644 index 00000000..33d235ae --- /dev/null +++ b/modules/customers/src/web/pages/create/customer.schema.ts @@ -0,0 +1,6 @@ +import * as z from "zod/v4"; + +import { CreateCustomerRequestSchema } from "../../../common"; + +export const CustomerDataFormSchema = CreateCustomerRequestSchema; +export type CustomerData = z.infer; diff --git a/modules/customers/src/web/pages/create/index.ts b/modules/customers/src/web/pages/create/index.ts new file mode 100644 index 00000000..c6262006 --- /dev/null +++ b/modules/customers/src/web/pages/create/index.ts @@ -0,0 +1 @@ +export * from "./create"; diff --git a/modules/customers/src/web/pages/create/utils.ts b/modules/customers/src/web/pages/create/utils.ts new file mode 100644 index 00000000..ce8469c8 --- /dev/null +++ b/modules/customers/src/web/pages/create/utils.ts @@ -0,0 +1,41 @@ +import type { InvoiceItem } from "@/types/invoice"; + +export function calculateItemTotal(quantity: number, unitPrice: number, discount = 0): number { + const subtotal = quantity * unitPrice; + const discountAmount = (subtotal * discount) / 100; + return subtotal - discountAmount; +} + +export function calculateInvoiceTotals(items: InvoiceItem[], taxRate = 21) { + const subtotal = items.reduce((sum, item) => { + return ( + sum + (item.quantity.amount * item.unit_price.amount) / Math.pow(10, item.unit_price.scale) + ); + }, 0); + + const totalDiscount = items.reduce((sum, item) => { + const itemSubtotal = + (item.quantity.amount * item.unit_price.amount) / Math.pow(10, item.unit_price.scale); + return sum + (itemSubtotal * item.discount.amount) / Math.pow(10, item.discount.scale) / 100; + }, 0); + + const beforeTax = subtotal - totalDiscount; + const taxAmount = (beforeTax * taxRate) / 100; + const total = beforeTax + taxAmount; + + return { + subtotal: Math.round(subtotal * 100), + totalDiscount: Math.round(totalDiscount * 100), + beforeTax: Math.round(beforeTax * 100), + taxAmount: Math.round(taxAmount * 100), + total: Math.round(total * 100), + }; +} + +export function formatCurrency(amount: number, scale = 2, currency = "EUR"): string { + const value = amount / Math.pow(10, scale); + return new Intl.NumberFormat("es-ES", { + style: "currency", + currency: currency, + }).format(value); +} diff --git a/modules/customers/src/web/pages/index.ts b/modules/customers/src/web/pages/index.ts new file mode 100644 index 00000000..d1c12f23 --- /dev/null +++ b/modules/customers/src/web/pages/index.ts @@ -0,0 +1,2 @@ +export * from "./create"; +export * from "./list"; diff --git a/modules/customers/src/web/pages/list.tsx b/modules/customers/src/web/pages/list.tsx new file mode 100644 index 00000000..e3b99726 --- /dev/null +++ b/modules/customers/src/web/pages/list.tsx @@ -0,0 +1,34 @@ +import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; +import { PlusIcon } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { CustomersListGrid } from "../components"; +import { useTranslation } from "../i18n"; + +export const CustomersList = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( + <> + + +
+
+

{t("pages.list.title")}

+

{t("pages.list.description")}

+
+
+ +
+
+
+ +
+
+ + ); +}; diff --git a/package.json b/package.json index 7db28130..1a6da17b 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,7 @@ { "name": "uecko-erp-2025", "private": true, - "workspaces": [ - "apps/*", - "modules/*", - "packages/*" - ], + "workspaces": ["apps/*", "modules/*", "packages/*"], "scripts": { "build": "turbo build", "dev": "turbo dev", @@ -21,9 +17,7 @@ "devDependencies": { "@biomejs/biome": "1.9.4", "@repo/typescript-config": "workspace:*", - "fs": "0.0.1-security", "inquirer": "^12.5.2", - "path": "^0.12.7", "ts-node": "^10.9.2", "turbo": "^2.5.1", "typescript": "5.8.3" diff --git a/packages/rdx-ui/src/components/form/SelectField.tsx b/packages/rdx-ui/src/components/form/SelectField.tsx new file mode 100644 index 00000000..82bada63 --- /dev/null +++ b/packages/rdx-ui/src/components/form/SelectField.tsx @@ -0,0 +1,84 @@ +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} 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 SelectFieldProps = { + control: Control; + name: FieldPath; + items: Array<{ value: string; label: string }>; + label?: string; + placeholder?: string; + description?: string; + disabled?: boolean; + required?: boolean; + readOnly?: boolean; + className?: string; +}; + +export function SelectField({ + control, + name, + items, + label, + placeholder, + description, + disabled = false, + required = false, + readOnly = false, + className, +}: SelectFieldProps) { + const { t } = useTranslation(); + const isDisabled = disabled || readOnly; + + return ( + ( + + {label && ( +
+ {label} + {required && {t("common.required")}} +
+ )} + + + + {description || "\u00A0"} + + +
+ )} + /> + ); +} diff --git a/packages/rdx-ui/src/components/form/TextField.tsx b/packages/rdx-ui/src/components/form/TextField.tsx index 6a7ce105..155c4cb3 100644 --- a/packages/rdx-ui/src/components/form/TextField.tsx +++ b/packages/rdx-ui/src/components/form/TextField.tsx @@ -1,5 +1,3 @@ -// DatePickerField.tsx - import { FormControl, FormField, diff --git a/packages/rdx-ui/src/components/form/index.tsx b/packages/rdx-ui/src/components/form/index.tsx index bb1a65db..8637e04d 100644 --- a/packages/rdx-ui/src/components/form/index.tsx +++ b/packages/rdx-ui/src/components/form/index.tsx @@ -1,4 +1,5 @@ export * from "./DatePickerField.tsx"; export * from "./DatePickerInputField.tsx"; +export * from "./SelectField.tsx"; export * from "./TextAreaField.tsx"; export * from "./TextField.tsx"; diff --git a/packages/rdx-ui/src/components/loading-overlay/loading-indicator.tsx b/packages/rdx-ui/src/components/loading-overlay/loading-indicator.tsx index eaef7a71..f9391a05 100644 --- a/packages/rdx-ui/src/components/loading-overlay/loading-indicator.tsx +++ b/packages/rdx-ui/src/components/loading-overlay/loading-indicator.tsx @@ -1,5 +1,5 @@ import { cn } from "@repo/shadcn-ui/lib/utils"; -import { t } from "i18next"; +import { useTranslation } from "../../locales/i18n.ts"; import { LoadingSpinIcon } from "./loading-spin-icon.tsx"; export type LoadingIndicatorProps = { @@ -13,9 +13,10 @@ export type LoadingIndicatorProps = { export const LoadingIndicator = ({ active = true, look = "dark", - title = t("components.loading_indicator.title"), + title, subtitle = "", }: LoadingIndicatorProps) => { + const { t } = useTranslation(); const isDark = look === "dark"; const loadingSpinClassName = isDark ? "text-brand" : "text-white"; @@ -24,11 +25,7 @@ export const LoadingIndicator = ({ } return ( -
+
{/**/} {title ? ( @@ -38,12 +35,12 @@ export const LoadingIndicator = ({ isDark ? "text-slate-600" : "text-white" )} > - {title} + {title || t("components.loading_indicator.title")} ) : null} {subtitle ? (

- {subtitle} + {subtitle || t("components.loading_indicator.subtitle")}

) : null}
diff --git a/packages/rdx-ui/src/locales/en.json b/packages/rdx-ui/src/locales/en.json index 7daa52c2..71953281 100644 --- a/packages/rdx-ui/src/locales/en.json +++ b/packages/rdx-ui/src/locales/en.json @@ -7,7 +7,8 @@ }, "components": { "loading_indicator": { - "title": "Loading..." + "title": "Loading...", + "subtitle": "This may take a few seconds. Please do not close this page." }, "loading_overlay": { "title": "Loading...", diff --git a/packages/rdx-ui/src/locales/es.json b/packages/rdx-ui/src/locales/es.json index f5a161b0..3e3b687b 100644 --- a/packages/rdx-ui/src/locales/es.json +++ b/packages/rdx-ui/src/locales/es.json @@ -6,8 +6,9 @@ "search": "Buscar" }, "components": { - "LoadingIndicator": { - "title": "Cargando..." + "loading_indicator": { + "title": "Cargando...", + "subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página." }, "loading_overlay": { "title": "Cargando...", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1af4cf1..2e4f643f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,15 +14,9 @@ importers: '@repo/typescript-config': specifier: workspace:* version: link:packages/typescript-config - fs: - specifier: 0.0.1-security - version: 0.0.1-security inquirer: specifier: ^12.5.2 version: 12.6.3(@types/node@24.0.3) - path: - specifier: ^0.12.7 - version: 0.12.7 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@24.0.3)(typescript@5.8.3) @@ -204,9 +198,6 @@ importers: '@erp/customers': specifier: workspace:* version: link:../../modules/customers - '@repo/rdx-criteria': - specifier: workspace:* - version: link:../../packages/rdx-criteria '@repo/rdx-ui': specifier: workspace:* version: link:../../packages/rdx-ui @@ -240,9 +231,6 @@ importers: react-hook-form: specifier: ^7.56.4 version: 7.58.1(react@19.1.0) - react-hook-form-persist: - specifier: ^3.0.0 - version: 3.0.0(react-hook-form@7.58.1(react@19.1.0))(react@19.1.0) react-i18next: specifier: ^15.0.1 version: 15.5.3(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) @@ -255,9 +243,6 @@ importers: sequelize: specifier: ^6.37.5 version: 6.37.7(mysql2@3.14.1) - tailwind-merge: - specifier: ^3.2.0 - version: 3.3.1 tailwindcss: specifier: ^4.1.10 version: 4.1.11 @@ -271,15 +256,6 @@ importers: '@biomejs/biome': specifier: 1.9.4 version: 1.9.4 - '@hookform/devtools': - specifier: ^4.4.0 - version: 4.4.0(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@repo/typescript-config': - specifier: workspace:* - version: link:../../packages/typescript-config - '@tailwindcss/postcss': - specifier: ^4.1.5 - version: 4.1.10 '@tanstack/react-query-devtools': specifier: ^5.74.11 version: 5.81.2(@tanstack/react-query@5.81.2(react@19.1.0))(react@19.1.0) @@ -298,12 +274,6 @@ importers: '@vitejs/plugin-react': specifier: ^4.4.1 version: 4.6.0(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)) - autoprefixer: - specifier: ^10.4.20 - version: 10.4.21(postcss@8.5.6) - globals: - specifier: ^16.0.0 - version: 16.2.0 typescript: specifier: ~5.8.3 version: 5.8.3 @@ -380,9 +350,15 @@ importers: '@repo/rdx-ddd': specifier: workspace:* version: link:../../packages/rdx-ddd + '@repo/rdx-ui': + specifier: workspace:* + version: link:../../packages/rdx-ui '@repo/rdx-utils': specifier: workspace:* version: link:../../packages/rdx-utils + '@repo/shadcn-ui': + specifier: workspace:* + version: link:../../packages/shadcn-ui '@tanstack/react-query': specifier: ^5.75.4 version: 5.81.2(react@19.1.0) @@ -392,15 +368,24 @@ importers: dinero.js: specifier: ^1.9.1 version: 1.9.1 + express: + specifier: ^4.18.2 + version: 4.21.2 http-status: specifier: ^2.1.0 version: 2.1.0 - joi: - specifier: ^17.13.3 - version: 17.13.3 - libphonenumber-js: - specifier: ^1.11.20 - version: 1.12.9 + i18next: + specifier: ^25.1.1 + version: 25.2.1(typescript@5.8.3) + react: + specifier: ^19.1.0 + version: 19.1.0 + react-hook-form: + specifier: ^7.58.1 + version: 7.58.1(react@19.1.0) + react-i18next: + specifier: ^15.5.1 + version: 15.5.3(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) react-router-dom: specifier: ^6.26.0 version: 6.30.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -411,9 +396,6 @@ importers: specifier: ^3.25.67 version: 3.25.67 devDependencies: - '@biomejs/biome': - specifier: 1.9.4 - version: 1.9.4 '@types/axios': specifier: ^0.14.4 version: 0.14.4 @@ -423,24 +405,12 @@ importers: '@types/express': specifier: ^4.17.21 version: 4.17.23 - '@types/jest': - specifier: 29.5.14 - version: 29.5.14 '@types/react': specifier: ^19.1.2 version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.3 - version: 19.1.6(@types/react@19.1.8) - ts-to-zod: - specifier: ^3.15.0 - version: 3.15.0 typescript: specifier: ^5.8.3 version: 5.8.3 - zod-to-ts: - specifier: ^1.2.0 - version: 1.2.0(typescript@5.8.3)(zod@3.25.67) modules/customer-invoices: dependencies: @@ -507,6 +477,9 @@ importers: i18next: specifier: ^25.1.1 version: 25.2.1(typescript@5.8.3) + libphonenumber-js: + specifier: ^1.12.7 + version: 1.12.9 lucide-react: specifier: ^0.503.0 version: 0.503.0(react@19.1.0) @@ -528,31 +501,13 @@ importers: sequelize: specifier: ^6.37.5 version: 6.37.7(mysql2@3.14.1) - slugify: - specifier: ^1.6.6 - version: 1.6.6 - sonner: - specifier: ^2.0.5 - version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - tailwindcss: - specifier: ^4.1.11 - version: 4.1.11 - tw-animate-css: - specifier: ^1.3.4 - version: 1.3.5 zod: specifier: ^3.25.67 version: 3.25.67 devDependencies: - '@biomejs/biome': - specifier: 1.9.4 - version: 1.9.4 '@hookform/devtools': specifier: ^4.4.0 version: 4.4.0(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@types/dinero.js': - specifier: ^1.9.4 - version: 1.9.4 '@types/express': specifier: ^4.17.21 version: 4.17.23 @@ -574,24 +529,12 @@ importers: '@ag-grid-community/locale': specifier: 34.0.0 version: 34.0.0 - '@dnd-kit/core': - specifier: ^6.3.1 - version: 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@dnd-kit/sortable': - specifier: ^10.0.0 - version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) - '@dnd-kit/utilities': - specifier: ^3.2.2 - version: 3.2.2(react@19.1.0) '@erp/auth': specifier: workspace:* version: link:../auth '@erp/core': specifier: workspace:* version: link:../core - '@erp/customers': - specifier: workspace:* - version: 'link:' '@hookform/resolvers': specifier: ^5.0.1 version: 5.1.1(react-hook-form@7.58.1(react@19.1.0)) @@ -613,18 +556,12 @@ importers: '@tanstack/react-query': specifier: ^5.74.11 version: 5.81.2(react@19.1.0) - '@tanstack/react-table': - specifier: ^8.21.3 - version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) ag-grid-community: specifier: ^33.3.0 version: 33.3.2 ag-grid-react: specifier: ^33.3.0 version: 33.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - date-fns: - specifier: ^4.1.0 - version: 4.1.0 dinero.js: specifier: ^1.9.1 version: 1.9.1 @@ -640,6 +577,9 @@ importers: react: specifier: ^19.1.0 version: 19.1.0 + react-data-table-component: + specifier: ^7.7.0 + version: 7.7.0(react@19.1.0)(styled-components@6.1.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) @@ -655,40 +595,19 @@ importers: sequelize: specifier: ^6.37.5 version: 6.37.7(mysql2@3.14.1) - slugify: - specifier: ^1.6.6 - version: 1.6.6 - sonner: - specifier: ^2.0.5 - version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - tailwindcss: - specifier: ^4.1.11 - version: 4.1.11 - tw-animate-css: - specifier: ^1.3.4 - version: 1.3.5 + use-debounce: + specifier: ^10.0.5 + version: 10.0.5(react@19.1.0) zod: specifier: ^3.25.67 version: 3.25.67 devDependencies: - '@biomejs/biome': - specifier: 1.9.4 - version: 1.9.4 - '@hookform/devtools': - specifier: ^4.4.0 - version: 4.4.0(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@types/dinero.js': - specifier: ^1.9.4 - version: 1.9.4 '@types/express': specifier: ^4.17.21 version: 4.17.23 '@types/react': specifier: ^19.1.2 version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.3 - version: 19.1.6(@types/react@19.1.8) '@types/react-i18next': specifier: ^8.1.0 version: 8.1.0(i18next@25.2.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) @@ -1378,9 +1297,15 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + '@emotion/is-prop-valid@1.3.1': resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + '@emotion/memoize@0.9.0': resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} @@ -1412,6 +1337,9 @@ packages: '@emotion/unitless@0.10.0': resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} peerDependencies: @@ -1848,10 +1776,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oclif/core@4.4.0': - resolution: {integrity: sha512-wH5g3SLmbRutnr7UzQBSozRFEAZ7U9YGB/wFuBRr0ZghTgv5DE+KQaf6ZtU7iFb9pvkvoVRnT5XheNAtbjRDaQ==} - engines: {node: '>=18.0.0'} - '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -3087,6 +3011,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} @@ -3105,11 +3032,6 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript/vfs@1.6.1': - resolution: {integrity: sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA==} - peerDependencies: - typescript: '*' - '@vitejs/plugin-react@4.6.0': resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3187,10 +3109,6 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - ansis@3.17.0: - resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} - engines: {node: '>=14'} - any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -3293,10 +3211,6 @@ packages: resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} engines: {node: '>= 10.0.0'} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -3378,13 +3292,12 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001720: resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==} - case@1.6.3: - resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} - engines: {node: '>= 0.8.0'} - chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3407,10 +3320,6 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -3441,10 +3350,6 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clean-stack@3.0.1: - resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} - engines: {node: '>=10'} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -3608,9 +3513,16 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -3931,10 +3843,6 @@ packages: engines: {node: '>=6.0'} hasBin: true - esm@3.2.25: - resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} - engines: {node: '>=6'} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -4083,10 +3991,6 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} - engines: {node: '>=14.14'} - fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -4094,9 +3998,6 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fs@0.0.1-security: - resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4167,10 +4068,6 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@16.2.0: - resolution: {integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==} - engines: {node: '>=18'} - globby@10.0.2: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} engines: {node: '>=8'} @@ -4384,19 +4281,10 @@ packages: is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -4424,10 +4312,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-observable@2.1.0: - resolution: {integrity: sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==} - engines: {node: '>=8'} - is-path-cwd@2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} engines: {node: '>=6'} @@ -4453,10 +4337,6 @@ packages: is-what@3.14.1: resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -5133,9 +5013,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - observable-fns@0.6.1: - resolution: {integrity: sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -5378,15 +5255,14 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prettier@3.0.3: - resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} - engines: {node: '>=14'} - hasBin: true - pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5442,6 +5318,12 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-data-table-component@7.7.0: + resolution: {integrity: sha512-5knL6zMSKlbvzu9P04KM5Lx8/EyQujb4I9z3rWeoVX++IDJadQ7aR4X5J6EeS90wjK0Xoa6btaVeglnCAqD2ag==} + peerDependencies: + react: '>= 17.0.0' + styled-components: '>= 5.0.0' + react-day-picker@8.10.1: resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} peerDependencies: @@ -5453,12 +5335,6 @@ packages: peerDependencies: react: ^19.1.0 - react-hook-form-persist@3.0.0: - resolution: {integrity: sha512-6nwW65JyFpBem9RjLYAWvIFxOLoCk0E13iB9e5yeF5jeHlwx1ua0M77FvwhPpD8eaCz7hG4ziCdOxRcnJVUSxQ==} - peerDependencies: - react: '>= 16.3' - react-hook-form: '>= 6' - react-hook-form@7.58.1: resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==} engines: {node: '>=18.0.0'} @@ -5568,10 +5444,6 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -5776,6 +5648,9 @@ packages: shallow-equal-object@1.1.1: resolution: {integrity: sha512-9DDzYRlzCwF2CemeF0aOFk5T5KMrjG7HldcW7utwYhA/limuGHn3No8KhpDE8BrO7GLaSRJumNKReipZBybd7A==} + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -5817,10 +5692,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slugify@1.6.6: - resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} - engines: {node: '>=8.0.0'} - smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -5935,9 +5806,19 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + styled-components@6.1.19: + resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==} + engines: {node: '>= 16'} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + stylus@0.62.0: resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==} hasBin: true @@ -6006,18 +5887,12 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - threads@1.7.0: - resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-worker@2.3.0: - resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} - tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} @@ -6110,10 +5985,6 @@ packages: '@swc/wasm': optional: true - ts-to-zod@3.15.0: - resolution: {integrity: sha512-Lu5ITqD8xCIo4JZp4Cg3iSK3J2x3TGwwuDtNHfAIlx1mXWKClRdzqV+x6CFEzhKtJlZzhyvJIqg7DzrWfsdVSg==} - hasBin: true - tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -6131,6 +6002,9 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -6153,12 +6027,6 @@ packages: typescript: optional: true - tsutils@3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - tsx@4.19.4: resolution: {integrity: sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==} engines: {node: '>=18.0.0'} @@ -6271,6 +6139,12 @@ packages: '@types/react': optional: true + use-debounce@10.0.5: + resolution: {integrity: sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==} + engines: {node: '>= 16.0.0'} + peerDependencies: + react: '*' + use-deep-compare-effect@1.8.1: resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==} engines: {node: '>=10', npm: '>=6'} @@ -6425,10 +6299,6 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - widest-line@3.1.0: - resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} - engines: {node: '>=8'} - winston-daily-rotate-file@5.0.0: resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} engines: {node: '>=8'} @@ -6506,12 +6376,6 @@ packages: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} - zod-to-ts@1.2.0: - resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} - peerDependencies: - typescript: ^4.9.4 || ^5.0.2 - zod: ^3 - zod@3.25.67: resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} @@ -6549,7 +6413,7 @@ snapshots: '@babel/traverse': 7.27.4 '@babel/types': 7.27.6 convert-source-map: 2.0.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6733,7 +6597,7 @@ snapshots: '@babel/parser': 7.27.5 '@babel/template': 7.27.2 '@babel/types': 7.27.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6857,10 +6721,16 @@ snapshots: '@emotion/hash@0.9.2': {} + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/is-prop-valid@1.3.1': dependencies: '@emotion/memoize': 0.9.0 + '@emotion/memoize@0.8.1': {} + '@emotion/memoize@0.9.0': {} '@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0)': @@ -6906,6 +6776,8 @@ snapshots: '@emotion/unitless@0.10.0': {} + '@emotion/unitless@0.8.1': {} + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)': dependencies: react: 19.1.0 @@ -7388,27 +7260,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@oclif/core@4.4.0': - dependencies: - ansi-escapes: 4.3.2 - ansis: 3.17.0 - clean-stack: 3.0.1 - cli-spinners: 2.9.2 - debug: 4.4.1(supports-color@8.1.1) - ejs: 3.1.10 - get-package-type: 0.1.0 - indent-string: 4.0.0 - is-wsl: 2.2.0 - lilconfig: 3.1.3 - minimatch: 9.0.5 - semver: 7.7.2 - string-width: 4.2.3 - supports-color: 8.1.1 - tinyglobby: 0.2.14 - widest-line: 3.1.0 - wordwrap: 1.0.0 - wrap-ansi: 7.0.0 - '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -8653,6 +8504,8 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/stylis@4.2.5': {} + '@types/through@0.0.33': dependencies: '@types/node': 22.15.32 @@ -8669,13 +8522,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript/vfs@1.6.1(typescript@5.8.3)': - dependencies: - debug: 4.4.1(supports-color@8.1.1) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - '@vitejs/plugin-react@4.6.0(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4))': dependencies: '@babel/core': 7.27.4 @@ -8718,7 +8564,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -8749,8 +8595,6 @@ snapshots: ansi-styles@6.2.1: {} - ansis@3.17.0: {} - any-promise@1.3.0: {} anymatch@3.1.3: @@ -8884,8 +8728,6 @@ snapshots: - encoding - supports-color - binary-extensions@2.3.0: {} - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -8983,9 +8825,9 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001720: {} + camelize@1.0.1: {} - case@1.6.3: {} + caniuse-lite@1.0.30001720: {} chalk@2.4.2: dependencies: @@ -9028,18 +8870,6 @@ snapshots: chardet@0.7.0: {} - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -9062,10 +8892,6 @@ snapshots: clean-stack@2.2.0: {} - clean-stack@3.0.1: - dependencies: - escape-string-regexp: 4.0.0 - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -9222,6 +9048,8 @@ snapshots: crypto-js@4.2.0: {} + css-color-keywords@1.0.0: {} + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -9230,6 +9058,12 @@ snapshots: domutils: 2.8.0 nth-check: 2.1.1 + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + css-what@6.1.0: {} cssesc@3.0.0: {} @@ -9282,11 +9116,9 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.1(supports-color@8.1.1): + debug@4.4.1: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 decimal.js-light@2.5.1: {} @@ -9519,9 +9351,6 @@ snapshots: optionalDependencies: source-map: 0.6.1 - esm@3.2.25: - optional: true - esprima@4.0.1: {} estraverse@5.3.0: {} @@ -9699,20 +9528,12 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 - fs-extra@11.3.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - fs-minipass@2.1.0: dependencies: minipass: 3.3.6 fs.realpath@1.0.0: {} - fs@0.0.1-security: {} - fsevents@2.3.3: optional: true @@ -9774,7 +9595,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -9802,8 +9623,6 @@ snapshots: globals@11.12.0: {} - globals@16.2.0: {} - globby@10.0.2: dependencies: '@types/glob': 7.2.0 @@ -9891,7 +9710,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -9902,14 +9721,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -10037,16 +9856,10 @@ snapshots: is-arrayish@0.3.2: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-docker@2.2.1: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -10065,8 +9878,6 @@ snapshots: is-number@7.0.0: {} - is-observable@2.1.0: {} - is-path-cwd@2.2.0: {} is-path-inside@3.0.3: {} @@ -10083,10 +9894,6 @@ snapshots: is-what@3.14.1: {} - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - isbinaryfile@4.0.10: {} isexe@2.0.0: {} @@ -10121,7 +9928,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -10902,8 +10709,6 @@ snapshots: object-inspect@1.13.4: {} - observable-fns@0.6.1: {} - on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -10969,7 +10774,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 get-uri: 6.0.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -11143,13 +10948,17 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.6: + postcss@8.4.49: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - prettier@3.0.3: {} + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 pretty-format@29.7.0: dependencies: @@ -11178,7 +10987,7 @@ snapshots: proxy-agent@6.5.0: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -11219,6 +11028,12 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-data-table-component@7.7.0(react@19.1.0)(styled-components@6.1.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): + dependencies: + deepmerge: 4.3.1 + react: 19.1.0 + styled-components: 6.1.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-day-picker@8.10.1(date-fns@4.1.0)(react@19.1.0): dependencies: date-fns: 4.1.0 @@ -11229,11 +11044,6 @@ snapshots: react: 19.1.0 scheduler: 0.26.0 - react-hook-form-persist@3.0.0(react-hook-form@7.58.1(react@19.1.0))(react@19.1.0): - dependencies: - react: 19.1.0 - react-hook-form: 7.58.1(react@19.1.0) - react-hook-form@7.58.1(react@19.1.0): dependencies: react: 19.1.0 @@ -11332,10 +11142,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - readdirp@4.1.2: {} recharts-scale@0.4.5: @@ -11511,7 +11317,7 @@ snapshots: dependencies: '@types/debug': 4.1.12 '@types/validator': 13.15.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 dottie: 2.0.6 inflection: 1.13.4 lodash: 4.17.21 @@ -11545,6 +11351,8 @@ snapshots: shallow-equal-object@1.1.1: {} + shallowequal@1.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -11591,8 +11399,6 @@ snapshots: slash@3.0.0: {} - slugify@1.6.6: {} - smart-buffer@4.2.0: {} snake-case@2.1.0: @@ -11602,7 +11408,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -11694,12 +11500,28 @@ snapshots: strip-json-comments@3.1.1: {} + styled-components@6.1.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@emotion/is-prop-valid': 1.2.2 + '@emotion/unitless': 0.8.1 + '@types/stylis': 4.2.5 + css-to-react-native: 3.2.0 + csstype: 3.1.3 + postcss: 8.4.49 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + shallowequal: 1.1.0 + stylis: 4.3.2 + tslib: 2.6.2 + stylis@4.2.0: {} + stylis@4.3.2: {} + stylus@0.62.0: dependencies: '@adobe/css-tools': 4.3.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 glob: 7.2.3 sax: 1.3.0 source-map: 0.7.4 @@ -11784,26 +11606,10 @@ snapshots: dependencies: any-promise: 1.3.0 - threads@1.7.0: - dependencies: - callsites: 3.1.0 - debug: 4.4.1(supports-color@8.1.1) - is-observable: 2.1.0 - observable-fns: 0.6.1 - optionalDependencies: - tiny-worker: 2.3.0 - transitivePeerDependencies: - - supports-color - through@2.3.8: {} tiny-invariant@1.3.3: {} - tiny-worker@2.3.0: - dependencies: - esm: 3.2.25 - optional: true - tinycolor2@1.6.0: {} tinyexec@0.3.2: {} @@ -11906,27 +11712,6 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-to-zod@3.15.0: - dependencies: - '@oclif/core': 4.4.0 - '@typescript/vfs': 1.6.1(typescript@5.8.3) - case: 1.6.3 - chokidar: 3.6.0 - fs-extra: 11.3.0 - inquirer: 8.2.6 - lodash: 4.17.21 - ora: 5.4.1 - prettier: 3.0.3 - rxjs: 7.8.2 - slash: 3.0.0 - threads: 1.7.0 - tslib: 2.8.1 - tsutils: 3.21.0(typescript@5.8.3) - typescript: 5.8.3 - zod: 3.25.67 - transitivePeerDependencies: - - supports-color - tsconfck@3.1.6(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -11939,6 +11724,8 @@ snapshots: tslib@1.14.1: {} + tslib@2.6.2: {} + tslib@2.8.1: {} tsup@8.4.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.19.4)(typescript@5.8.3): @@ -11947,7 +11734,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 esbuild: 0.25.5 joycon: 3.1.1 picocolors: 1.1.1 @@ -11968,11 +11755,6 @@ snapshots: - tsx - yaml - tsutils@3.21.0(typescript@5.8.3): - dependencies: - tslib: 1.14.1 - typescript: 5.8.3 - tsx@4.19.4: dependencies: esbuild: 0.25.5 @@ -12080,6 +11862,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + use-debounce@10.0.5(react@19.1.0): + dependencies: + react: 19.1.0 + use-deep-compare-effect@1.8.1(react@19.1.0): dependencies: '@babel/runtime': 7.27.6 @@ -12170,7 +11956,7 @@ snapshots: vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)): dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.8.3) optionalDependencies: @@ -12231,10 +12017,6 @@ snapshots: dependencies: string-width: 4.2.3 - widest-line@3.1.0: - dependencies: - string-width: 4.2.3 - winston-daily-rotate-file@5.0.0(winston@3.17.0): dependencies: file-stream-rotator: 0.6.1 @@ -12322,9 +12104,4 @@ snapshots: yoctocolors-cjs@2.1.2: {} - zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.67): - dependencies: - typescript: 5.8.3 - zod: 3.25.67 - zod@3.25.67: {}