Facturas de cliente
This commit is contained in:
parent
75738dadde
commit
0e59a18cbb
@ -25,6 +25,7 @@
|
|||||||
"complexity": {
|
"complexity": {
|
||||||
"noForEach": "off",
|
"noForEach": "off",
|
||||||
"noBannedTypes": "info",
|
"noBannedTypes": "info",
|
||||||
|
"noUselessFragments": "off",
|
||||||
"useOptionalChain": "off"
|
"useOptionalChain": "off"
|
||||||
},
|
},
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
|
|||||||
@ -1,150 +1,5 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {},
|
||||||
"append_empty_row": "Añadir fila",
|
|
||||||
"append_empty_row_tooltip": "Añadir una fila vacía",
|
|
||||||
"duplicate_row": "Duplicar fila",
|
|
||||||
"insert_row_above": "Insertar fila encima",
|
|
||||||
"insert_row_below": "Insertar fila debajo",
|
|
||||||
"remove_row": "Eliminar"
|
|
||||||
},
|
|
||||||
"pages": {
|
|
||||||
"title": "Facturas",
|
|
||||||
"description": "Gestiona tus facturas",
|
|
||||||
"list": {
|
|
||||||
"title": "Lista de facturas",
|
|
||||||
"description": "Lista todas las facturas",
|
|
||||||
"grid_columns": {
|
|
||||||
"invoice_number": "Num. factura",
|
|
||||||
"invoice_series": "Serie",
|
|
||||||
"invoice_status": "Estado",
|
|
||||||
"issue_date": "Fecha",
|
|
||||||
"total_price": "Imp. total"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"title": "Crear factura",
|
|
||||||
"description": "Crear una nueva factura",
|
|
||||||
"back_to_list": "Volver a la lista"
|
|
||||||
},
|
|
||||||
"edit": {
|
|
||||||
"title": "Editar factura",
|
|
||||||
"description": "Editar la factura seleccionada"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "Eliminar factura",
|
|
||||||
"description": "Eliminar la factura seleccionada"
|
|
||||||
},
|
|
||||||
"view": {
|
|
||||||
"title": "Ver factura",
|
|
||||||
"description": "Ver los detalles de la factura seleccionada"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"draft": "Borrador",
|
|
||||||
"emitted": "Emitida",
|
|
||||||
"sent": "Enviada",
|
|
||||||
"received": "Recibida",
|
|
||||||
"rejected": "Rechazada"
|
|
||||||
},
|
|
||||||
"form_fields": {
|
|
||||||
"invoice_number": {
|
|
||||||
"label": "Num. factura",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"issue_date": {
|
|
||||||
"label": "Fecha",
|
|
||||||
"placeholder": "Seleccionar una fecha",
|
|
||||||
"description": "Fecha de emisión de la factura"
|
|
||||||
},
|
|
||||||
"invoice_series": {
|
|
||||||
"label": "Serie",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"operation_date": {
|
|
||||||
"label": "Intervención",
|
|
||||||
"placeholder": "Seleccionar una fecha",
|
|
||||||
"description": "Fecha de intervención de los trabajos"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"label": "Descripción",
|
|
||||||
"placeholder": "Descripción de la factura",
|
|
||||||
"description": "Descripción general de la factura"
|
|
||||||
},
|
|
||||||
"subtotal_price": {
|
|
||||||
"label": "Subtotal",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"discount": {
|
|
||||||
"label": "Dto (%)",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": "Porcentaje de descuento"
|
|
||||||
},
|
|
||||||
"discount_price": {
|
|
||||||
"label": "Imp. descuento",
|
|
||||||
"placeholder": "",
|
|
||||||
"desc": "Importe del descuento"
|
|
||||||
},
|
|
||||||
"total_price": {
|
|
||||||
"label": "Imp. total",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": "Importe total con el descuento ya aplicado"
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"label": "Notas",
|
|
||||||
"placeholder": "Notas adicionales sobre la factura",
|
|
||||||
"description": "Notas adicionales que se pueden incluir en la factura"
|
|
||||||
},
|
|
||||||
"items": {
|
|
||||||
"quantity": {
|
|
||||||
"label": "Cantidad",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"label": "Descripción",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"unit_price": {
|
|
||||||
"label": "Imp. unitario",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": "Importe unitario del artículo"
|
|
||||||
},
|
|
||||||
"subtotal_price": {
|
|
||||||
"label": "Subtotal",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"discount": {
|
|
||||||
"label": "Dto (%)",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": "Porcentaje de descuento"
|
|
||||||
},
|
|
||||||
"discount_price": {
|
|
||||||
"label": "Imp. descuento",
|
|
||||||
"placeholder": "",
|
|
||||||
"desc": "Importe del descuento"
|
|
||||||
},
|
|
||||||
"taxes": {
|
|
||||||
"label": "Impuestos",
|
|
||||||
"placeholder": "",
|
|
||||||
"desc": "Lista de impuestos aplicables"
|
|
||||||
},
|
|
||||||
"taxes_price": {
|
|
||||||
"label": "Imp. impuestos",
|
|
||||||
"placeholder": "",
|
|
||||||
"desc": "Importe de los impuestos"
|
|
||||||
},
|
|
||||||
"total_price": {
|
|
||||||
"label": "Imp. total",
|
|
||||||
"placeholder": "",
|
|
||||||
"description": "Importe total con el descuento ya aplicado"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
"components": {
|
||||||
"customer_invoice_taxes_multi_select": {
|
"customer_invoice_taxes_multi_select": {
|
||||||
"label": "Impuestos",
|
"label": "Impuestos",
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import { IModuleClient, ModuleClientParams } from "@erp/core/client";
|
import { IModuleClient, ModuleClientParams } from "@erp/core/client";
|
||||||
import i18next from "i18next";
|
|
||||||
import enResources from "../common/locales/en.json";
|
|
||||||
import esResources from "../common/locales/es.json";
|
|
||||||
import { CustomerInvoiceRoutes } from "./customer-invoice-routes";
|
import { CustomerInvoiceRoutes } from "./customer-invoice-routes";
|
||||||
|
|
||||||
export const MODULE_NAME = "CustomerInvoices";
|
export const MODULE_NAME = "CustomerInvoices";
|
||||||
@ -10,13 +7,13 @@ const MODULE_VERSION = "1.0.0";
|
|||||||
export const CustomerInvoicesModuleManifiest: IModuleClient = {
|
export const CustomerInvoicesModuleManifiest: IModuleClient = {
|
||||||
name: MODULE_NAME,
|
name: MODULE_NAME,
|
||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: ["auth"],
|
dependencies: ["auth", "Customers"],
|
||||||
protected: true,
|
protected: true,
|
||||||
layout: "app",
|
layout: "app",
|
||||||
|
|
||||||
routes: (params: ModuleClientParams) => {
|
routes: (params: ModuleClientParams) => {
|
||||||
i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
// i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
||||||
i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
// i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
||||||
return CustomerInvoiceRoutes(params);
|
return CustomerInvoiceRoutes(params);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
"i18next": "^25.1.1",
|
"i18next": "^25.1.1",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
"react-data-table-component": "^7.7.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
@ -43,6 +44,8 @@
|
|||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.5",
|
||||||
|
"use-debounce": "^10.0.5",
|
||||||
|
"use-query": "^1.0.2",
|
||||||
"zod": "^3.25.67"
|
"zod": "^3.25.67"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
modules/customers/src/common/dto/index.ts
Normal file
2
modules/customers/src/common/dto/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./request";
|
||||||
|
export * from "./response";
|
||||||
1
modules/customers/src/common/dto/request/index.ts
Normal file
1
modules/customers/src/common/dto/request/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./list-customers.query.dto";
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO que transporta los parámetros de la consulta (paginación, filtros, etc.)
|
||||||
|
* para la búsqueda de clientes.
|
||||||
|
*
|
||||||
|
* Este DTO es utilizado por el endpoint:
|
||||||
|
* `GET /customers` (listado / búsqueda de clientes).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const operatorEnum = z.enum([
|
||||||
|
"CONTAINS",
|
||||||
|
"NOT_CONTAINS",
|
||||||
|
"NOT_EQUALS",
|
||||||
|
"GREATER_THAN",
|
||||||
|
"GREATER_THAN_OR_EQUAL",
|
||||||
|
"LOWER_THAN",
|
||||||
|
"LOWER_THAN_OR_EQUAL",
|
||||||
|
"EQUALS",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filterSchema = z.object({
|
||||||
|
field: z.string(),
|
||||||
|
operator: operatorEnum,
|
||||||
|
value: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListCustomersQuerySchema = z.object({
|
||||||
|
filters: z.array(filterSchema).optional(),
|
||||||
|
|
||||||
|
pageSize: z.coerce.number().int().positive().optional(),
|
||||||
|
pageNumber: z.coerce.number().int().nonnegative().optional(),
|
||||||
|
|
||||||
|
orderBy: z.string().optional(),
|
||||||
|
order: z.enum(["asc", "desc"]).default("asc").optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ListCustomersQueryDTO = z.infer<typeof ListCustomersQuerySchema>;
|
||||||
1
modules/customers/src/common/dto/response/index.ts
Normal file
1
modules/customers/src/common/dto/response/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./list-customers.result.dto";
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { MetadataSchema, createListViewSchema } from "@erp/core";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
export const ListCustomersResultSchema = createListViewSchema(
|
||||||
|
z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
reference: z.string().optional(),
|
||||||
|
|
||||||
|
is_freelancer: z.boolean(),
|
||||||
|
name: z.string(),
|
||||||
|
trade_name: z.string().optional(),
|
||||||
|
tin: z.string(),
|
||||||
|
|
||||||
|
street: z.string(),
|
||||||
|
city: z.string(),
|
||||||
|
state: z.string(),
|
||||||
|
postal_code: z.string(),
|
||||||
|
country: z.string(),
|
||||||
|
|
||||||
|
email: z.email(),
|
||||||
|
phone: z.string(),
|
||||||
|
|
||||||
|
default_tax: z.number(),
|
||||||
|
status: z.string(),
|
||||||
|
lang_code: z.string(),
|
||||||
|
currency_code: z.string(),
|
||||||
|
|
||||||
|
metadata: MetadataSchema.optional(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export type ListCustomersResultDTO = z.infer<typeof ListCustomersResultSchema>;
|
||||||
14
modules/customers/src/common/locales/en.json
Normal file
14
modules/customers/src/common/locales/en.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"common": {},
|
||||||
|
"components": {
|
||||||
|
"entity_selector": {
|
||||||
|
"close": "Close",
|
||||||
|
"select_entity": "Select entity",
|
||||||
|
"create_new_entity": "Create new entity",
|
||||||
|
"search_entity": "Search entity",
|
||||||
|
"no_entities_found": "No results found for \"{{search}}\"",
|
||||||
|
"select_or_create": "Select an item from the list or create a new one.",
|
||||||
|
"create_label": "Create new item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
modules/customers/src/common/locales/es.json
Normal file
14
modules/customers/src/common/locales/es.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"common": {},
|
||||||
|
"components": {
|
||||||
|
"entity_selector": {
|
||||||
|
"close": "Cerrar",
|
||||||
|
"select_entity": "Seleccionar entidad",
|
||||||
|
"create_new_entity": "Crear nueva entidad",
|
||||||
|
"search_entity": "Buscar entidad",
|
||||||
|
"no_entities_found": "No se encontraron resultados para \"{{search}}\"",
|
||||||
|
"select_or_create": "Seleccione un elemento de la lista o cree uno nuevo.",
|
||||||
|
"create_label": "Crear nuevo elemento"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,42 +1,58 @@
|
|||||||
"use client";
|
import { LookupDialog } from "@repo/rdx-ui/components";
|
||||||
|
import DataTable, { TableColumn } from "react-data-table-component";
|
||||||
|
import { useDebounce } from "use-debounce";
|
||||||
|
|
||||||
import { generateUUIDv4 } from "@repo/rdx-utils";
|
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CommandDialog,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
CommandList,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Input,
|
|
||||||
Label,
|
Label,
|
||||||
Separator,
|
TableCell,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
import {
|
import { Building, Calendar, Mail, MapPin, Phone, Plus, User } from "lucide-react";
|
||||||
Building,
|
|
||||||
Calendar,
|
|
||||||
Edit,
|
|
||||||
Mail,
|
|
||||||
MapPin,
|
|
||||||
Phone,
|
|
||||||
Plus,
|
|
||||||
Search,
|
|
||||||
Trash2,
|
|
||||||
User,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useCustomersQuery } from "../hooks";
|
||||||
|
|
||||||
const mockCustomers = [
|
type Customer = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
company: string;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: TableColumn<Customer>[] = [
|
||||||
|
{
|
||||||
|
name: "Nombre",
|
||||||
|
selector: (row) => row.name,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Email",
|
||||||
|
selector: (row) => row.email,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empresa",
|
||||||
|
selector: (row) => row.company,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Estado",
|
||||||
|
selector: (row) => row.status,
|
||||||
|
cell: (row) => (
|
||||||
|
<span className={row.status === "Activo" ? "text-green-600" : "text-gray-400"}>
|
||||||
|
{row.status}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockCustomers: Customer[] = [
|
||||||
{
|
{
|
||||||
id: "a1d2e3f4-5678-90ab-cdef-1234567890ab",
|
id: "a1d2e3f4-5678-90ab-cdef-1234567890ab",
|
||||||
name: "Juan Pérez",
|
name: "Juan Pérez",
|
||||||
@ -79,357 +95,154 @@ const mockCustomers = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
async function fetchClientes(search: string): Promise<Customer[]> {
|
||||||
|
await new Promise((res) => setTimeout(res, 500));
|
||||||
|
const mock: Customer[] = [
|
||||||
|
{
|
||||||
|
id: "a1",
|
||||||
|
name: "Juan Pérez",
|
||||||
|
email: "juan@email.com",
|
||||||
|
phone: "+34 600 123 456",
|
||||||
|
company: "Tech Corp",
|
||||||
|
address: "Madrid",
|
||||||
|
createdAt: "2024-01-15",
|
||||||
|
status: "Activo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "b1",
|
||||||
|
name: "María García",
|
||||||
|
email: "maria@email.com",
|
||||||
|
phone: "+34 600 789 012",
|
||||||
|
company: "Design Studio",
|
||||||
|
address: "Barcelona",
|
||||||
|
createdAt: "2024-02-20",
|
||||||
|
status: "Activo",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return mock.filter(
|
||||||
|
(c) =>
|
||||||
|
c.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
c.email.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
c.company.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const ClientSelector = () => {
|
export const ClientSelector = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
const [search, setSearch] = useState("");
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
|
const [pageSize] = useState(10);
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [selectedCustomer, setSelectedCustomer] = useState(undefined);
|
||||||
const [newCustomer, setNewCustomer] = useState({
|
|
||||||
name: "",
|
const [debouncedSearch] = useDebounce(search, 400);
|
||||||
email: "",
|
const paginated = filtered.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
|
||||||
phone: "",
|
|
||||||
company: "",
|
const { data, isLoading, isError, error, refetch } = useCustomersQuery({
|
||||||
address: "",
|
filters: [
|
||||||
|
{
|
||||||
|
field: "name",
|
||||||
|
operator: "CONTAINS",
|
||||||
|
value: debouncedSearch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "trade_name",
|
||||||
|
operator: "CONTAINS",
|
||||||
|
value: debouncedSearch,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageNumber,
|
||||||
|
pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCreateCustomer = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const createdCustomer = {
|
|
||||||
id: generateUUIDv4(),
|
|
||||||
...newCustomer,
|
|
||||||
createdAt: new Date().toISOString().split("T")[0],
|
|
||||||
status: "Activo",
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("Cliente creado:", createdCustomer);
|
|
||||||
setSelectedCustomer(createdCustomer);
|
|
||||||
setIsCreateModalOpen(false);
|
|
||||||
setNewCustomer({ name: "", email: "", phone: "", company: "", address: "" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditCustomer = () => {
|
|
||||||
console.log("Editar cliente:", selectedCustomer);
|
|
||||||
setIsDetailsModalOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteCustomer = () => {
|
|
||||||
console.log("Eliminar cliente:", selectedCustomer);
|
|
||||||
setSelectedCustomer(null);
|
|
||||||
setIsDetailsModalOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectCustomer = (customer) => {
|
|
||||||
console.log("Seleccionar cliente:", customer);
|
|
||||||
setSelectedCustomer(customer);
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full max-w-md space-y-4'>
|
<div className='space-y-4 max-w-2xl'>
|
||||||
<div className='space-y-0'>
|
<div className='space-y-1'>
|
||||||
<Label className='m-0'>Cliente</Label>
|
<Label>Cliente</Label>
|
||||||
<Button
|
<Button variant='outline' className='w-full justify-start' onClick={() => setOpen(true)}>
|
||||||
variant='outline'
|
<User className='h-4 w-4 mr-2' />
|
||||||
className='w-full justify-start bg-transparent'
|
{selectedCustomer ? selectedCustomer.name : "Seleccionar cliente"}
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Search className='mr-2 h-4 w-4' />
|
|
||||||
{selectedCustomer ? selectedCustomer.name : "Buscar cliente..."}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CommandDialog
|
|
||||||
open={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
className='[&>div]:max-w-3xl [&>div]:max-h-[80vh] [&>div]:w-full'
|
|
||||||
>
|
|
||||||
<CommandInput
|
|
||||||
placeholder='Buscar cliente por nombre, email o empresa...'
|
|
||||||
value={searchValue}
|
|
||||||
onValueChange={setSearchValue}
|
|
||||||
/>
|
|
||||||
<CommandList className='max-w-screen'>
|
|
||||||
<CommandEmpty>
|
|
||||||
<div className='p-6 text-center'>
|
|
||||||
<User className='h-12 w-12 mx-auto text-muted-foreground mb-4' />
|
|
||||||
<p className='text-sm text-muted-foreground mb-4'>
|
|
||||||
No se encontró ningún cliente con "{searchValue}"
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setNewCustomer({ ...newCustomer, name: searchValue });
|
|
||||||
setIsCreateModalOpen(true);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus className='h-4 w-4 mr-2' />
|
|
||||||
Crear Cliente
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CommandEmpty>
|
|
||||||
<CommandGroup heading='Clientes'>
|
|
||||||
{mockCustomers.map((customer) => (
|
|
||||||
<CommandItem
|
|
||||||
key={customer.id}
|
|
||||||
onSelect={() => handleSelectCustomer(customer)}
|
|
||||||
className='flex items-center space-x-3 p-3'
|
|
||||||
>
|
|
||||||
<User className='h-4 w-4 flex-shrink-0' />
|
|
||||||
<div className='flex-1 min-w-0'>
|
|
||||||
<div className='flex items-center space-x-2'>
|
|
||||||
<p className='font-medium truncate'>{customer.name}</p>
|
|
||||||
<Badge
|
|
||||||
variant={customer.status === "Activo" ? "default" : "secondary"}
|
|
||||||
className='text-xs'
|
|
||||||
>
|
|
||||||
{customer.status}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center space-x-4 text-xs text-muted-foreground'>
|
|
||||||
<span className='flex items-center'>
|
|
||||||
<Building className='h-3 w-3 mr-1' />
|
|
||||||
{customer.company}
|
|
||||||
</span>
|
|
||||||
<span className='flex items-center'>
|
|
||||||
<Mail className='h-3 w-3 mr-1' />
|
|
||||||
{customer.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
<Separator />
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem
|
|
||||||
onSelect={(e) => {
|
|
||||||
setIsCreateModalOpen(true);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
className='flex items-center space-x-3 p-3 text-primary'
|
|
||||||
>
|
|
||||||
<Plus className='h-4 w-4' />
|
|
||||||
<span>Crear nuevo cliente</span>
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</CommandDialog>
|
|
||||||
|
|
||||||
{selectedCustomer && (
|
{selectedCustomer && (
|
||||||
<Card className='border-primary'>
|
<Card>
|
||||||
<CardContent className='p-4'>
|
<CardContent className='p-4 space-y-2'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='flex items-center space-x-3'>
|
<div className='flex items-center gap-2'>
|
||||||
<User className='h-8 w-8 text-primary' />
|
<User className='h-6 w-6 text-primary' />
|
||||||
<div>
|
<h3 className='font-semibold'>{selectedCustomer.name}</h3>
|
||||||
<div className='flex items-center space-x-2'>
|
<Badge
|
||||||
<h3 className='font-semibold'>{selectedCustomer.name}</h3>
|
variant={selectedCustomer.status === "Activo" ? "default" : "secondary"}
|
||||||
<Badge
|
className='text-xs'
|
||||||
variant={selectedCustomer.status === "Activo" ? "default" : "secondary"}
|
|
||||||
className='text-xs'
|
|
||||||
>
|
|
||||||
{selectedCustomer.status}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className='text-sm text-muted-foreground'>{selectedCustomer.company}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex space-x-2'>
|
|
||||||
<Button
|
|
||||||
variant='outline'
|
|
||||||
size='sm'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsDetailsModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Ver Detalles
|
{selectedCustomer.status}
|
||||||
</Button>
|
</Badge>
|
||||||
<Button
|
|
||||||
variant='outline'
|
|
||||||
size='sm'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cambiar
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span className='text-sm text-muted-foreground'>{selectedCustomer.company}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p className='text-sm text-muted-foreground'>{selectedCustomer.email}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
|
<LookupDialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
items={data?.items ?? []}
|
||||||
|
search={search}
|
||||||
|
onSearchChange={setSearch}
|
||||||
|
isLoading={isLoading}
|
||||||
|
title='Seleccionar cliente'
|
||||||
|
description='Busca un cliente por nombre, email o empresa'
|
||||||
|
onSelect={(item) => {
|
||||||
|
setSelectedCustomer(item);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
onCreate={() => {
|
||||||
|
setOpen(false);
|
||||||
|
console.log("Crear nuevo cliente");
|
||||||
|
}}
|
||||||
|
page={pageNumber}
|
||||||
|
perPage={perPage}
|
||||||
|
totalItems={filtered.length}
|
||||||
|
onPageChange={setPage}
|
||||||
|
renderItem={() => null} // No se usa con DataTable
|
||||||
|
renderContainer={(items) => (
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={items}
|
||||||
|
onRowClicked={(item) => {
|
||||||
|
setSelectedCustomer(item);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
pagination
|
||||||
|
paginationServer
|
||||||
|
paginationPerPage={perPage}
|
||||||
|
paginationTotalRows={filtered.length}
|
||||||
|
onChangePage={(p) => setPage(p)}
|
||||||
|
highlightOnHover
|
||||||
|
pointerOnHover
|
||||||
|
noDataComponent='No se encontraron resultados'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
|
||||||
<DialogContent className='max-w-md'>
|
<DialogContent className='max-w-md'>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className='flex items-center space-x-2'>
|
<DialogTitle className='flex items-center gap-2'>
|
||||||
<Plus className='h-5 w-5' />
|
<Plus className='h-5 w-5' />
|
||||||
<span>Crear Nuevo Cliente</span>
|
Nuevo Cliente
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>Completa la información del nuevo cliente</DialogDescription>
|
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className='space-y-4'>
|
<p className='text-muted-foreground text-sm mb-4'>Formulario de creación pendiente…</p>
|
||||||
<div className='grid grid-cols-2 gap-4'>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='name'>Nombre *</Label>
|
|
||||||
<Input
|
|
||||||
id='name'
|
|
||||||
value={newCustomer.name}
|
|
||||||
onChange={(e) => setNewCustomer({ ...newCustomer, name: e.target.value })}
|
|
||||||
placeholder='Nombre completo'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='company'>Empresa</Label>
|
|
||||||
<Input
|
|
||||||
id='company'
|
|
||||||
value={newCustomer.company}
|
|
||||||
onChange={(e) => setNewCustomer({ ...newCustomer, company: e.target.value })}
|
|
||||||
placeholder='Nombre de la empresa'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='email'>Email *</Label>
|
|
||||||
<Input
|
|
||||||
id='email'
|
|
||||||
type='email'
|
|
||||||
value={newCustomer.email}
|
|
||||||
onChange={(e) => setNewCustomer({ ...newCustomer, email: e.target.value })}
|
|
||||||
placeholder='correo@ejemplo.com'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='phone'>Teléfono</Label>
|
|
||||||
<Input
|
|
||||||
id='phone'
|
|
||||||
value={newCustomer.phone}
|
|
||||||
onChange={(e) => setNewCustomer({ ...newCustomer, phone: e.target.value })}
|
|
||||||
placeholder='+34 600 000 000'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='address'>Dirección</Label>
|
|
||||||
<Input
|
|
||||||
id='address'
|
|
||||||
value={newCustomer.address}
|
|
||||||
onChange={(e) => setNewCustomer({ ...newCustomer, address: e.target.value })}
|
|
||||||
placeholder='Dirección completa'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button variant='outline' onClick={() => setIsCreateOpen(false)}>
|
||||||
variant='outline'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsCreateModalOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button>
|
||||||
onClick={handleCreateCustomer}
|
|
||||||
disabled={!newCustomer.name || !newCustomer.email}
|
|
||||||
>
|
|
||||||
<Plus className='h-4 w-4 mr-2' />
|
<Plus className='h-4 w-4 mr-2' />
|
||||||
Crear Cliente
|
Crear
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<Dialog open={isDetailsModalOpen} onOpenChange={setIsDetailsModalOpen}>
|
|
||||||
<DialogContent className='max-w-md'>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className='flex items-center space-x-2'>
|
|
||||||
<User className='h-5 w-5' />
|
|
||||||
<span>Detalles del Cliente</span>
|
|
||||||
</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
{selectedCustomer && (
|
|
||||||
<div className='space-y-6'>
|
|
||||||
<div className='space-y-4'>
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div>
|
|
||||||
<h3 className='text-lg font-semibold'>{selectedCustomer.name}</h3>
|
|
||||||
<p className='text-sm text-muted-foreground'>{selectedCustomer.company}</p>
|
|
||||||
</div>
|
|
||||||
<Badge variant={selectedCustomer.status === "Activo" ? "default" : "secondary"}>
|
|
||||||
{selectedCustomer.status}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className='space-y-3'>
|
|
||||||
<div className='flex items-center space-x-3'>
|
|
||||||
<Mail className='h-4 w-4 text-muted-foreground' />
|
|
||||||
<div>
|
|
||||||
<Label className='text-xs font-medium text-muted-foreground'>EMAIL</Label>
|
|
||||||
<p className='font-medium'>{selectedCustomer.email}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center space-x-3'>
|
|
||||||
<Phone className='h-4 w-4 text-muted-foreground' />
|
|
||||||
<div>
|
|
||||||
<Label className='text-xs font-medium text-muted-foreground'>TELÉFONO</Label>
|
|
||||||
<p className='font-medium'>{selectedCustomer.phone}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-start space-x-3'>
|
|
||||||
<MapPin className='h-4 w-4 text-muted-foreground mt-1' />
|
|
||||||
<div>
|
|
||||||
<Label className='text-xs font-medium text-muted-foreground'>DIRECCIÓN</Label>
|
|
||||||
<p className='font-medium'>{selectedCustomer.address}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex items-center space-x-3'>
|
|
||||||
<Calendar className='h-4 w-4 text-muted-foreground' />
|
|
||||||
<div>
|
|
||||||
<Label className='text-xs font-medium text-muted-foreground'>
|
|
||||||
FECHA DE REGISTRO
|
|
||||||
</Label>
|
|
||||||
<p className='font-medium'>
|
|
||||||
{new Date(selectedCustomer.createdAt).toLocaleDateString("es-ES")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className='flex space-x-2'>
|
|
||||||
<Button className='flex-1'>
|
|
||||||
<Mail className='h-4 w-4 mr-2' />
|
|
||||||
Enviar Email
|
|
||||||
</Button>
|
|
||||||
<Button variant='outline' onClick={handleEditCustomer}>
|
|
||||||
<Edit className='h-4 w-4 mr-2' />
|
|
||||||
Editar
|
|
||||||
</Button>
|
|
||||||
<Button variant='outline' onClick={handleDeleteCustomer}>
|
|
||||||
<Trash2 className='h-4 w-4 mr-2' />
|
|
||||||
Eliminar
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant='outline' onClick={() => setIsDetailsModalOpen(false)}>
|
|
||||||
Cerrar
|
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@ -437,3 +250,54 @@ export const ClientSelector = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// COMPONENTES VISUALES
|
||||||
|
|
||||||
|
const CustomerCard = ({ customer }: { customer: Customer }) => (
|
||||||
|
<Card>
|
||||||
|
<CardContent className='p-4 space-y-2'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<User className='h-5 w-5' />
|
||||||
|
<span className='font-semibold'>{customer.name}</span>
|
||||||
|
<Badge variant={customer.status === "Activo" ? "default" : "secondary"}>
|
||||||
|
{customer.status}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className='text-sm text-muted-foreground flex flex-col gap-1'>
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
<Mail className='h-4 w-4' />
|
||||||
|
{customer.email}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
<Building className='h-4 w-4' />
|
||||||
|
{customer.company}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
<Phone className='h-4 w-4' />
|
||||||
|
{customer.phone}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
<MapPin className='h-4 w-4' />
|
||||||
|
{customer.address}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
<Calendar className='h-4 w-4' />
|
||||||
|
{new Date(customer.createdAt).toLocaleDateString("es-ES")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CustomerRow = ({ customer }: { customer: Customer }) => (
|
||||||
|
<>
|
||||||
|
<TableCell>{customer.name}</TableCell>
|
||||||
|
<TableCell>{customer.email}</TableCell>
|
||||||
|
<TableCell>{customer.company}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge variant={customer.status === "Activo" ? "default" : "secondary"}>
|
||||||
|
{customer.status}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|||||||
@ -1,2 +1,19 @@
|
|||||||
@source "./components";
|
@source "./components";
|
||||||
@source "./pages";
|
@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;
|
||||||
|
}
|
||||||
|
|||||||
1
modules/customers/src/web/hooks/index.ts
Normal file
1
modules/customers/src/web/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./use-customers-query";
|
||||||
24
modules/customers/src/web/hooks/use-customers-query.tsx
Normal file
24
modules/customers/src/web/hooks/use-customers-query.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useDataSource, useQueryKey } from "@erp/core/client";
|
||||||
|
import { ListCustomersQueryDTO, ListCustomersResultDTO } from "@erp/customer-invoices/common/dto";
|
||||||
|
import { UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
type UseCustomersQueryParams = ListCustomersQueryDTO;
|
||||||
|
|
||||||
|
// Obtener clientes
|
||||||
|
export const useCustomersQuery = (
|
||||||
|
params: UseCustomersQueryParams
|
||||||
|
): UseQueryResult<ListCustomersResultDTO, Error> => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const keys = useQueryKey();
|
||||||
|
|
||||||
|
return useQuery<ListCustomersResultDTO, Error>({
|
||||||
|
queryKey: keys().data().resource("customers").action("list").params(params).get(),
|
||||||
|
queryFn: (context) => {
|
||||||
|
const { signal } = context;
|
||||||
|
return dataSource.getList<ListCustomersResultDTO>("customers", {
|
||||||
|
signal,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
28
modules/customers/src/web/i18n.ts
Normal file
28
modules/customers/src/web/i18n.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||||
|
import enResources from "../common/locales/en.json";
|
||||||
|
import esResources from "../common/locales/es.json";
|
||||||
|
import { MODULE_NAME } from "./manifest";
|
||||||
|
|
||||||
|
const addMissingBundles = (i18n: any) => {
|
||||||
|
const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME);
|
||||||
|
const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME);
|
||||||
|
|
||||||
|
if (needsEn) {
|
||||||
|
i18n.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsEs) {
|
||||||
|
i18n.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTranslation = () => {
|
||||||
|
const { i18n } = useI18NextTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addMissingBundles(i18n);
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
return useI18NextTranslation(MODULE_NAME);
|
||||||
|
};
|
||||||
@ -5,6 +5,7 @@ export * from "./error-overlay.tsx";
|
|||||||
export * from "./form/index.tsx";
|
export * from "./form/index.tsx";
|
||||||
export * from "./layout/index.tsx";
|
export * from "./layout/index.tsx";
|
||||||
export * from "./loading-overlay/index.tsx";
|
export * from "./loading-overlay/index.tsx";
|
||||||
|
export * from "./lookup-dialog/index.tsx";
|
||||||
export * from "./multi-select.tsx";
|
export * from "./multi-select.tsx";
|
||||||
export * from "./multiple-selector.tsx";
|
export * from "./multiple-selector.tsx";
|
||||||
export * from "./scroll-to-top.tsx";
|
export * from "./scroll-to-top.tsx";
|
||||||
|
|||||||
1
packages/rdx-ui/src/components/lookup-dialog/index.tsx
Normal file
1
packages/rdx-ui/src/components/lookup-dialog/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./lookup-dialog.tsx";
|
||||||
143
packages/rdx-ui/src/components/lookup-dialog/lookup-dialog.tsx
Normal file
143
packages/rdx-ui/src/components/lookup-dialog/lookup-dialog.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
Input,
|
||||||
|
Separator,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import { PlusIcon, RefreshCwIcon } from "lucide-react";
|
||||||
|
|
||||||
|
type LookupDialogProps<T> = {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
items: T[];
|
||||||
|
isLoading: boolean;
|
||||||
|
isError?: boolean;
|
||||||
|
refetch?: () => void;
|
||||||
|
search: string;
|
||||||
|
onSearchChange: (value: string) => void;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
onSelect: (item: T) => void;
|
||||||
|
onCreate: () => void;
|
||||||
|
createLabel?: string;
|
||||||
|
maxWidth?: "lg" | "xl" | "2xl" | "3xl";
|
||||||
|
renderItem: (item: T) => React.ReactNode; // no se usa si se usa renderContainer
|
||||||
|
renderContainer: (items: T[]) => React.ReactNode;
|
||||||
|
emptyStateComponent?: React.ReactNode;
|
||||||
|
page?: number;
|
||||||
|
perPage?: number;
|
||||||
|
totalItems?: number;
|
||||||
|
onPageChange?: (page: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LookupDialog = <T,>({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
items,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
refetch,
|
||||||
|
search,
|
||||||
|
onSearchChange,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
searchPlaceholder,
|
||||||
|
onSelect,
|
||||||
|
onCreate,
|
||||||
|
createLabel,
|
||||||
|
maxWidth = "xl",
|
||||||
|
renderItem,
|
||||||
|
renderContainer,
|
||||||
|
emptyStateComponent,
|
||||||
|
page,
|
||||||
|
perPage = 10,
|
||||||
|
totalItems,
|
||||||
|
onPageChange,
|
||||||
|
}: LookupDialogProps<T>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const widthClass = {
|
||||||
|
lg: "custom-dialog-lg",
|
||||||
|
xl: "custom-dialog-xl",
|
||||||
|
"2xl": "custom-dialog-2xl",
|
||||||
|
"3xl": "custom-dialog-3xl",
|
||||||
|
}[maxWidth];
|
||||||
|
|
||||||
|
const showPagination = totalItems !== undefined && onPageChange !== undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className={`${widthClass} max-h-[90vh] overflow-y-auto`}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{title || t("components.entity_selector.select_entity")}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{description || t("components.entity_selector.select_or_create")}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className='flex items-center justify-between mb-4 gap-4'>
|
||||||
|
<Input
|
||||||
|
placeholder={searchPlaceholder || t("components.entity_selector.search_entity")}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
|
className='flex-1'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<p className='text-center text-muted-foreground py-8'>
|
||||||
|
{t("components.entity_selector.loading")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isError && (
|
||||||
|
<div className='text-center text-red-500 py-8 space-y-2'>
|
||||||
|
<p>Error al cargar los datos.</p>
|
||||||
|
{refetch && (
|
||||||
|
<Button variant='outline' onClick={refetch}>
|
||||||
|
<RefreshCwIcon className='h-4 w-4 mr-2' />
|
||||||
|
Reintentar
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && !isError && (
|
||||||
|
<>
|
||||||
|
{items.length === 0 ? (
|
||||||
|
emptyStateComponent || (
|
||||||
|
<p className='text-center text-sm text-muted-foreground mt-8'>
|
||||||
|
{t("components.entity_selector.no_entities_found")}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className='mt-4'>{renderContainer(items)}</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='mt-6 flex justify-center'>
|
||||||
|
<Button onClick={onCreate}>
|
||||||
|
<PlusIcon className='h-4 w-4 mr-2' />
|
||||||
|
{createLabel || t("components.entity_selector.create_label")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className='mt-4'>
|
||||||
|
<Button variant='outline' onClick={() => onOpenChange(false)}>
|
||||||
|
{t("components.entity_selector.close")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -16,9 +16,10 @@
|
|||||||
"multi_select": {
|
"multi_select": {
|
||||||
"clear_selection": "Clear",
|
"clear_selection": "Clear",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"no_results": "No results found.",
|
||||||
"select_options": "Select options",
|
"select_options": "Select options",
|
||||||
"select_all": "Select all",
|
"select_all": "Select all"
|
||||||
"no_results": "No results found."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,9 +16,10 @@
|
|||||||
"multi_select": {
|
"multi_select": {
|
||||||
"clear_selection": "Limpiar",
|
"clear_selection": "Limpiar",
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
|
"loading": "Cargando...",
|
||||||
|
"no_results": "No se han encontrado resultados.",
|
||||||
"select_options": "Seleccionar opciones",
|
"select_options": "Seleccionar opciones",
|
||||||
"select_all": "Seleccionar todo",
|
"select_all": "Seleccionar todo"
|
||||||
"no_results": "No se han encontrado resultados."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
125
pnpm-lock.yaml
125
pnpm-lock.yaml
@ -604,6 +604,9 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 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:
|
react-dom:
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 19.1.0(react@19.1.0)
|
version: 19.1.0(react@19.1.0)
|
||||||
@ -628,6 +631,12 @@ importers:
|
|||||||
tw-animate-css:
|
tw-animate-css:
|
||||||
specifier: ^1.3.5
|
specifier: ^1.3.5
|
||||||
version: 1.3.5
|
version: 1.3.5
|
||||||
|
use-debounce:
|
||||||
|
specifier: ^10.0.5
|
||||||
|
version: 10.0.5(react@19.1.0)
|
||||||
|
use-query:
|
||||||
|
specifier: ^1.0.2
|
||||||
|
version: 1.0.2
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.25.67
|
specifier: ^3.25.67
|
||||||
version: 3.25.67
|
version: 3.25.67
|
||||||
@ -1333,9 +1342,15 @@ packages:
|
|||||||
'@emotion/hash@0.9.2':
|
'@emotion/hash@0.9.2':
|
||||||
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
|
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':
|
'@emotion/is-prop-valid@1.3.1':
|
||||||
resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==}
|
resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==}
|
||||||
|
|
||||||
|
'@emotion/memoize@0.8.1':
|
||||||
|
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
|
||||||
|
|
||||||
'@emotion/memoize@0.9.0':
|
'@emotion/memoize@0.9.0':
|
||||||
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
|
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
|
||||||
|
|
||||||
@ -1367,6 +1382,9 @@ packages:
|
|||||||
'@emotion/unitless@0.10.0':
|
'@emotion/unitless@0.10.0':
|
||||||
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
|
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
|
||||||
|
|
||||||
|
'@emotion/unitless@0.8.1':
|
||||||
|
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
|
||||||
|
|
||||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0':
|
'@emotion/use-insertion-effect-with-fallbacks@1.2.0':
|
||||||
resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
|
resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3058,6 +3076,9 @@ packages:
|
|||||||
'@types/stack-utils@2.0.3':
|
'@types/stack-utils@2.0.3':
|
||||||
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
|
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
|
||||||
|
|
||||||
|
'@types/stylis@4.2.5':
|
||||||
|
resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==}
|
||||||
|
|
||||||
'@types/through@0.0.33':
|
'@types/through@0.0.33':
|
||||||
resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
|
resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
|
||||||
|
|
||||||
@ -3349,6 +3370,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
camelize@1.0.1:
|
||||||
|
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001720:
|
caniuse-lite@1.0.30001720:
|
||||||
resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==}
|
resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==}
|
||||||
|
|
||||||
@ -3579,9 +3603,16 @@ packages:
|
|||||||
crypto-js@4.2.0:
|
crypto-js@4.2.0:
|
||||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
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:
|
css-select@4.3.0:
|
||||||
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
|
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
|
||||||
|
|
||||||
|
css-to-react-native@3.2.0:
|
||||||
|
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
|
||||||
|
|
||||||
css-what@6.1.0:
|
css-what@6.1.0:
|
||||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -5349,6 +5380,10 @@ packages:
|
|||||||
postcss-value-parser@4.2.0:
|
postcss-value-parser@4.2.0:
|
||||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
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:
|
postcss@8.5.6:
|
||||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@ -5413,6 +5448,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||||
hasBin: true
|
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:
|
react-day-picker@8.10.1:
|
||||||
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
|
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5753,6 +5794,9 @@ packages:
|
|||||||
shallow-equal-object@1.1.1:
|
shallow-equal-object@1.1.1:
|
||||||
resolution: {integrity: sha512-9DDzYRlzCwF2CemeF0aOFk5T5KMrjG7HldcW7utwYhA/limuGHn3No8KhpDE8BrO7GLaSRJumNKReipZBybd7A==}
|
resolution: {integrity: sha512-9DDzYRlzCwF2CemeF0aOFk5T5KMrjG7HldcW7utwYhA/limuGHn3No8KhpDE8BrO7GLaSRJumNKReipZBybd7A==}
|
||||||
|
|
||||||
|
shallowequal@1.1.0:
|
||||||
|
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -5912,9 +5956,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
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:
|
stylis@4.2.0:
|
||||||
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
||||||
|
|
||||||
|
stylis@4.3.2:
|
||||||
|
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
|
||||||
|
|
||||||
stylus@0.62.0:
|
stylus@0.62.0:
|
||||||
resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==}
|
resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -6108,6 +6162,9 @@ packages:
|
|||||||
tslib@1.14.1:
|
tslib@1.14.1:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
|
|
||||||
|
tslib@2.6.2:
|
||||||
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
|
|
||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
@ -6248,12 +6305,22 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
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:
|
use-deep-compare-effect@1.8.1:
|
||||||
resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==}
|
resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==}
|
||||||
engines: {node: '>=10', npm: '>=6'}
|
engines: {node: '>=10', npm: '>=6'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.13'
|
react: '>=16.13'
|
||||||
|
|
||||||
|
use-query@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Ypdv/LMbs4OnjCCZ4QtWVCu5XKUUiHZSf0X0dToZahX9BXs5LmVkBCgLN8PVEGcuulNI7fL1SOukFGesWhO2EA==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
use-sidecar@1.1.3:
|
use-sidecar@1.1.3:
|
||||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -6834,10 +6901,16 @@ snapshots:
|
|||||||
|
|
||||||
'@emotion/hash@0.9.2': {}
|
'@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':
|
'@emotion/is-prop-valid@1.3.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/memoize': 0.9.0
|
'@emotion/memoize': 0.9.0
|
||||||
|
|
||||||
|
'@emotion/memoize@0.8.1': {}
|
||||||
|
|
||||||
'@emotion/memoize@0.9.0': {}
|
'@emotion/memoize@0.9.0': {}
|
||||||
|
|
||||||
'@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0)':
|
'@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0)':
|
||||||
@ -6883,6 +6956,8 @@ snapshots:
|
|||||||
|
|
||||||
'@emotion/unitless@0.10.0': {}
|
'@emotion/unitless@0.10.0': {}
|
||||||
|
|
||||||
|
'@emotion/unitless@0.8.1': {}
|
||||||
|
|
||||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)':
|
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
@ -8639,6 +8714,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/stack-utils@2.0.3': {}
|
'@types/stack-utils@2.0.3': {}
|
||||||
|
|
||||||
|
'@types/stylis@4.2.5': {}
|
||||||
|
|
||||||
'@types/through@0.0.33':
|
'@types/through@0.0.33':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.32
|
'@types/node': 22.15.32
|
||||||
@ -8969,6 +9046,8 @@ snapshots:
|
|||||||
|
|
||||||
camelcase@6.3.0: {}
|
camelcase@6.3.0: {}
|
||||||
|
|
||||||
|
camelize@1.0.1: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001720: {}
|
caniuse-lite@1.0.30001720: {}
|
||||||
|
|
||||||
case@1.6.3: {}
|
case@1.6.3: {}
|
||||||
@ -9208,6 +9287,8 @@ snapshots:
|
|||||||
|
|
||||||
crypto-js@4.2.0: {}
|
crypto-js@4.2.0: {}
|
||||||
|
|
||||||
|
css-color-keywords@1.0.0: {}
|
||||||
|
|
||||||
css-select@4.3.0:
|
css-select@4.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
@ -9216,6 +9297,12 @@ snapshots:
|
|||||||
domutils: 2.8.0
|
domutils: 2.8.0
|
||||||
nth-check: 2.1.1
|
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: {}
|
css-what@6.1.0: {}
|
||||||
|
|
||||||
cssesc@3.0.0: {}
|
cssesc@3.0.0: {}
|
||||||
@ -11129,6 +11216,12 @@ snapshots:
|
|||||||
|
|
||||||
postcss-value-parser@4.2.0: {}
|
postcss-value-parser@4.2.0: {}
|
||||||
|
|
||||||
|
postcss@8.4.49:
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.11
|
||||||
|
picocolors: 1.1.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
postcss@8.5.6:
|
postcss@8.5.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
@ -11205,6 +11298,12 @@ snapshots:
|
|||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
strip-json-comments: 2.0.1
|
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):
|
react-day-picker@8.10.1(date-fns@4.1.0)(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
date-fns: 4.1.0
|
date-fns: 4.1.0
|
||||||
@ -11536,6 +11635,8 @@ snapshots:
|
|||||||
|
|
||||||
shallow-equal-object@1.1.1: {}
|
shallow-equal-object@1.1.1: {}
|
||||||
|
|
||||||
|
shallowequal@1.1.0: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shebang-regex: 3.0.0
|
shebang-regex: 3.0.0
|
||||||
@ -11685,8 +11786,24 @@ snapshots:
|
|||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
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.2.0: {}
|
||||||
|
|
||||||
|
stylis@4.3.2: {}
|
||||||
|
|
||||||
stylus@0.62.0:
|
stylus@0.62.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@adobe/css-tools': 4.3.3
|
'@adobe/css-tools': 4.3.3
|
||||||
@ -11929,6 +12046,8 @@ snapshots:
|
|||||||
|
|
||||||
tslib@1.14.1: {}
|
tslib@1.14.1: {}
|
||||||
|
|
||||||
|
tslib@2.6.2: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
tsup@8.4.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.19.4)(typescript@5.8.3):
|
tsup@8.4.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.19.4)(typescript@5.8.3):
|
||||||
@ -12070,12 +12189,18 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.8
|
'@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):
|
use-deep-compare-effect@1.8.1(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.6
|
'@babel/runtime': 7.27.6
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
use-query@1.0.2: {}
|
||||||
|
|
||||||
use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0):
|
use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-node-es: 1.1.0
|
detect-node-es: 1.1.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user