295 lines
8.6 KiB
TypeScript
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 ClientSelectorModal = () => {
|
|
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>
|
|
</>
|
|
);
|