Uecko_ERP/modules/customers/src/web/components/client-selector.tsx

295 lines
8.6 KiB
TypeScript
Raw Normal View History

2025-07-17 18:04:00 +00:00
import { LookupDialog } from "@repo/rdx-ui/components";
import DataTable, { TableColumn } from "react-data-table-component";
import { useDebounce } from "use-debounce";
2025-07-07 18:25:13 +00:00
2025-07-18 11:24:03 +00:00
import { buildTextFilters } from "@erp/core/client";
2025-07-07 18:25:13 +00:00
import {
Badge,
Button,
Card,
CardContent,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Label,
2025-07-17 18:04:00 +00:00
TableCell,
2025-07-07 18:25:13 +00:00
} from "@repo/shadcn-ui/components";
2025-07-17 18:04:00 +00:00
import { Building, Calendar, Mail, MapPin, Phone, Plus, User } from "lucide-react";
2025-07-07 18:25:13 +00:00
import { useState } from "react";
2025-09-16 17:29:37 +00:00
import { ListCustomersResponseDTO } from "../../common";
2025-07-17 18:04:00 +00:00
import { useCustomersQuery } from "../hooks";
2025-09-16 17:29:37 +00:00
type Customer = ListCustomersResponseDTO["items"][number];
2025-07-17 18:04:00 +00:00
const columns: TableColumn<Customer>[] = [
{
name: "Nombre",
selector: (row) => row.name,
sortable: true,
},
{
name: "Email",
selector: (row) => row.email,
2025-07-18 11:24:03 +00:00
sortable: true,
2025-07-17 18:04:00 +00:00
},
{
name: "Empresa",
2025-07-18 11:24:03 +00:00
selector: (row) => row.trade_name ?? row.metadata?.company_name ?? "-",
sortable: false,
2025-07-17 18:04:00 +00:00
},
{
name: "Estado",
selector: (row) => row.status,
2025-07-18 11:24:03 +00:00
sortable: false,
2025-07-17 18:04:00 +00:00
},
];
2025-07-07 18:25:13 +00:00
2025-07-17 18:04:00 +00:00
const mockCustomers: Customer[] = [
2025-07-07 18:25:13 +00:00
{
id: "a1d2e3f4-5678-90ab-cdef-1234567890ab",
name: "Juan Pérez",
email: "juan@email.com",
phone: "+34 600 123 456",
company: "Tech Corp",
address: "Calle Mayor 123, Madrid",
createdAt: "2024-01-15",
status: "Activo",
},
{
id: "b1d2e3f4-5678-90ab-cdef-1234567890ab",
name: "María García",
email: "maria@email.com",
phone: "+34 600 789 012",
company: "Design Studio",
address: "Av. Diagonal 456, Barcelona",
createdAt: "2024-02-20",
status: "Activo",
},
{
id: "c1d2e3f4-5678-90ab-cdef-1234567890ab",
name: "Carlos López",
email: "carlos@email.com",
phone: "+34 600 345 678",
company: "Marketing Plus",
address: "Gran Vía 789, Valencia",
createdAt: "2024-01-30",
status: "Inactivo",
},
{
id: "d1d2e3f4-5678-90ab-cdef-1234567890ab",
name: "Ana Martínez",
email: "ana@email.com",
phone: "+34 600 901 234",
company: "Consulting Group",
address: "Calle Sierpes 321, Sevilla",
createdAt: "2024-03-10",
status: "Activo",
},
];
2025-07-17 18:04:00 +00:00
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",
2025-07-07 18:25:13 +00:00
status: "Activo",
2025-07-17 18:04:00 +00:00
},
{
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())
);
}
2025-07-07 18:25:13 +00:00
2025-07-17 18:04:00 +00:00
export const ClientSelector = () => {
2025-07-18 11:24:03 +00:00
const [isCreateOpen, setIsCreateOpen] = useState(false);
2025-07-17 18:04:00 +00:00
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [pageNumber, setPageNumber] = useState(1);
const [pageSize] = useState(10);
2025-07-18 11:24:03 +00:00
const [selectedCustomer, setSelectedCustomer] = useState<Customer | undefined>(undefined);
2025-07-07 18:25:13 +00:00
2025-07-17 18:04:00 +00:00
const [debouncedSearch] = useDebounce(search, 400);
2025-07-07 18:25:13 +00:00
2025-07-18 11:24:03 +00:00
const { data, isLoading, isError, refetch } = useCustomersQuery({
filters: buildTextFilters(["name", "email", "trade_name"], debouncedSearch),
2025-07-17 18:04:00 +00:00
pageSize,
2025-07-18 11:24:03 +00:00
pageNumber,
2025-07-17 18:04:00 +00:00
});
2025-07-07 18:25:13 +00:00
2025-08-11 17:49:52 +00:00
const handleSelectClient = (event): void => {
event.preventDefault();
setOpen(true);
};
2025-07-07 18:25:13 +00:00
return (
2025-07-17 18:04:00 +00:00
<div className='space-y-4 max-w-2xl'>
<div className='space-y-1'>
<Label>Cliente</Label>
2025-08-11 17:49:52 +00:00
<Button variant='outline' className='w-full justify-start' onClick={handleSelectClient}>
2025-07-17 18:04:00 +00:00
<User className='h-4 w-4 mr-2' />
{selectedCustomer ? selectedCustomer.name : "Seleccionar cliente"}
2025-07-07 18:25:13 +00:00
</Button>
</div>
{selectedCustomer && (
2025-07-17 18:04:00 +00:00
<Card>
<CardContent className='p-4 space-y-2'>
2025-07-07 18:25:13 +00:00
<div className='flex items-center justify-between'>
2025-07-17 18:04:00 +00:00
<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'
2025-07-07 18:25:13 +00:00
>
2025-07-17 18:04:00 +00:00
{selectedCustomer.status}
</Badge>
2025-07-07 18:25:13 +00:00
</div>
2025-07-17 18:04:00 +00:00
<span className='text-sm text-muted-foreground'>{selectedCustomer.company}</span>
2025-07-07 18:25:13 +00:00
</div>
2025-07-17 18:04:00 +00:00
<p className='text-sm text-muted-foreground'>{selectedCustomer.email}</p>
2025-07-07 18:25:13 +00:00
</CardContent>
</Card>
)}
2025-07-17 18:04:00 +00:00
<LookupDialog
open={open}
onOpenChange={setOpen}
items={data?.items ?? []}
2025-07-18 11:24:03 +00:00
totalItems={data?.total_items ?? 0}
2025-07-17 18:04:00 +00:00
search={search}
onSearchChange={setSearch}
isLoading={isLoading}
2025-07-18 11:24:03 +00:00
isError={isError}
refetch={refetch}
2025-07-17 18:04:00 +00:00
title='Seleccionar cliente'
description='Busca un cliente por nombre, email o empresa'
2025-07-18 11:24:03 +00:00
onSelect={(customer) => {
setSelectedCustomer(customer);
2025-07-17 18:04:00 +00:00
setOpen(false);
}}
2025-09-12 18:07:38 +00:00
onCreate={(e) => {
e.preventDefault();
setOpen(true);
2025-07-17 18:04:00 +00:00
console.log("Crear nuevo cliente");
}}
page={pageNumber}
2025-07-18 11:24:03 +00:00
perPage={pageSize}
onPageChange={setPageNumber}
2025-07-17 18:04:00 +00:00
renderItem={() => null} // No se usa con DataTable
renderContainer={(items) => (
<DataTable
columns={columns}
data={items}
onRowClicked={(item) => {
setSelectedCustomer(item);
setOpen(false);
}}
pagination
paginationServer
2025-08-11 17:49:52 +00:00
paginationPerPage={pageSize}
paginationTotalRows={data?.total_items ?? 0}
onChangePage={(p) => setPageNumber(p)}
2025-07-17 18:04:00 +00:00
highlightOnHover
pointerOnHover
noDataComponent='No se encontraron resultados'
/>
)}
/>
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
2025-07-07 18:25:13 +00:00
<DialogContent className='max-w-md'>
<DialogHeader>
2025-07-17 18:04:00 +00:00
<DialogTitle className='flex items-center gap-2'>
2025-07-07 18:25:13 +00:00
<Plus className='h-5 w-5' />
2025-07-17 18:04:00 +00:00
Nuevo Cliente
2025-07-07 18:25:13 +00:00
</DialogTitle>
</DialogHeader>
2025-07-17 18:04:00 +00:00
<p className='text-muted-foreground text-sm mb-4'>Formulario de creación pendiente</p>
2025-07-07 18:25:13 +00:00
<DialogFooter>
2025-07-17 18:04:00 +00:00
<Button variant='outline' onClick={() => setIsCreateOpen(false)}>
2025-07-07 18:25:13 +00:00
Cancelar
</Button>
2025-07-17 18:04:00 +00:00
<Button>
2025-07-07 18:25:13 +00:00
<Plus className='h-4 w-4 mr-2' />
2025-07-17 18:04:00 +00:00
Crear
2025-07-07 18:25:13 +00:00
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
};
2025-07-17 18:04:00 +00:00
// 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>
</>
);