Uecko_ERP/modules/customers/src/web/components/client-selector.tsx
2025-09-16 19:29:37 +02:00

295 lines
8.6 KiB
TypeScript

import { LookupDialog } from "@repo/rdx-ui/components";
import DataTable, { TableColumn } from "react-data-table-component";
import { useDebounce } from "use-debounce";
import { buildTextFilters } from "@erp/core/client";
import {
Badge,
Button,
Card,
CardContent,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Label,
TableCell,
} from "@repo/shadcn-ui/components";
import { Building, Calendar, Mail, MapPin, Phone, Plus, User } from "lucide-react";
import { useState } from "react";
import { ListCustomersResponseDTO } from "../../common";
import { useCustomersQuery } from "../hooks";
type Customer = ListCustomersResponseDTO["items"][number];
const columns: TableColumn<Customer>[] = [
{
name: "Nombre",
selector: (row) => row.name,
sortable: true,
},
{
name: "Email",
selector: (row) => row.email,
sortable: true,
},
{
name: "Empresa",
selector: (row) => row.trade_name ?? row.metadata?.company_name ?? "-",
sortable: false,
},
{
name: "Estado",
selector: (row) => row.status,
sortable: false,
},
];
const mockCustomers: Customer[] = [
{
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",
},
];
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 [isCreateOpen, setIsCreateOpen] = useState(false);
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [pageNumber, setPageNumber] = useState(1);
const [pageSize] = useState(10);
const [selectedCustomer, setSelectedCustomer] = useState<Customer | undefined>(undefined);
const [debouncedSearch] = useDebounce(search, 400);
const { data, isLoading, isError, refetch } = useCustomersQuery({
filters: buildTextFilters(["name", "email", "trade_name"], debouncedSearch),
pageSize,
pageNumber,
});
const handleSelectClient = (event): void => {
event.preventDefault();
setOpen(true);
};
return (
<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={handleSelectClient}>
<User className='h-4 w-4 mr-2' />
{selectedCustomer ? selectedCustomer.name : "Seleccionar cliente"}
</Button>
</div>
{selectedCustomer && (
<Card>
<CardContent className='p-4 space-y-2'>
<div className='flex items-center justify-between'>
<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'
>
{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>
)}
<LookupDialog
open={open}
onOpenChange={setOpen}
items={data?.items ?? []}
totalItems={data?.total_items ?? 0}
search={search}
onSearchChange={setSearch}
isLoading={isLoading}
isError={isError}
refetch={refetch}
title='Seleccionar cliente'
description='Busca un cliente por nombre, email o empresa'
onSelect={(customer) => {
setSelectedCustomer(customer);
setOpen(false);
}}
onCreate={(e) => {
e.preventDefault();
setOpen(true);
console.log("Crear nuevo cliente");
}}
page={pageNumber}
perPage={pageSize}
onPageChange={setPageNumber}
renderItem={() => null} // No se usa con DataTable
renderContainer={(items) => (
<DataTable
columns={columns}
data={items}
onRowClicked={(item) => {
setSelectedCustomer(item);
setOpen(false);
}}
pagination
paginationServer
paginationPerPage={pageSize}
paginationTotalRows={data?.total_items ?? 0}
onChangePage={(p) => setPageNumber(p)}
highlightOnHover
pointerOnHover
noDataComponent='No se encontraron resultados'
/>
)}
/>
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
<DialogContent className='max-w-md'>
<DialogHeader>
<DialogTitle className='flex items-center gap-2'>
<Plus className='h-5 w-5' />
Nuevo Cliente
</DialogTitle>
</DialogHeader>
<p className='text-muted-foreground text-sm mb-4'>Formulario de creación pendiente</p>
<DialogFooter>
<Button variant='outline' onClick={() => setIsCreateOpen(false)}>
Cancelar
</Button>
<Button>
<Plus className='h-4 w-4 mr-2' />
Crear
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</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>
</>
);