Facturas de cliente
This commit is contained in:
parent
f39e55ca92
commit
6e0900afae
@ -15,7 +15,7 @@ const ProformasListPage = lazy(() =>
|
||||
);
|
||||
|
||||
const IssuedInvoiceListPage = lazy(() =>
|
||||
import("./issued-invoices/pages").then((m) => ({ default: m.IssuedInvoiceListPage }))
|
||||
import("./issued-invoices/list").then((m) => ({ default: m.IssuedInvoiceListPage }))
|
||||
);
|
||||
|
||||
/*const CustomerInvoiceAdd = lazy(() =>
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
export async function downloadInvoicePDFApi(
|
||||
dataSource: IDataSource,
|
||||
invoiceId: string,
|
||||
params?: Record<string, unknown>
|
||||
): Promise<Blob> {
|
||||
return dataSource.custom<Blob>({
|
||||
...params,
|
||||
path: `issued-invoices/${invoiceId}/report`,
|
||||
method: "get",
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./download-invoice-pdf.api";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./use-download-invoice-pdf.controller";
|
||||
@ -0,0 +1,53 @@
|
||||
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
||||
import * as React from "react";
|
||||
|
||||
import { useDownloadInvoicePDFQuery } from "../hooks";
|
||||
|
||||
interface PendingDownload {
|
||||
id: string;
|
||||
invoice_number: string;
|
||||
}
|
||||
|
||||
export function useDownloadInvoicePDFController() {
|
||||
const [pending, setPending] = React.useState<PendingDownload | null>(null);
|
||||
|
||||
const { data, isFetching, refetch } = useDownloadInvoicePDFQuery(pending?.id);
|
||||
|
||||
// Efecto: cuando hay blob + pending, disparamos la descarga
|
||||
React.useEffect(() => {
|
||||
if (!(pending && data)) return;
|
||||
|
||||
const blob = data;
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `invoice-${pending.invoice_number}.pdf`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
showSuccessToast(
|
||||
"Descarga iniciada",
|
||||
`La factura ${pending.invoice_number} se está descargando.`
|
||||
);
|
||||
|
||||
setPending(null);
|
||||
}, [data, pending]);
|
||||
|
||||
const download = (id: string, invoice_number: string) => {
|
||||
setPending({ id, invoice_number });
|
||||
|
||||
// refetch se dispara en un efecto, o aquí si prefieres:
|
||||
refetch().catch((err) => {
|
||||
showErrorToast(
|
||||
"Error al descargar",
|
||||
err instanceof Error ? err.message : "Error desconocido"
|
||||
);
|
||||
setPending(null);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
download, // (id, reference) => void
|
||||
isLoading: isFetching,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./use-download-invoice-pdf-query";
|
||||
@ -0,0 +1,55 @@
|
||||
// features/invoices/view-pdf/hooks/use-download-invoice-pdf.ts
|
||||
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type QueryKey, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { downloadInvoicePDFApi } from "../api";
|
||||
|
||||
export const ISSUED_INVOICE_QUERY_KEY = (id: string): QueryKey => ["issued_invoice", id] as const;
|
||||
|
||||
type DownloadInvoicePDFOptions = {
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export function useDownloadInvoicePDFQuery(
|
||||
invoiceId?: string,
|
||||
options?: DownloadInvoicePDFOptions
|
||||
) {
|
||||
const dataSource = useDataSource();
|
||||
const enabled = (options?.enabled ?? true) && !!invoiceId;
|
||||
|
||||
return useQuery({
|
||||
queryKey: ISSUED_INVOICE_QUERY_KEY(invoiceId ?? "unknown"),
|
||||
queryFn: async (context) => {
|
||||
if (!invoiceId) throw new Error("invoiceId is required");
|
||||
|
||||
const { signal } = context;
|
||||
return await downloadInvoicePDFApi(dataSource, invoiceId, {
|
||||
signal,
|
||||
});
|
||||
},
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
export function useQuery<
|
||||
TQueryFnData = unknown,
|
||||
TError = unknown,
|
||||
TData = TQueryFnData,
|
||||
TQueryKey extends QueryKey = QueryKey
|
||||
>
|
||||
|
||||
TQueryFnData: the type returned from the queryFn.
|
||||
TError: the type of Errors to expect from the queryFn.
|
||||
TData: the type our data property will eventually have.
|
||||
Only relevant if you use the select option,
|
||||
because then the data property can be different
|
||||
from what the queryFn returns.
|
||||
Otherwise, it will default to whatever the queryFn returns.
|
||||
TQueryKey: the type of our queryKey, only relevant
|
||||
if you use the queryKey that is passed to your queryFn.
|
||||
|
||||
*/
|
||||
@ -4,13 +4,14 @@ import type {
|
||||
IssuedInvoiceSummaryData,
|
||||
IssuedInvoiceSummaryPage,
|
||||
IssuedInvoiceSummaryPageData,
|
||||
} from "../schema";
|
||||
} from "../../types";
|
||||
|
||||
/**
|
||||
* Convierte el DTO completo de API a datos numéricos para el formulario.
|
||||
*/
|
||||
export const IssuedInvoiceSummaryDtoAdapter = {
|
||||
fromDto(pageDto: IssuedInvoiceSummaryPage, context?: unknown): IssuedInvoiceSummaryPageData {
|
||||
console.log(pageDto);
|
||||
return {
|
||||
...pageDto,
|
||||
items: pageDto.items.map(
|
||||
@ -0,0 +1,18 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { IssuedInvoiceSummaryPage } from "../../types";
|
||||
|
||||
export async function getIssuedInvoiceListApi(
|
||||
dataSource: IDataSource,
|
||||
signal: AbortSignal,
|
||||
criteria: CriteriaDTO
|
||||
) {
|
||||
const response = dataSource.getList<IssuedInvoiceSummaryPage>("issued-invoices", {
|
||||
signal,
|
||||
...criteria,
|
||||
});
|
||||
|
||||
//return mapIssuedInvoiceList(raw);
|
||||
return response;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./get-issued-invoice-list.api";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./use-issued-invoice-list.controller";
|
||||
export * from "./use-issued-invoice-list-page.controller.ts";
|
||||
@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
|
||||
import { useDownloadInvoicePDFController } from "../../download-pdf/controller";
|
||||
import type { IssuedInvoiceSummaryData } from "../../types";
|
||||
|
||||
import { useIssuedInvoiceListController } from "./use-issued-invoice-list.controller";
|
||||
|
||||
export function useIssuedInvoiceListPageController() {
|
||||
const listCtrl = useIssuedInvoiceListController();
|
||||
const downloadPDFCtrl = useDownloadInvoicePDFController();
|
||||
|
||||
const handleDownloadPDF = React.useCallback(
|
||||
(issuedInvoice: IssuedInvoiceSummaryData) => {
|
||||
downloadPDFCtrl.download(issuedInvoice.id, issuedInvoice.invoice_number);
|
||||
},
|
||||
[downloadPDFCtrl]
|
||||
);
|
||||
|
||||
return {
|
||||
listCtrl,
|
||||
|
||||
handleDownloadPDF,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { useDebounce } from "@repo/rdx-ui/components";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { IssuedInvoiceSummaryDtoAdapter } from "../adapters";
|
||||
import { useIssuedInvoiceListQuery } from "../hooks";
|
||||
|
||||
export const useIssuedInvoiceListController = () => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [search, setSearch] = useState("");
|
||||
const [status, setStatus] = useState("all");
|
||||
|
||||
const debouncedQ = useDebounce(search, 300);
|
||||
|
||||
const criteria = useMemo<CriteriaDTO>(() => {
|
||||
const baseFilters =
|
||||
status !== "all" ? [{ field: "status", operator: "CONTAINS", value: status }] : [];
|
||||
|
||||
return {
|
||||
q: debouncedQ || "",
|
||||
pageSize,
|
||||
pageNumber: pageIndex,
|
||||
order: "desc",
|
||||
orderBy: "invoice_date",
|
||||
filters: baseFilters,
|
||||
};
|
||||
}, [pageSize, pageIndex, debouncedQ, status]);
|
||||
|
||||
const query = useIssuedInvoiceListQuery({ criteria });
|
||||
const data = useMemo(
|
||||
() => (query.data ? IssuedInvoiceSummaryDtoAdapter.fromDto(query.data) : undefined),
|
||||
[query.data]
|
||||
);
|
||||
|
||||
const setSearchValue = (value: string) => setSearch(value.trim().replace(/\s+/g, " "));
|
||||
|
||||
const setStatusFilter = (newStatus: string) => setStatus(newStatus);
|
||||
|
||||
return {
|
||||
...query,
|
||||
data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
search,
|
||||
setPageIndex,
|
||||
setPageSize,
|
||||
setSearchValue,
|
||||
setStatusFilter,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./use-issued-invoice-list-query";
|
||||
@ -0,0 +1,38 @@
|
||||
import type { CriteriaDTO } from "@erp/core";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
|
||||
import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import type { IssuedInvoiceSummaryPage } from "../../types";
|
||||
import { getIssuedInvoiceListApi } from "../api";
|
||||
|
||||
export const ISSUED_INVOICES_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey => [
|
||||
"issued_invoices",
|
||||
{
|
||||
pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX,
|
||||
pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE,
|
||||
q: criteria?.q ?? "",
|
||||
filters: criteria?.filters ?? [],
|
||||
orderBy: criteria?.orderBy ?? "",
|
||||
order: criteria?.order ?? "",
|
||||
},
|
||||
];
|
||||
|
||||
type IssuedInvoicesQueryOptions = {
|
||||
enabled?: boolean;
|
||||
criteria?: CriteriaDTO;
|
||||
};
|
||||
|
||||
// Obtener todas las facturas
|
||||
export const useIssuedInvoiceListQuery = (options?: IssuedInvoicesQueryOptions) => {
|
||||
const dataSource = useDataSource();
|
||||
const enabled = options?.enabled ?? true;
|
||||
const criteria = options?.criteria ?? {};
|
||||
|
||||
return useQuery<IssuedInvoiceSummaryPage, DefaultError>({
|
||||
queryKey: ISSUED_INVOICES_QUERY_KEY(criteria),
|
||||
queryFn: async ({ signal }) => getIssuedInvoiceListApi(dataSource, signal, criteria),
|
||||
enabled,
|
||||
placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`)
|
||||
});
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./ui";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./issued-invoices-grid";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./issued-invoices-grid";
|
||||
export * from "./use-issued-invoices-grid-columns";
|
||||
@ -0,0 +1,125 @@
|
||||
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "../../../../../i18n";
|
||||
import type { IssuedInvoiceSummaryData, IssuedInvoiceSummaryPageData } from "../../../../types";
|
||||
|
||||
export type InvoiceUpdateCompProps = {
|
||||
data: IssuedInvoiceSummaryPageData;
|
||||
loading?: boolean;
|
||||
|
||||
columns: ColumnDef<IssuedInvoiceSummaryData, unknown>[];
|
||||
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
onPageChange?: (pageNumber: number) => void;
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
|
||||
onRowClick?: (
|
||||
row: IssuedInvoiceSummaryPageData,
|
||||
index: number,
|
||||
event: React.MouseEvent<HTMLTableRowElement>
|
||||
) => void;
|
||||
};
|
||||
|
||||
// Create new GridExample component
|
||||
export const IssuedInvoicesGrid = ({
|
||||
data,
|
||||
loading,
|
||||
columns,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
onRowClick,
|
||||
}: InvoiceUpdateCompProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { items, total_items } = data;
|
||||
|
||||
// Navegación accesible (click o teclado)
|
||||
/* const goToRow = useCallback(
|
||||
(id: string, newTab = false) => {
|
||||
const url = `/customer-invoices/${id}/edit`;
|
||||
newTab ? window.open(url, "_blank", "noopener,noreferrer") : navigate(url);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const onRowClicked = useCallback(
|
||||
(e: RowClickedEvent<any>) => {
|
||||
if (!e.data) return;
|
||||
const newTab = e.event instanceof MouseEvent && (e.event.metaKey || e.event.ctrlKey);
|
||||
goToRow(e.data.id, newTab);
|
||||
},
|
||||
[goToRow]
|
||||
);
|
||||
|
||||
const onCellKeyDown = useCallback(
|
||||
(e: CellKeyDownEvent<any>) => {
|
||||
if (!e.data) return;
|
||||
|
||||
const ev = e.event;
|
||||
if (!(ev && ev instanceof KeyboardEvent)) return;
|
||||
|
||||
const key = ev.key;
|
||||
if (key === "Enter" || key === " ") {
|
||||
ev.preventDefault();
|
||||
goToRow(e.data.id);
|
||||
}
|
||||
if ((ev.ctrlKey || ev.metaKey) && key === "Enter") {
|
||||
ev.preventDefault();
|
||||
goToRow(e.data.id, true);
|
||||
}
|
||||
},
|
||||
[goToRow]
|
||||
);
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
(invoice: IssuedInvoiceSummaryPageData, _i: number, e: React.MouseEvent) => {
|
||||
const url = `/customer-invoices/${invoice.id}/edit`;
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
return;
|
||||
}
|
||||
preview.open(invoice);
|
||||
},
|
||||
[preview]
|
||||
); */
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<SkeletonDataTable
|
||||
columns={columns.length}
|
||||
footerProps={{ pageIndex, pageSize, totalItems: total_items ?? 0 }}
|
||||
rows={Math.max(6, pageSize)}
|
||||
showFooter
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Render principal
|
||||
return (
|
||||
<>
|
||||
{/*<div className={preview.isPinned ? "flex-1 mr-[500px]" : "flex-1"}>*/}
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={items}
|
||||
enablePagination
|
||||
enableRowSelection
|
||||
manualPagination
|
||||
onPageChange={onPageChange}
|
||||
onPageSizeChange={onPageSizeChange}
|
||||
//onRowClick={handleRowClick}
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
readOnly
|
||||
totalItems={total_items}
|
||||
/>
|
||||
{/*</div>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -14,21 +14,20 @@ import {
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { DownloadIcon, MailIcon, MoreVerticalIcon, QrCodeIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import QRCode from "react-qr-code";
|
||||
import QrCode from "react-qr-code";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type { IssuedInvoiceSummaryData } from "../../../schema";
|
||||
import { useTranslation } from "../../../../../i18n";
|
||||
import type { IssuedInvoiceSummaryData } from "../../../../types";
|
||||
|
||||
type GridActionHandlers = {
|
||||
onDownloadPdf?: (proforma: IssuedInvoiceSummaryData) => void;
|
||||
onSendEmail?: (proforma: IssuedInvoiceSummaryData) => void;
|
||||
onDownloadPdf?: (issuedInvoice: IssuedInvoiceSummaryData) => void;
|
||||
onSendEmail?: (issuedInvoice: IssuedInvoiceSummaryData) => void;
|
||||
};
|
||||
|
||||
export function useIssuedInvoicesGridColumns(
|
||||
actionHandlers: GridActionHandlers = {}
|
||||
): ColumnDef<IssuedInvoiceSummaryData, unknown>[] {
|
||||
const { t } = useTranslation();
|
||||
const { onDownloadPdf, onSendEmail } = actionHandlers;
|
||||
|
||||
return React.useMemo<ColumnDef<IssuedInvoiceSummaryData>[]>(
|
||||
() => [
|
||||
@ -101,7 +100,7 @@ export function useIssuedInvoicesGridColumns(
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="m-0 p-3">
|
||||
<QRCode className="bg-white p-8" value={verifactu.url} />
|
||||
<QrCode className="bg-white p-8" value={verifactu.url} />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
@ -132,15 +131,15 @@ export function useIssuedInvoicesGridColumns(
|
||||
size: 140,
|
||||
minSize: 120,
|
||||
cell: ({ row }) => {
|
||||
const c = row.original.recipient;
|
||||
const r = row.original.recipient;
|
||||
return (
|
||||
<div className="flex items-start gap-1">
|
||||
<div className="min-w-0 grid gap-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-semibold truncate text-primary">{c.name}</span>
|
||||
<span className="font-semibold truncate text-primary">{r.name}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{c.tin && <span className="font-base truncate">{c.tin}</span>}
|
||||
{r.tin && <span className="font-base truncate">{r.tin}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -321,7 +320,7 @@ export function useIssuedInvoicesGridColumns(
|
||||
size: 64,
|
||||
minSize: 64,
|
||||
cell: ({ row }) => {
|
||||
const proforma = row.original;
|
||||
const issuedInvoice = row.original;
|
||||
const stop = (e: React.MouseEvent | React.KeyboardEvent) => e.stopPropagation();
|
||||
|
||||
return (
|
||||
@ -333,8 +332,8 @@ export function useIssuedInvoicesGridColumns(
|
||||
aria-label={t("common.download_pdf")}
|
||||
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDownloadPdf?.(proforma);
|
||||
stop(e);
|
||||
actionHandlers.onDownloadPdf?.(issuedInvoice);
|
||||
}}
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
@ -366,14 +365,14 @@ export function useIssuedInvoicesGridColumns(
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => onDownloadPdf?.(proforma)}
|
||||
onClick={() => actionHandlers.onDownloadPdf?.(issuedInvoice)}
|
||||
>
|
||||
<DownloadIcon className="mr-2 size-4" />
|
||||
{t("common.download_pdf")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSendEmail?.(proforma)}
|
||||
onClick={() => actionHandlers.onSendEmail?.(issuedInvoice)}
|
||||
>
|
||||
<MailIcon className="mr-2 size-4" />
|
||||
{t("common.send_email")}
|
||||
@ -389,6 +388,6 @@ export function useIssuedInvoicesGridColumns(
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, onDownloadPdf, onSendEmail]
|
||||
[t, actionHandlers]
|
||||
);
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./pages";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./issued-invoice-list-page";
|
||||
@ -0,0 +1,123 @@
|
||||
import { PageHeader, SimpleSearchInput } from "@erp/core/components";
|
||||
import { ErrorAlert } from "@erp/customers/components";
|
||||
import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
import { FilterIcon, LockIcon, PlusIcon } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import { useIssuedInvoiceListPageController } from "../../controllers";
|
||||
import { IssuedInvoicesGrid, useIssuedInvoicesGridColumns } from "../blocks";
|
||||
|
||||
export const IssuedInvoiceListPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { listCtrl, handleDownloadPDF } = useIssuedInvoiceListPageController();
|
||||
|
||||
const columns = useIssuedInvoicesGridColumns({
|
||||
onDownloadPdf: handleDownloadPDF,
|
||||
onSendEmail: () => null,
|
||||
});
|
||||
|
||||
// Hook con Sheet de shadcn
|
||||
/*const preview = usePinnedPreviewSheet<IssuedInvoiceSummaryPageData>({
|
||||
persistKey: "invoice-preview-pin",
|
||||
widthClass: "w-[500px]",
|
||||
});*/
|
||||
|
||||
if (listCtrl.isError || !listCtrl.data) {
|
||||
return (
|
||||
<AppContent>
|
||||
<ErrorAlert
|
||||
message={(listCtrl.error as Error)?.message || "Error al cargar el listado"}
|
||||
title={t("pages.issued_invoices.list.loadErrorTitle")}
|
||||
/>
|
||||
<BackHistoryButton />
|
||||
</AppContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader>
|
||||
<PageHeader
|
||||
description={t("pages.issued_invoices.list.description")}
|
||||
rightSlot={
|
||||
<Button
|
||||
aria-label={t("pages.issued_invoices.create.title")}
|
||||
onClick={() => navigate("/issued-invoices/create")}
|
||||
>
|
||||
<PlusIcon aria-hidden className="mr-2 size-4" />
|
||||
{t("pages.issued_invoices.create.title")}
|
||||
</Button>
|
||||
}
|
||||
title={t("pages.issued_invoices.list.title")}
|
||||
/>
|
||||
</AppHeader>
|
||||
<AppContent>
|
||||
<Alert className="bg-green-50 text-green-800">
|
||||
<LockIcon />
|
||||
<AlertTitle className="font-semibold text-green-800">¡Atención!</AlertTitle>
|
||||
<AlertDescription className="text-green-800">
|
||||
Las facturas de esta pantalla son de solo lectura. Se generan automáticamente al emitir
|
||||
una proforma aprobada y no se pueden modificar ni eliminar. Solo puedes descargar el PDF
|
||||
de cada factura.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{/* Search and filters */}
|
||||
<div className="flex items-center justify-between gap-16">
|
||||
<SimpleSearchInput
|
||||
loading={listCtrl.isLoading}
|
||||
onSearchChange={listCtrl.setSearchValue}
|
||||
/>
|
||||
|
||||
<Select defaultValue="all" onValueChange={listCtrl.setStatusFilter}>
|
||||
<SelectTrigger className="w-full sm:w-48">
|
||||
<FilterIcon aria-hidden className="mr-2 size-4" />
|
||||
<SelectValue placeholder={t("filters.status")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">{t("catalog.issuedInvoices.status.all.label")}</SelectItem>
|
||||
<SelectItem value="draft">
|
||||
{t("catalog.issuedInvoices.status.draft.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="sent">{t("catalog.issuedInvoices.status.sent.label")}</SelectItem>
|
||||
<SelectItem value="approved">
|
||||
{t("catalog.issuedInvoices.status.approved.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="rejected">
|
||||
{t("catalog.issuedInvoices.status.rejected.label")}
|
||||
</SelectItem>
|
||||
<SelectItem value="issued">
|
||||
{t("catalog.issuedInvoices.status.issued.label")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<IssuedInvoicesGrid
|
||||
columns={columns}
|
||||
data={listCtrl.data}
|
||||
loading={listCtrl.isLoading}
|
||||
onPageChange={listCtrl.setPageIndex}
|
||||
onPageSizeChange={listCtrl.setPageSize}
|
||||
// acciones rápidas del grid → page controller
|
||||
//onRowClick={(id) => navigate(`/issuedInvoices/${id}`)}
|
||||
pageIndex={listCtrl.pageIndex}
|
||||
pageSize={listCtrl.pageSize}
|
||||
/>
|
||||
</AppContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./use-issued-invoices-grid-columns";
|
||||
export * from "../../../list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns";
|
||||
|
||||
export * from "./use-issued-invoices-list";
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
import { PageHeader } from "@erp/core/components";
|
||||
import { ErrorAlert } from "@erp/customers/components";
|
||||
import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
|
||||
import { useIssuedInvoicesList } from "./hooks";
|
||||
import { IssuedInvoicesGrid } from "./ui/issued-invoices-grid";
|
||||
|
||||
export const IssuedInvoiceListPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const list = useIssuedInvoicesList();
|
||||
|
||||
if (list.isError || !list.data) {
|
||||
return (
|
||||
<AppContent>
|
||||
<ErrorAlert
|
||||
message={(list.error as Error)?.message || "Error al cargar el listado"}
|
||||
title={t("pages.issued_invoices.list.loadErrorTitle")}
|
||||
/>
|
||||
<BackHistoryButton />
|
||||
</AppContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader>
|
||||
<PageHeader
|
||||
description={t("pages.issued_invoices.list.description")}
|
||||
rightSlot={
|
||||
<Button
|
||||
aria-label={t("pages.issued_invoices.create.title")}
|
||||
onClick={() => navigate("/issued-invoices/create")}
|
||||
>
|
||||
<PlusIcon aria-hidden className="mr-2 size-4" />
|
||||
{t("pages.issued_invoices.create.title")}
|
||||
</Button>
|
||||
}
|
||||
title={t("pages.issued_invoices.list.title")}
|
||||
/>
|
||||
</AppHeader>
|
||||
<AppContent>
|
||||
<IssuedInvoicesGrid
|
||||
data={list.data}
|
||||
loading={list.isLoading}
|
||||
onPageChange={list.setPageIndex}
|
||||
onPageSizeChange={list.setPageSize}
|
||||
onSearchChange={list.setSearchValue}
|
||||
pageIndex={list.pageIndex}
|
||||
pageSize={list.pageSize}
|
||||
searchValue={list.search}
|
||||
/>
|
||||
</AppContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -138,7 +138,7 @@ export function useProformasGridColumns(
|
||||
return (
|
||||
<div>
|
||||
<a
|
||||
className="text-primary hover:underline"
|
||||
className="text-primary hover:underline font-semibold"
|
||||
href={`/customers/${proforma.customer_id}`}
|
||||
>
|
||||
{proforma.recipient.name}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user