Facturas de cliente
This commit is contained in:
parent
75738dadde
commit
0e59a18cbb
@ -25,6 +25,7 @@
|
||||
"complexity": {
|
||||
"noForEach": "off",
|
||||
"noBannedTypes": "info",
|
||||
"noUselessFragments": "off",
|
||||
"useOptionalChain": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
|
||||
@ -1,150 +1,5 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {},
|
||||
"components": {
|
||||
"customer_invoice_taxes_multi_select": {
|
||||
"label": "Impuestos",
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
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";
|
||||
|
||||
export const MODULE_NAME = "CustomerInvoices";
|
||||
@ -10,13 +7,13 @@ const MODULE_VERSION = "1.0.0";
|
||||
export const CustomerInvoicesModuleManifiest: IModuleClient = {
|
||||
name: MODULE_NAME,
|
||||
version: MODULE_VERSION,
|
||||
dependencies: ["auth"],
|
||||
dependencies: ["auth", "Customers"],
|
||||
protected: true,
|
||||
layout: "app",
|
||||
|
||||
routes: (params: ModuleClientParams) => {
|
||||
i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
||||
i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
||||
// i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
||||
// i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
||||
return CustomerInvoiceRoutes(params);
|
||||
},
|
||||
};
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
"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",
|
||||
@ -43,6 +44,8 @@
|
||||
"slugify": "^1.6.6",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.5",
|
||||
"use-debounce": "^10.0.5",
|
||||
"use-query": "^1.0.2",
|
||||
"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 {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Input,
|
||||
Label,
|
||||
Separator,
|
||||
TableCell,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import {
|
||||
Building,
|
||||
Calendar,
|
||||
Edit,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
Plus,
|
||||
Search,
|
||||
Trash2,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import { Building, Calendar, Mail, MapPin, Phone, Plus, User } from "lucide-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",
|
||||
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 = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [newCustomer, setNewCustomer] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
company: "",
|
||||
address: "",
|
||||
const [search, setSearch] = useState("");
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [pageSize] = useState(10);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(undefined);
|
||||
|
||||
const [debouncedSearch] = useDebounce(search, 400);
|
||||
const paginated = filtered.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
|
||||
|
||||
const { data, isLoading, isError, error, refetch } = useCustomersQuery({
|
||||
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 (
|
||||
<div className='w-full max-w-md space-y-4'>
|
||||
<div className='space-y-0'>
|
||||
<Label className='m-0'>Cliente</Label>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='w-full justify-start bg-transparent'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<Search className='mr-2 h-4 w-4' />
|
||||
{selectedCustomer ? selectedCustomer.name : "Buscar cliente..."}
|
||||
<div className='space-y-4 max-w-2xl'>
|
||||
<div className='space-y-1'>
|
||||
<Label>Cliente</Label>
|
||||
<Button variant='outline' className='w-full justify-start' onClick={() => setOpen(true)}>
|
||||
<User className='h-4 w-4 mr-2' />
|
||||
{selectedCustomer ? selectedCustomer.name : "Seleccionar cliente"}
|
||||
</Button>
|
||||
</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 && (
|
||||
<Card className='border-primary'>
|
||||
<CardContent className='p-4'>
|
||||
<Card>
|
||||
<CardContent className='p-4 space-y-2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center space-x-3'>
|
||||
<User className='h-8 w-8 text-primary' />
|
||||
<div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<h3 className='font-semibold'>{selectedCustomer.name}</h3>
|
||||
<Badge
|
||||
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);
|
||||
}}
|
||||
<div className='flex items-center gap-2'>
|
||||
<User className='h-6 w-6 text-primary' />
|
||||
<h3 className='font-semibold'>{selectedCustomer.name}</h3>
|
||||
<Badge
|
||||
variant={selectedCustomer.status === "Activo" ? "default" : "secondary"}
|
||||
className='text-xs'
|
||||
>
|
||||
Ver Detalles
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
Cambiar
|
||||
</Button>
|
||||
{selectedCustomer.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<span className='text-sm text-muted-foreground'>{selectedCustomer.company}</span>
|
||||
</div>
|
||||
<p className='text-sm text-muted-foreground'>{selectedCustomer.email}</p>
|
||||
</CardContent>
|
||||
</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'>
|
||||
<DialogHeader>
|
||||
<DialogTitle className='flex items-center space-x-2'>
|
||||
<DialogTitle className='flex items-center gap-2'>
|
||||
<Plus className='h-5 w-5' />
|
||||
<span>Crear Nuevo Cliente</span>
|
||||
Nuevo Cliente
|
||||
</DialogTitle>
|
||||
<DialogDescription>Completa la información del nuevo cliente</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className='space-y-4'>
|
||||
<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>
|
||||
<p className='text-muted-foreground text-sm mb-4'>Formulario de creación pendiente…</p>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsCreateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<Button variant='outline' onClick={() => setIsCreateOpen(false)}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateCustomer}
|
||||
disabled={!newCustomer.name || !newCustomer.email}
|
||||
>
|
||||
<Button>
|
||||
<Plus className='h-4 w-4 mr-2' />
|
||||
Crear Cliente
|
||||
</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
|
||||
Crear
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@ -437,3 +250,54 @@ export const ClientSelector = () => {
|
||||
</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 "./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 "./layout/index.tsx";
|
||||
export * from "./loading-overlay/index.tsx";
|
||||
export * from "./lookup-dialog/index.tsx";
|
||||
export * from "./multi-select.tsx";
|
||||
export * from "./multiple-selector.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": {
|
||||
"clear_selection": "Clear",
|
||||
"close": "Close",
|
||||
"loading": "Loading...",
|
||||
"no_results": "No results found.",
|
||||
"select_options": "Select options",
|
||||
"select_all": "Select all",
|
||||
"no_results": "No results found."
|
||||
"select_all": "Select all"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,10 @@
|
||||
"multi_select": {
|
||||
"clear_selection": "Limpiar",
|
||||
"close": "Cerrar",
|
||||
"loading": "Cargando...",
|
||||
"no_results": "No se han encontrado resultados.",
|
||||
"select_options": "Seleccionar opciones",
|
||||
"select_all": "Seleccionar todo",
|
||||
"no_results": "No se han encontrado resultados."
|
||||
"select_all": "Seleccionar todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
125
pnpm-lock.yaml
125
pnpm-lock.yaml
@ -604,6 +604,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)
|
||||
@ -628,6 +631,12 @@ importers:
|
||||
tw-animate-css:
|
||||
specifier: ^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:
|
||||
specifier: ^3.25.67
|
||||
version: 3.25.67
|
||||
@ -1333,9 +1342,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==}
|
||||
|
||||
@ -1367,6 +1382,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:
|
||||
@ -3058,6 +3076,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==}
|
||||
|
||||
@ -3349,6 +3370,9 @@ 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==}
|
||||
|
||||
@ -3579,9 +3603,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'}
|
||||
@ -5349,6 +5380,10 @@ 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}
|
||||
@ -5413,6 +5448,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:
|
||||
@ -5753,6 +5794,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'}
|
||||
@ -5912,9 +5956,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
|
||||
@ -6108,6 +6162,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==}
|
||||
|
||||
@ -6248,12 +6305,22 @@ 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'}
|
||||
peerDependencies:
|
||||
react: '>=16.13'
|
||||
|
||||
use-query@1.0.2:
|
||||
resolution: {integrity: sha512-Ypdv/LMbs4OnjCCZ4QtWVCu5XKUUiHZSf0X0dToZahX9BXs5LmVkBCgLN8PVEGcuulNI7fL1SOukFGesWhO2EA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
use-sidecar@1.1.3:
|
||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -6834,10 +6901,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)':
|
||||
@ -6883,6 +6956,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
|
||||
@ -8639,6 +8714,8 @@ snapshots:
|
||||
|
||||
'@types/stack-utils@2.0.3': {}
|
||||
|
||||
'@types/stylis@4.2.5': {}
|
||||
|
||||
'@types/through@0.0.33':
|
||||
dependencies:
|
||||
'@types/node': 22.15.32
|
||||
@ -8969,6 +9046,8 @@ snapshots:
|
||||
|
||||
camelcase@6.3.0: {}
|
||||
|
||||
camelize@1.0.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001720: {}
|
||||
|
||||
case@1.6.3: {}
|
||||
@ -9208,6 +9287,8 @@ snapshots:
|
||||
|
||||
crypto-js@4.2.0: {}
|
||||
|
||||
css-color-keywords@1.0.0: {}
|
||||
|
||||
css-select@4.3.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
@ -9216,6 +9297,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: {}
|
||||
@ -11129,6 +11216,12 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
@ -11205,6 +11298,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
|
||||
@ -11536,6 +11635,8 @@ snapshots:
|
||||
|
||||
shallow-equal-object@1.1.1: {}
|
||||
|
||||
shallowequal@1.1.0: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
@ -11685,8 +11786,24 @@ 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
|
||||
@ -11929,6 +12046,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):
|
||||
@ -12070,12 +12189,18 @@ 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
|
||||
dequal: 2.0.3
|
||||
react: 19.1.0
|
||||
|
||||
use-query@1.0.2: {}
|
||||
|
||||
use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user