.
This commit is contained in:
parent
0ff5c39023
commit
d298754ee5
@ -44,6 +44,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@tanstack/react-query": "^5.51.23",
|
"@tanstack/react-query": "^5.51.23",
|
||||||
"@tanstack/react-table": "^8.20.1",
|
"@tanstack/react-table": "^8.20.1",
|
||||||
|
"@wojtekmaj/react-hooks": "^1.21.0",
|
||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
@ -52,6 +53,7 @@
|
|||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"joi": "^17.13.1",
|
"joi": "^17.13.1",
|
||||||
"lucide-react": "^0.427.0",
|
"lucide-react": "^0.427.0",
|
||||||
|
"print-js": "^1.6.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-currency-input-field": "^3.8.0",
|
"react-currency-input-field": "^3.8.0",
|
||||||
@ -60,6 +62,7 @@
|
|||||||
"react-hook-form": "^7.52.2",
|
"react-hook-form": "^7.52.2",
|
||||||
"react-hook-form-persist": "^3.0.0",
|
"react-hook-form-persist": "^3.0.0",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
|
"react-pdf": "^9.1.0",
|
||||||
"react-resizable-panels": "^2.0.23",
|
"react-resizable-panels": "^2.0.23",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"react-secure-storage": "^1.3.2",
|
"react-secure-storage": "^1.3.2",
|
||||||
@ -94,6 +97,7 @@
|
|||||||
"ts-jest": "^29.2.4",
|
"ts-jest": "^29.2.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.4.0"
|
"vite": "^5.4.0",
|
||||||
|
"vite-plugin-static-copy": "^1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,34 @@
|
|||||||
import { Badge, Button, Card, CardContent } from "@/ui";
|
import { Badge, Button, Card, CardContent, CardHeader } from "@/ui";
|
||||||
|
|
||||||
import { DataTable, DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
import {
|
||||||
|
DataTable,
|
||||||
|
DataTableSkeleton,
|
||||||
|
ErrorOverlay,
|
||||||
|
PDFViewer,
|
||||||
|
SimpleEmptyState,
|
||||||
|
} from "@/components";
|
||||||
|
|
||||||
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
||||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||||
import { IListQuotes_Response_DTO, MoneyValue, UTCDateValue } from "@shared/contexts";
|
import { IListQuotes_Response_DTO, MoneyValue, UTCDateValue } from "@shared/contexts";
|
||||||
import { ColumnDef, Row, Table } from "@tanstack/react-table";
|
import { ColumnDef, Row, Table } from "@tanstack/react-table";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useMemo } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuotes } from "../hooks";
|
import { useQuotes } from "../hooks";
|
||||||
|
|
||||||
export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
export const QuotesDataTable = ({
|
||||||
|
status = "all",
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
status?: string;
|
||||||
|
className?: string;
|
||||||
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
||||||
const { useList } = useQuotes();
|
const [focusedRow, setFocusedRow] = useState<Row<IListQuotes_Response_DTO>>();
|
||||||
|
const { useList, useReport } = useQuotes();
|
||||||
|
|
||||||
const { data, isPending, isError, error } = useList({
|
const { data, isPending, isError, error } = useList({
|
||||||
pagination: {
|
pagination: {
|
||||||
@ -26,6 +39,13 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
|||||||
quickSearchTerm: globalFilter,
|
quickSearchTerm: globalFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: reportData,
|
||||||
|
isPending: reportIsPending,
|
||||||
|
isError: reportIsError,
|
||||||
|
error: errorReport,
|
||||||
|
} = useReport(focusedRow ? focusedRow.original.id : undefined);
|
||||||
|
|
||||||
const columns = useMemo<ColumnDef<IListQuotes_Response_DTO, any>[]>(
|
const columns = useMemo<ColumnDef<IListQuotes_Response_DTO, any>[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -119,23 +139,31 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
|||||||
pageCount: data?.total_pages ?? -1,
|
pageCount: data?.total_pages ?? -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOnRowClick = (row: Row<IListQuotes_Response_DTO>) => {
|
||||||
|
console.log("setFocusedRow", row.id);
|
||||||
|
setFocusedRow(row);
|
||||||
|
};
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <ErrorOverlay subtitle={(error as Error).message} />;
|
return <ErrorOverlay subtitle={(error as Error).message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<div className='grid items-start flex-1 gap-4 sm:py-0 md:gap-8 lg:grid-cols-3 xl:grid-cols-3'>
|
||||||
<CardContent>
|
<Card className='grid items-start gap-4 auto-rows-max md:gap-8 lg:col-span-2'>
|
||||||
<DataTableSkeleton
|
<CardContent>
|
||||||
columnCount={6}
|
<DataTableSkeleton
|
||||||
searchableColumnCount={1}
|
columnCount={6}
|
||||||
filterableColumnCount={2}
|
searchableColumnCount={1}
|
||||||
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
|
filterableColumnCount={2}
|
||||||
shrinkZero
|
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
|
||||||
/>
|
shrinkZero
|
||||||
</CardContent>
|
/>
|
||||||
</Card>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,11 +177,29 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(reportData);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='grid items-start flex-1 gap-4 sm:py-0 md:gap-8 lg:grid-cols-3 xl:grid-cols-3'>
|
||||||
<DataTable table={table} paginationOptions={{ visible: true }}>
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
paginationOptions={{ visible: true }}
|
||||||
|
className='grid items-start gap-4 auto-rows-max md:gap-8 lg:col-span-2'
|
||||||
|
onRowClick={handleOnRowClick}
|
||||||
|
>
|
||||||
<DataTableToolbar table={table} />
|
<DataTableToolbar table={table} />
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</>
|
<div>
|
||||||
|
<Card className='overflow-hidden' x-chunk='dashboard-05-chunk-4'>
|
||||||
|
<CardHeader className='flex flex-row items-start bg-muted/50'></CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<PDFViewer
|
||||||
|
file={reportData}
|
||||||
|
className='aspect-[3/4] object-contain overflow-hidden border-2 border-dashed rounded-lg'
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { UseListQueryResult, useList, useOne, useSave } from "@/lib/hooks/useDataSource";
|
import { UseListQueryResult, useCustom, useList, useOne, useSave } from "@/lib/hooks/useDataSource";
|
||||||
import {
|
import {
|
||||||
IFilterItemDataProviderParam,
|
IFilterItemDataProviderParam,
|
||||||
IGetListDataProviderParams,
|
IGetListDataProviderParams,
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
IGetQuote_Response_DTO,
|
IGetQuote_Response_DTO,
|
||||||
IListQuotes_Response_DTO,
|
IListQuotes_Response_DTO,
|
||||||
IListResponse_DTO,
|
IListResponse_DTO,
|
||||||
|
IReportQuote_Response_DTO,
|
||||||
IUpdateQuote_Request_DTO,
|
IUpdateQuote_Request_DTO,
|
||||||
IUpdateQuote_Response_DTO,
|
IUpdateQuote_Response_DTO,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
@ -33,6 +34,11 @@ export type UseQuotesGetParamsType = {
|
|||||||
queryOptions?: Record<string, unknown>;
|
queryOptions?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UseQuotesReportParamsType = {
|
||||||
|
enabled?: boolean;
|
||||||
|
queryOptions?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
const quoteStatusFilter: Record<string, IFilterItemDataProviderParam> = {
|
const quoteStatusFilter: Record<string, IFilterItemDataProviderParam> = {
|
||||||
draft: {
|
draft: {
|
||||||
field: "status",
|
field: "status",
|
||||||
@ -85,7 +91,7 @@ export const useQuotes = () => {
|
|||||||
|
|
||||||
useOne: (id?: string, params?: UseQuotesGetParamsType) =>
|
useOne: (id?: string, params?: UseQuotesGetParamsType) =>
|
||||||
useOne<IGetQuote_Response_DTO>({
|
useOne<IGetQuote_Response_DTO>({
|
||||||
queryKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
queryKey: keys().data().resource("quotes").action("one").id(id).params().get(),
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
dataSource.getOne({
|
dataSource.getOne({
|
||||||
resource: "quotes",
|
resource: "quotes",
|
||||||
@ -94,6 +100,7 @@ export const useQuotes = () => {
|
|||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
...params,
|
...params,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
useCreate: () =>
|
useCreate: () =>
|
||||||
useSave<ICreateQuote_Response_DTO, TDataSourceError, ICreateQuote_Request_DTO>({
|
useSave<ICreateQuote_Response_DTO, TDataSourceError, ICreateQuote_Request_DTO>({
|
||||||
//mutationKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
//mutationKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
||||||
@ -131,5 +138,21 @@ export const useQuotes = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
useReport: (id?: string, params?: UseQuotesReportParamsType) =>
|
||||||
|
useCustom<IReportQuote_Response_DTO>({
|
||||||
|
queryKey: keys().data().resource("quotes").action("report").id(id).params().get(),
|
||||||
|
queryFn: () =>
|
||||||
|
dataSource.custom({
|
||||||
|
url: `${dataSource.getApiUrl()}/quotes/${id}/report`,
|
||||||
|
method: "get",
|
||||||
|
headers: {
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
enabled: !!id,
|
||||||
|
select: (data) => new Uint8Array(data),
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ColumnDef, Table as ReactTable, flexRender } from "@tanstack/react-table";
|
import { ColumnDef, Table as ReactTable, Row, flexRender } from "@tanstack/react-table";
|
||||||
import { PropsWithChildren, ReactNode } from "react";
|
import { PropsWithChildren, ReactNode } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -45,6 +45,7 @@ export type DataTableProps<TData> = PropsWithChildren<{
|
|||||||
footerClassName?: string;
|
footerClassName?: string;
|
||||||
rowClassName?: string;
|
rowClassName?: string;
|
||||||
cellClassName?: string;
|
cellClassName?: string;
|
||||||
|
onRowClick?: (row: Row<TData>) => void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function DataTable<TData>({
|
export function DataTable<TData>({
|
||||||
@ -60,6 +61,7 @@ export function DataTable<TData>({
|
|||||||
footerClassName,
|
footerClassName,
|
||||||
rowClassName,
|
rowClassName,
|
||||||
cellClassName,
|
cellClassName,
|
||||||
|
onRowClick,
|
||||||
}: DataTableProps<TData>) {
|
}: DataTableProps<TData>) {
|
||||||
const headerVisible = headerOptions?.visible;
|
const headerVisible = headerOptions?.visible;
|
||||||
|
|
||||||
@ -104,6 +106,8 @@ export function DataTable<TData>({
|
|||||||
{table.getRowModel().rows?.length ? (
|
{table.getRowModel().rows?.length ? (
|
||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
onClick={() => (onRowClick ? onRowClick(row) : null)}
|
||||||
|
tabIndex={0}
|
||||||
key={row.id}
|
key={row.id}
|
||||||
data-state={row.getIsSelected() && "selected"}
|
data-state={row.getIsSelected() && "selected"}
|
||||||
className={cn(row.getIsSelected() ? "bg-accent" : "", rowClassName)}
|
className={cn(row.getIsSelected() ? "bg-accent" : "", rowClassName)}
|
||||||
|
|||||||
156
client/src/components/PDFViewer/PDFViewer.tsx
Normal file
156
client/src/components/PDFViewer/PDFViewer.tsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { Button } from "@/ui";
|
||||||
|
import { useResizeObserver } from "@wojtekmaj/react-hooks";
|
||||||
|
import type { PDFDocumentProxy } from "pdfjs-dist";
|
||||||
|
import printJS from "print-js";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { Document, Page, pdfjs } from "react-pdf";
|
||||||
|
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
|
||||||
|
import "react-pdf/dist/esm/Page/TextLayer.css";
|
||||||
|
|
||||||
|
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
"pdfjs-dist/build/pdf.worker.min.mjs",
|
||||||
|
import.meta.url
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
cMapUrl: "/cmaps/",
|
||||||
|
standardFontDataUrl: "/standard_fonts/",
|
||||||
|
};
|
||||||
|
|
||||||
|
const maxWidth = 800;
|
||||||
|
const resizeObserverOptions = {};
|
||||||
|
|
||||||
|
export interface PDFViewerProps {
|
||||||
|
file?: Uint8Array;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PDFViewer = ({ file, className }: PDFViewerProps): JSX.Element => {
|
||||||
|
const [eventResize, setEventResize] = useState(false);
|
||||||
|
|
||||||
|
const [numPages, setNumPages] = useState(0);
|
||||||
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
|
|
||||||
|
const [width, setWidth] = useState(undefined);
|
||||||
|
//const parentRef = useRef(null);
|
||||||
|
//const canvasRef = useRef(null);
|
||||||
|
|
||||||
|
const [containerRef, setContainerRef] = useState<HTMLElement | null>(null);
|
||||||
|
const [containerWidth, setContainerWidth] = useState<number>();
|
||||||
|
|
||||||
|
const onResize = useCallback<ResizeObserverCallback>((entries) => {
|
||||||
|
const [entry] = entries;
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
setContainerWidth(entry.contentRect.width);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useResizeObserver(containerRef, resizeObserverOptions, onResize);
|
||||||
|
|
||||||
|
/*const hidePageCanvas = useCallback(() => {
|
||||||
|
const canvas = containerRef?.current?.querySelector("canvas");
|
||||||
|
if (canvas) canvas.style.visibility = "hidden";
|
||||||
|
}, [containerRef]);
|
||||||
|
|
||||||
|
const showPageCanvas = useCallback(() => {
|
||||||
|
const canvas = containerRef?.current?.querySelector("canvas");
|
||||||
|
if (canvas) canvas.style.visibility = "visible";
|
||||||
|
}, [containerRef]);
|
||||||
|
|
||||||
|
const onPageLoadSuccess = useCallback(() => {
|
||||||
|
hidePageCanvas();
|
||||||
|
}, [hidePageCanvas]);
|
||||||
|
|
||||||
|
const onPageRenderSuccess = useCallback(() => {
|
||||||
|
showPageCanvas();
|
||||||
|
}, [showPageCanvas]);
|
||||||
|
|
||||||
|
const onPageRenderError = useCallback(() => {
|
||||||
|
showPageCanvas();
|
||||||
|
}, [showPageCanvas]);
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onDocumentLoadSuccess({ numPages: nextNumPages }: PDFDocumentProxy): void {
|
||||||
|
setNumPages(nextNumPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function onDocumentLoadSuccess({ numPages: nextNumPages }: PDFDocumentProxy): void {
|
||||||
|
setPageNumber(1);
|
||||||
|
setNumPages(nextNumPages);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
const changePage = (offset: number) =>
|
||||||
|
setPageNumber((prevPage) =>
|
||||||
|
offset > 0 ? Math.min(prevPage + offset, numPages) : Math.max(prevPage + offset, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const goToNextPage = () => changePage(1);
|
||||||
|
const goToPrevPage = () => changePage(-1);
|
||||||
|
const goToFirstPage = () => setPageNumber(1);
|
||||||
|
const goToLastPage = () => setPageNumber(numPages);
|
||||||
|
|
||||||
|
/*useEffect(() => {
|
||||||
|
if (numPages > 0 || eventResize) {
|
||||||
|
const parentWidth = parentRef.current ? parentRef.current.offsetWidth : 0;
|
||||||
|
const canvasWidth = canvasRef.current ? canvasRef.current.width : 0;
|
||||||
|
|
||||||
|
console.log("Document => ", parentWidth);
|
||||||
|
console.log("Canvas => ", canvasWidth);
|
||||||
|
|
||||||
|
setWidth(parentWidth);
|
||||||
|
setEventResize(false);
|
||||||
|
}
|
||||||
|
}, [eventResize, numPages]);**/
|
||||||
|
|
||||||
|
//file={`data:application/pdf;base64,$(pdfBase64String)`}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col' ref={setContainerRef}>
|
||||||
|
<Document
|
||||||
|
options={options}
|
||||||
|
file={file}
|
||||||
|
onLoadSuccess={onDocumentLoadSuccess}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{Array.from(new Array(numPages), (_el, index) => (
|
||||||
|
<Page
|
||||||
|
canvasBackground={"white"}
|
||||||
|
key={`page_${index + 1}`}
|
||||||
|
pageNumber={index + 1}
|
||||||
|
width={containerWidth ? Math.min(containerWidth, maxWidth) : maxWidth}
|
||||||
|
//onLoadSuccess={onPageLoadSuccess}
|
||||||
|
//onRenderSuccess={onPageRenderSuccess}
|
||||||
|
//onRenderError={onPageRenderError}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Document>
|
||||||
|
<p className='text-center'>
|
||||||
|
Página {pageNumber} de {numPages}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<Button size={"icon"} variant='link' onClick={goToPrevPage}>
|
||||||
|
Prev
|
||||||
|
</Button>
|
||||||
|
<Button size={"icon"} variant='link' onClick={goToNextPage}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={"icon"}
|
||||||
|
variant='link'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
printJS({
|
||||||
|
printable: file,
|
||||||
|
type: "pdf",
|
||||||
|
showModal: false,
|
||||||
|
modalMessage: "Cargando...",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Imprimir react
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
client/src/components/PDFViewer/index.ts
Normal file
1
client/src/components/PDFViewer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './PDFViewer';
|
||||||
@ -10,6 +10,7 @@ export * from "./Forms";
|
|||||||
export * from "./Layout";
|
export * from "./Layout";
|
||||||
export * from "./LoadingIndicator";
|
export * from "./LoadingIndicator";
|
||||||
export * from "./LoadingOverlay";
|
export * from "./LoadingOverlay";
|
||||||
|
export * from "./PDFViewer";
|
||||||
export * from "./ProtectedRoute";
|
export * from "./ProtectedRoute";
|
||||||
//export * from "./SorteableDataTable";
|
//export * from "./SorteableDataTable";
|
||||||
export * from "./TailwindIndicator";
|
export * from "./TailwindIndicator";
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { IListResponse_DTO, INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@shared/contexts";
|
import { IListResponse_DTO, INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@shared/contexts";
|
||||||
import {
|
import {
|
||||||
ICreateOneDataProviderParams,
|
ICreateOneDataProviderParams,
|
||||||
|
ICustomDataProviderParam,
|
||||||
IDataSource,
|
IDataSource,
|
||||||
IFilterItemDataProviderParam,
|
IFilterItemDataProviderParam,
|
||||||
IGetListDataProviderParams,
|
IGetListDataProviderParams,
|
||||||
@ -18,6 +19,10 @@ export const createAxiosDataProvider = (
|
|||||||
): IDataSource => ({
|
): IDataSource => ({
|
||||||
name: () => "AxiosDataProvider",
|
name: () => "AxiosDataProvider",
|
||||||
|
|
||||||
|
getApiUrl: () => {
|
||||||
|
return apiUrl;
|
||||||
|
},
|
||||||
|
|
||||||
getList: async <R>(params: IGetListDataProviderParams): Promise<IListResponse_DTO<R>> => {
|
getList: async <R>(params: IGetListDataProviderParams): Promise<IListResponse_DTO<R>> => {
|
||||||
const { resource, quickSearchTerm, pagination, filters, sort } = params;
|
const { resource, quickSearchTerm, pagination, filters, sort } = params;
|
||||||
|
|
||||||
@ -111,6 +116,56 @@ export const createAxiosDataProvider = (
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
custom: async <R>(params: ICustomDataProviderParam): Promise<R> => {
|
||||||
|
const { url, method, headers, payload } = params;
|
||||||
|
const requestUrl = `${url}?`;
|
||||||
|
|
||||||
|
/*if (sort) {
|
||||||
|
const generatedSort = extractSortParams(sort);
|
||||||
|
if (generatedSort) {
|
||||||
|
const { _sort, _order } = generatedSort;
|
||||||
|
const sortQuery = {
|
||||||
|
_sort: _sort.join(","),
|
||||||
|
_order: _order.join(","),
|
||||||
|
};
|
||||||
|
requestUrl = `${requestUrl}&${queryString.stringify(sortQuery)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters) {
|
||||||
|
const filterQuery = extractFilterParams(filters);
|
||||||
|
requestUrl = `${requestUrl}&${queryString.stringify(filterQuery)}`;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*if (query) {
|
||||||
|
requestUrl = `${requestUrl}&${queryString.stringify(query)}`;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
httpClient.defaults.headers = {
|
||||||
|
...httpClient.defaults.headers,
|
||||||
|
...headers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let customResponse;
|
||||||
|
switch (method) {
|
||||||
|
case "put":
|
||||||
|
case "post":
|
||||||
|
case "patch":
|
||||||
|
customResponse = await httpClient[method]<R>(url, payload);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
customResponse = await httpClient.delete<R>(url);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
customResponse = await httpClient.get<R>(requestUrl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return customResponse.data;
|
||||||
|
},
|
||||||
|
|
||||||
/*getMany: async ({ resource }) => {
|
/*getMany: async ({ resource }) => {
|
||||||
const { body } = await httpClient.request({
|
const { body } = await httpClient.request({
|
||||||
url: `${apiUrl}/${resource}`,
|
url: `${apiUrl}/${resource}`,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { IListResponse_DTO } from "@shared/contexts";
|
import { IListResponse_DTO } from "@shared/contexts";
|
||||||
|
import { AxiosHeaderValue } from "axios";
|
||||||
|
|
||||||
export interface IPaginationDataProviderParam {
|
export interface IPaginationDataProviderParam {
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
@ -51,11 +52,14 @@ export interface IRemoveOneDataProviderParams {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export interface ICustomDataProviderParam {
|
export interface ICustomDataProviderParam {
|
||||||
resource: string;
|
url: string;
|
||||||
method: string;
|
method: "get" | "delete" | "head" | "options" | "post" | "put" | "patch";
|
||||||
params: any;
|
headers?: {
|
||||||
}*/
|
[key: string]: AxiosHeaderValue;
|
||||||
|
};
|
||||||
|
payload?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IDataSource {
|
export interface IDataSource {
|
||||||
name: () => string;
|
name: () => string;
|
||||||
@ -66,9 +70,9 @@ export interface IDataSource {
|
|||||||
updateOne: <P, R>(params: IUpdateOneDataProviderParams<P>) => Promise<R>;
|
updateOne: <P, R>(params: IUpdateOneDataProviderParams<P>) => Promise<R>;
|
||||||
removeOne: (params: IRemoveOneDataProviderParams) => Promise<void>;
|
removeOne: (params: IRemoveOneDataProviderParams) => Promise<void>;
|
||||||
|
|
||||||
//custom: <R>(params: ICustomDataProviderParam) => Promise<R>;
|
custom: <R>(params: ICustomDataProviderParam) => Promise<R>;
|
||||||
|
|
||||||
//getApiUrl: () => string;
|
getApiUrl: () => string;
|
||||||
|
|
||||||
//create: () => any;
|
//create: () => any;
|
||||||
//createMany: () => any;
|
//createMany: () => any;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// export * from './useApiUrl';
|
// export * from './useApiUrl';
|
||||||
// export * from './useCreateMany';
|
// export * from './useCreateMany';
|
||||||
|
export * from "./useCustom";
|
||||||
export * from "./useList";
|
export * from "./useList";
|
||||||
export * from "./useMany";
|
export * from "./useMany";
|
||||||
export * from "./useOne";
|
export * from "./useOne";
|
||||||
|
|||||||
14
client/src/lib/hooks/useDataSource/useCustom.tsx
Normal file
14
client/src/lib/hooks/useDataSource/useCustom.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { UseQueryOptions, UseQueryResult, keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { TDataSourceError, TDataSourceRecord } from "./types";
|
||||||
|
|
||||||
|
export function useCustom<
|
||||||
|
TQueryFnData extends TDataSourceRecord = TDataSourceRecord,
|
||||||
|
TError = TDataSourceError,
|
||||||
|
TData extends TDataSourceRecord = TQueryFnData
|
||||||
|
>(options: UseQueryOptions<TQueryFnData, TError, TData>): UseQueryResult<TData, TError> {
|
||||||
|
return useQuery<TQueryFnData, TError, TData>({
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
type BaseKey = string | number;
|
type BaseKey = string | number;
|
||||||
|
|
||||||
type ParametrizedDataActions = "list" | "infinite";
|
type ParametrizedDataActions = "list" | "infinite";
|
||||||
type IdRequiredDataActions = "one";
|
type IdRequiredDataActions = "one" | "report";
|
||||||
type IdsRequiredDataActions = "many";
|
type IdsRequiredDataActions = "many";
|
||||||
type DataMutationActions =
|
type DataMutationActions =
|
||||||
| "custom"
|
| "custom"
|
||||||
@ -100,7 +100,7 @@ class DataResourceKeyBuilder extends BaseKeyBuilder {
|
|||||||
action(
|
action(
|
||||||
actionType: ParametrizedDataActions | IdRequiredDataActions | IdsRequiredDataActions
|
actionType: ParametrizedDataActions | IdRequiredDataActions | IdsRequiredDataActions
|
||||||
): ParamsKeyBuilder | DataIdRequiringKeyBuilder | DataIdsRequiringKeyBuilder {
|
): ParamsKeyBuilder | DataIdRequiringKeyBuilder | DataIdsRequiringKeyBuilder {
|
||||||
if (actionType === "one") {
|
if (["one", "report"].includes(actionType)) {
|
||||||
return new DataIdRequiringKeyBuilder([...this.segments, actionType]);
|
return new DataIdRequiringKeyBuilder([...this.segments, actionType]);
|
||||||
}
|
}
|
||||||
if (actionType === "many") {
|
if (actionType === "many") {
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { defineConfig } from "vite";
|
import { createRequire } from "node:module";
|
||||||
|
import path from "node:path";
|
||||||
|
import { defineConfig, normalizePath } from "vite";
|
||||||
|
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||||
|
|
||||||
import { resolve } from "node:path";
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
const pdfjsDistPath = path.dirname(require.resolve("pdfjs-dist/package.json"));
|
||||||
|
const cMapsDir = normalizePath(path.join(pdfjsDistPath, "cmaps"));
|
||||||
|
|
||||||
//const require = createRequire(import.meta.url);
|
//const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
@ -17,17 +23,28 @@ const standardFontsDir = normalizePath(
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
viteStaticCopy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: cMapsDir,
|
||||||
|
dest: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
css: { postcss: "./postcss.config.js" },
|
css: { postcss: "./postcss.config.js" },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
find: "@",
|
find: "@",
|
||||||
replacement: resolve(__dirname, "./src"),
|
replacement: path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "@shared",
|
find: "@shared",
|
||||||
replacement: resolve(__dirname, "../shared/lib/"),
|
replacement: path.resolve(__dirname, "../shared/lib/"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export abstract class ExpressController implements IController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public downloadPDF(pdfBuffer: Buffer, filename: string) {
|
public downloadPDF(pdfBuffer: Buffer, filename: string) {
|
||||||
return this._download(pdfBuffer, "application/pdf", `${filename}.pdf`);
|
return this._download(pdfBuffer, "application/pdf", `${filename}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clientError(message?: string) {
|
public clientError(message?: string) {
|
||||||
@ -125,7 +125,7 @@ export abstract class ExpressController implements IController {
|
|||||||
this.res.set({
|
this.res.set({
|
||||||
"Content-Type": contentType,
|
"Content-Type": contentType,
|
||||||
"Content-Disposition": `attachment; filename=${filename}`,
|
"Content-Disposition": `attachment; filename=${filename}`,
|
||||||
"Content-Length": buffer.length,
|
//"Content-Length": buffer.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.res.send(buffer);
|
return this.res.send(buffer);
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export class ReportQuoteController extends ExpressController {
|
|||||||
|
|
||||||
const quote = <Quote>result.object;
|
const quote = <Quote>result.object;
|
||||||
|
|
||||||
return this.downloadPDF(await this.reporter.toPDF(quote, this.context), "prueba.pdf");
|
return this.downloadPDF(await this.reporter.toPDF(quote, this.context), "quote.pdf");
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
return this.fail(e as IServerError);
|
return this.fail(e as IServerError);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export type IReportQuote_Response_DTO = Uint8Array;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./IReportQuote_Response.dto";
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./CreateQuote.dto";
|
export * from "./CreateQuote.dto";
|
||||||
export * from "./GetQuote.dto";
|
export * from "./GetQuote.dto";
|
||||||
export * from "./ListQuotes.dto";
|
export * from "./ListQuotes.dto";
|
||||||
|
export * from "./ReportQuote.dto";
|
||||||
export * from "./UpdateQuote.dto";
|
export * from "./UpdateQuote.dto";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user