Uecko_ERP/modules/customers/src/web/components/customers-list-grid.tsx

206 lines
5.4 KiB
TypeScript
Raw Normal View History

2025-08-11 17:49:52 +00:00
import { AG_GRID_LOCALE_ES } from "@ag-grid-community/locale";
2025-10-02 16:30:46 +00:00
import type { CellKeyDownEvent, RowClickedEvent, ValueFormatterParams } from "ag-grid-community";
2025-09-16 17:29:37 +00:00
import {
ColDef,
GridOptions,
SizeColumnsToContentStrategy,
SizeColumnsToFitGridStrategy,
SizeColumnsToFitProvidedWidthStrategy,
} from "ag-grid-community";
2025-10-02 16:30:46 +00:00
import { useCallback, useMemo, useState } from "react";
2025-08-11 17:49:52 +00:00
2025-09-29 08:42:46 +00:00
import { ErrorOverlay } from "@repo/rdx-ui/components";
2025-09-16 17:29:37 +00:00
import { Button } from "@repo/shadcn-ui/components";
2025-08-11 17:49:52 +00:00
import { AgGridReact } from "ag-grid-react";
2025-09-16 17:29:37 +00:00
import { ChevronRightIcon } from "lucide-react";
import { useNavigate } from "react-router-dom";
2025-08-11 17:49:52 +00:00
import { useCustomersQuery } from "../hooks";
import { useTranslation } from "../i18n";
import { CustomerStatusBadge } from "./customer-status-badge";
// Create new GridExample component
export const CustomersListGrid = () => {
const { t } = useTranslation();
2025-09-16 17:29:37 +00:00
const navigate = useNavigate();
const {
data: customersData,
isLoading: isLoadingCustomers,
isError: isLoadError,
error: loadError,
2025-09-23 11:48:19 +00:00
} = useCustomersQuery({
pagination: {
pageSize: 999,
},
});
2025-08-11 17:49:52 +00:00
// Column Definitions: Defines & controls grid columns.
2025-10-02 16:30:46 +00:00
const [columnDefs] = useState<ColDef[]>([
2025-09-16 17:29:37 +00:00
{ field: "name", headerName: t("pages.list.grid_columns.name"), minWidth: 300 },
2025-08-11 17:49:52 +00:00
{
2025-09-16 17:29:37 +00:00
field: "tin",
headerName: t("pages.list.grid_columns.tin"),
maxWidth: 120,
},
{
field: "city",
headerName: t("pages.list.grid_columns.city"),
},
{
field: "email_primary",
headerName: t("pages.list.grid_columns.email"),
},
{
field: "phone_primary",
headerName: t("pages.list.grid_columns.phone"),
maxWidth: 120,
},
2025-08-23 11:57:48 +00:00
2025-09-16 17:29:37 +00:00
{
field: "mobile_primary",
headerName: t("pages.list.grid_columns.mobile"),
maxWidth: 120,
},
{
field: "status",
2025-08-23 11:57:48 +00:00
headerName: t("pages.list.grid_columns.status"),
2025-09-24 15:09:37 +00:00
maxWidth: 135,
2025-08-11 17:49:52 +00:00
cellRenderer: (params: ValueFormatterParams) => {
return <CustomerStatusBadge status={params.value} />;
},
},
{
2025-09-16 17:29:37 +00:00
colId: "actions",
headerName: t("pages.list.grid_columns.actions", "Actions"),
cellRenderer: (params: ValueFormatterParams) => {
const { data } = params;
return (
<Button
variant='secondary'
size='icon'
className='size-8'
onClick={() => {
2025-09-22 17:43:55 +00:00
navigate(`/customers/${data.id}/edit`);
2025-09-16 17:29:37 +00:00
}}
>
<ChevronRightIcon />
</Button>
);
},
2025-08-11 17:49:52 +00:00
},
]);
2025-10-02 16:30:46 +00:00
// Navegación centralizada (click/teclado)
const goToRow = useCallback(
(id: string, newTab = false) => {
const url = `/customers/${id}`;
if (newTab) {
window.open(url, "_blank", "noopener,noreferrer");
} else {
navigate(url);
}
},
[navigate]
);
const onRowClicked = useCallback(
(e: RowClickedEvent<unknown>) => {
if (!e.data) return;
// Soporta Ctrl/Cmd click para nueva pestaña
const newTab = e.event instanceof MouseEvent && (e.event.metaKey || e.event.ctrlKey);
goToRow(e.data.id, newTab);
},
[goToRow]
);
const onCellKeyDown = useCallback(
(e: CellKeyDownEvent<unknown>) => {
if (!e.data) return;
const key = e.event.key;
// Enter o Space disparan navegación
if (key === "Enter" || key === " ") {
e.event.preventDefault();
goToRow(e.data.id);
}
// Ctrl/Cmd+Enter abre en nueva pestaña
if ((e.event.ctrlKey || e.event.metaKey) && key === "Enter") {
e.event.preventDefault();
goToRow(e.data.id, true);
}
},
[goToRow]
);
2025-09-16 17:29:37 +00:00
const autoSizeStrategy = useMemo<
| SizeColumnsToFitGridStrategy
| SizeColumnsToFitProvidedWidthStrategy
| SizeColumnsToContentStrategy
>(() => {
return {
type: "fitGridWidth",
defaultMinWidth: 100,
columnLimits: [{ colId: "actions", minWidth: 75, maxWidth: 75 }],
};
}, []);
const gridOptions: GridOptions = useMemo(
() => ({
2025-10-02 16:30:46 +00:00
columnDefs: columnDefs,
2025-09-16 17:29:37 +00:00
autoSizeStrategy: autoSizeStrategy,
defaultColDef: {
editable: false,
flex: 1,
filter: false,
sortable: false,
resizable: true,
},
pagination: true,
2025-09-23 11:48:19 +00:00
paginationPageSize: 15,
paginationPageSizeSelector: [10, 15, 20, 30, 50],
2025-09-16 17:29:37 +00:00
localeText: AG_GRID_LOCALE_ES,
2025-10-02 16:30:46 +00:00
// Evita conflictos con selección si la usas
suppressRowClickSelection: true,
// Clase visual de fila clickeable
getRowClass: () => "clickable-row",
// Accesibilidad con teclado
onCellKeyDown,
// Click en cualquier parte de la fila
onRowClicked,
// IDs estables (opcional pero recomendado)
getRowId: (params) => params.data.id,
2025-09-16 17:29:37 +00:00
}),
2025-10-02 16:30:46 +00:00
[autoSizeStrategy, columnDefs, onCellKeyDown, onRowClicked]
2025-09-16 17:29:37 +00:00
);
2025-08-11 17:49:52 +00:00
2025-09-29 08:42:46 +00:00
if (isLoadError) {
return (
<>
<ErrorOverlay
errorMessage={
(loadError as Error)?.message ??
t("pages.update.loadErrorMsg", "Inténtalo de nuevo más tarde.")
}
/>
</>
);
}
2025-08-11 17:49:52 +00:00
// Container: Defines the grid's theme & dimensions.
return (
<div
className='ag-theme-alpine'
style={{
height: "100%",
width: "100%",
}}
>
2025-09-16 17:29:37 +00:00
<AgGridReact
rowData={customersData?.items ?? []}
loading={isLoadingCustomers}
{...gridOptions}
/>
2025-08-11 17:49:52 +00:00
</div>
);
};