Facturas de cliente
This commit is contained in:
parent
8b3008f6d8
commit
af2997465e
@ -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`)
|
||||
});
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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 }) => (
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user