Facturas de cliente

This commit is contained in:
David Arranz 2025-10-18 22:33:01 +02:00
parent 8b3008f6d8
commit af2997465e
5 changed files with 84 additions and 23 deletions

View File

@ -21,10 +21,11 @@ export const useInvoicesQuery = (options?: InvoicesQueryOptions) => {
queryKey: CUSTOMER_INVOICES_QUERY_KEY(criteria),
queryFn: async ({ signal }) => {
return await dataSource.getList<CustomerInvoicesPage>("customer-invoices", {
params: criteria,
...criteria,
signal,
});
},
enabled
enabled,
placeholderData: (previousData, previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`)
});
};

View File

@ -2,22 +2,30 @@ import type { CellKeyDownEvent, RowClickedEvent } from "ag-grid-community";
import { useCallback, useState } from "react";
import { DataTable } from '@repo/rdx-ui/components';
import { DataTable, LoadingOverlay } from '@repo/rdx-ui/components';
import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@repo/shadcn-ui/components';
import { FileDownIcon, FilterIcon, SearchIcon } from 'lucide-react';
import { useNavigate } from "react-router-dom";
import { useTranslation } from "../../i18n";
import { CustomerInvoicesPage } from '../../schemas';
import { InvoiceSummaryFormData, InvoicesPageFormData } from '../../schemas';
import { useInvoicesListColumns } from './use-invoices-list-columns';
export type InvoiceUpdateCompProps = {
invoicesPage: CustomerInvoicesPage;
invoicesPage: InvoicesPageFormData;
loading?: boolean;
onPageChange?: (pageIndex: number) => void;
onPageSizeChange?: (pageSize: number) => void;
}
// Create new GridExample component
export const InvoicesListGrid = ({ invoicesPage, loading }: InvoiceUpdateCompProps) => {
export const InvoicesListGrid = ({
invoicesPage,
loading,
onPageChange,
onPageSizeChange,
}: InvoiceUpdateCompProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
@ -25,8 +33,7 @@ export const InvoicesListGrid = ({ invoicesPage, loading }: InvoiceUpdateCompPro
const [statusFilter, setStatusFilter] = useState("todas");
const columns = useInvoicesListColumns();
const { items, page, total_pages, total_items } = invoicesPage;
const { items, page, per_page, total_pages, total_items } = invoicesPage;
// Navegación accesible (click o teclado)
@ -71,10 +78,20 @@ export const InvoicesListGrid = ({ invoicesPage, loading }: InvoiceUpdateCompPro
);
const handleRowClick = useCallback(
(invoice: InvoiceSummaryFormData, _index: number, e: React.MouseEvent) => {
const url = `/customer-invoices/${invoice.id}/edit`;
if (e.metaKey || e.ctrlKey) window.open(url, "_blank", "noopener,noreferrer");
else navigate(url);
},
[navigate]
);
// Render principal
return (
<>
{/* Filters and Actions */}
<div className="flex flex-col gap-4">
{/* Barra de filtros */}
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="relative flex-1">
<SearchIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
@ -103,8 +120,28 @@ export const InvoicesListGrid = ({ invoicesPage, loading }: InvoiceUpdateCompPro
</Button>
</div>
<div className="overflow-hidden">
<DataTable columns={columns} data={items} enablePagination={true} />
<DataTable
columns={columns}
data={items}
readOnly
enableRowSelection
enablePagination
manualPagination
pageIndex={page - 1}
pageSize={per_page}
totalItems={total_items}
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
onRowClick={handleRowClick}
/>
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
<LoadingOverlay />
</div>
)}
</div>
</>
</div>
);
};

View File

@ -2,7 +2,7 @@ import { ErrorAlert } from '@erp/customers/components';
import { AppBreadcrumb, AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components";
import { Button } from "@repo/shadcn-ui/components";
import { FilePenIcon, PlusIcon } from "lucide-react";
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useNavigate } from "react-router-dom";
import { InvoicesListGrid, PageHeader } from '../../components';
import { useInvoicesQuery } from '../../hooks';
@ -13,6 +13,9 @@ export const InvoiceListPage = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const [pageIndex, setOageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const {
data,
isLoading,
@ -20,7 +23,8 @@ export const InvoiceListPage = () => {
error,
} = useInvoicesQuery({
criteria: {
pageSize: 999,
pageSize,
pageNumber: pageIndex
}
});
@ -32,6 +36,15 @@ export const InvoiceListPage = () => {
}
}, [data]);
const handlePageChange = (newPageIndex: number) => {
// TanStack usa pageIndex 0-based → API usa 0-based también
setOageIndex(newPageIndex);
};
const handlePageSizeChange = (newSize: number) => {
setPageSize(newSize);
setOageIndex(0);
};
if (isError || !invoicesPageData) {
return (
@ -92,7 +105,12 @@ export const InvoiceListPage = () => {
</div>
</div>
<div className='flex flex-col w-full h-full py-3'>
<InvoicesListGrid invoicesPage={invoicesPageData} loading={isLoading} />
<InvoicesListGrid
invoicesPage={invoicesPageData}
loading={isLoading}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
</div>
</AppContent>
</>

View File

@ -4,16 +4,16 @@ import type { ColumnDef } from "@tanstack/react-table";
import * as React from "react";
import { CustomerInvoiceStatusBadge } from '../../components';
import { useTranslation } from '../../i18n';
import { InvoicesPageFormData } from '../../schemas/invoice-resume.form.schema';
import { InvoiceSummaryFormData } from '../../schemas/invoice-resume.form.schema';
export function useInvoicesListColumns(): ColumnDef<InvoicesPageFormData>[] {
export function useInvoicesListColumns(): ColumnDef<InvoiceSummaryFormData>[] {
//const { t, readOnly, currency_code, language_code } = useInvoiceContext();
const { t } = useTranslation();
// Atención: Memoizar siempre para evitar reconstrucciones y resets de estado de tabla
return React.useMemo<ColumnDef<InvoicesPageFormData>[]>(() => [
return React.useMemo<ColumnDef<InvoiceSummaryFormData>[]>(() => [
{
accessorKey: "invoice_number",
header: ({ column }) => (

View File

@ -27,14 +27,19 @@ interface DataTablePaginationProps<TData> {
export function DataTablePagination<TData>({ table, className }: DataTablePaginationProps<TData>) {
const { t } = useTranslation();
const { pageIndex, pageSize } = table.getState().pagination;
const pageCount = table.getPageCount() || 1;
const { pageIndex: rawIndex, pageSize: rawSize } = table.getState().pagination;
const totalRows = (table.options.meta as DataTableMeta<TData>)?.totalItems ?? table.getFilteredRowModel().rows.length;
// Normalización segura
const pageIndex = Number.isFinite(rawIndex) && rawIndex >= 0 ? rawIndex : 0;
const pageSize = Number.isFinite(rawSize) && rawSize > 0 ? rawSize : 10;
const pageCount = table.getPageCount() || Math.max(1, Math.ceil((totalRows || 0) / pageSize));
const hasSelected = table.getFilteredSelectedRowModel().rows.length > 0;
// Calcula rango visible (inicio-fin)
const start = totalRows === 0 ? 0 : pageIndex * pageSize + 1;
const end = Math.min((pageIndex + 1) * pageSize, totalRows);
// Rango visible (1-based en UI)
const start = totalRows > 0 ? pageIndex * pageSize + 1 : 0;
const end = totalRows > 0 ? Math.min(start + pageSize - 1, totalRows) : 0;
return (
<div className={cn(