This commit is contained in:
David Arranz 2024-08-20 13:44:04 +02:00
parent 4853c4a0bb
commit fe816b73f8
5 changed files with 128 additions and 53 deletions

View File

@ -57,12 +57,8 @@ export const QuotePDFPreview = ({
return ( return (
<Card className={cn("overflow-hidden flex flex-col", className)}> <Card className={cn("overflow-hidden flex flex-col", className)}>
<CardHeader> <CardHeader>
<CardTitle> <Skeleton className='w-full h-8' />
<Skeleton className='w-full h-8' /> <Skeleton className='w-full h-8' />
</CardTitle>
<CardDescription>
<Skeleton className='w-full h-8' />
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className='py-4'> <CardContent className='py-4'>
<Skeleton className='w-full aspect-[3/4] relative bg-white shadow flex-1' /> <Skeleton className='w-full aspect-[3/4] relative bg-white shadow flex-1' />
@ -85,7 +81,7 @@ export const QuotePDFPreview = ({
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
printJS({ printJS({
printable: file, printable: file?.data,
type: "pdf", type: "pdf",
showModal: false, showModal: false,
modalMessage: "Cargando...", modalMessage: "Cargando...",

View File

@ -1,17 +1,40 @@
import { DataTable, DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components"; import { DataTable, DataTableSkeleton, ErrorOverlay, 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 { Badge, Button, Card, CardContent } from "@/ui"; import {
Badge,
Button,
Card,
CardContent,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/ui";
import { IListQuotes_Response_DTO, MoneyValue, UTCDateValue } from "@shared/contexts"; import { IListQuotes_Response_DTO, MoneyValue, UTCDateValue } from "@shared/contexts";
import { ColumnDef, Row } from "@tanstack/react-table"; import { ColumnDef, Row } from "@tanstack/react-table";
import { t } from "i18next"; import { t } from "i18next";
import { FilePenLineIcon, MoreVerticalIcon } from "lucide-react";
import { useMemo, useState } 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";
import { QuotePDFPreview } from "./QuotePDFPreview"; import { QuotePDFPreview } from "./QuotePDFPreview";
export const QuotesDataTable = ({ status = "all" }: { status?: string }) => { export const QuotesDataTable = ({
status = "all",
preview = false,
}: {
status?: string;
preview?: boolean;
}) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { pagination, globalFilter, isFiltered } = useDataTableContext(); const { pagination, globalFilter, isFiltered } = useDataTableContext();
const [focusedQuote, setFocusedQuote] = useState<IListQuotes_Response_DTO | undefined>(undefined); const [focusedQuote, setFocusedQuote] = useState<IListQuotes_Response_DTO | undefined>(undefined);
@ -34,17 +57,20 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
header: () => <>{t("quotes.list.columns.date")}</>, header: () => <>{t("quotes.list.columns.date")}</>,
cell: ({ row: { original } }) => { cell: ({ row: { original } }) => {
const quoteDate = UTCDateValue.create(original.date); const quoteDate = UTCDateValue.create(original.date);
return quoteDate.isSuccess ? quoteDate.object.toLocaleDateString("es-ES") : "-"; return (
<div className='text-right text-ellipsis'>
{quoteDate.isSuccess ? quoteDate.object.toLocaleDateString("es-ES") : "-"}
</div>
);
}, },
enableResizing: false, enableResizing: false,
size: 10,
}, },
{ {
id: "customer_information" as const, id: "customer_information" as const,
accessorKey: "customer_information", accessorKey: "customer_information",
header: () => <>{t("quotes.list.columns.customer_information")}</>, header: () => <>{t("quotes.list.columns.customer_information")}</>,
cell: ({ row: { original } }) => ( cell: ({ row: { original } }) => (
<div className='text-ellipsis'> <div className='text-left text-ellipsis'>
{original.customer_information.split("\n").map((item, index) => { {original.customer_information.split("\n").map((item, index) => {
return ( return (
<span <span
@ -61,14 +87,16 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
</div> </div>
), ),
enableResizing: false, enableResizing: false,
size: 10, size: 640,
}, },
{ {
id: "reference" as const, id: "reference" as const,
accessorKey: "reference", accessorKey: "reference",
header: () => <>{t("quotes.list.columns.reference")}</>, header: () => <>{t("quotes.list.columns.reference")}</>,
cell: ({ row: { original } }) => (
<div className='text-left text-ellipsis'>{original.reference}</div>
),
enableResizing: false, enableResizing: false,
size: 10,
}, },
{ {
id: "status" as const, id: "status" as const,
@ -77,7 +105,6 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
cell: ({ renderValue }: { renderValue: () => any }) => <Badge>{renderValue()}</Badge>, cell: ({ renderValue }: { renderValue: () => any }) => <Badge>{renderValue()}</Badge>,
enableResizing: false, enableResizing: false,
size: 10,
}, },
{ {
id: "total_price" as const, id: "total_price" as const,
@ -90,25 +117,53 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
); );
}, },
enableResizing: false, enableResizing: false,
size: 20,
}, },
{ {
id: "edit-acion", id: "row-actions",
header: () => null, header: () => null,
cell: ({ row }: { row: Row<IListQuotes_Response_DTO> }) => ( cell: ({ row }: { row: Row<IListQuotes_Response_DTO> }) => (
<Button <div className='flex items-center gap-1 ml-auto'>
variant='secondary' <Tooltip>
onClick={(e) => { <TooltipTrigger>
e.preventDefault(); <Button
navigate(`/quotes/edit/${row.original.id}`, { relative: "path" }); size='sm'
}} variant='outline'
> className='h-8 gap-1'
<Trans i18nKey={"common.edit"} /> onClick={(e) => {
<span className='sr-only'>, {row.original.id}</span> e.preventDefault();
</Button> navigate(`/quotes/edit/${row.original.id}`, { relative: "path" });
}}
>
<FilePenLineIcon className='h-3.5 w-3.5' />
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
<Trans i18nKey={"quotes.list.columns.actions.edit"} />
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>
<Trans i18nKey={"quotes.list.columns.actions.edit"} />
</p>
</TooltipContent>
</Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size='icon' variant='outline' className='w-8 h-8'>
<MoreVerticalIcon className='h-3.5 w-3.5' />
<span className='sr-only'>More</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Export</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Trash</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
), ),
enableResizing: false, enableResizing: false,
size: 20,
}, },
], ],
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -159,17 +214,23 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
} }
return ( return (
<div className='grid items-stretch flex-1 gap-4 sm:py-0 md:gap-8 lg:grid-cols-3 xl:grid-cols-3'> <ResizablePanelGroup direction='horizontal' className='flex items-stretch flex-1 gap-4'>
<DataTable <ResizablePanel defaultSize={75} className='flex items-stretch flex-1'>
table={table} <DataTable
paginationOptions={{ visible: true }} table={table}
className='grid items-start gap-4 auto-rows-max md:gap-8 lg:col-span-2' paginationOptions={{ visible: true }}
onRowClick={handleOnRowClick} className='grid items-start flex-1 gap-4 auto-rows-max md:gap-8 lg:col-span-2'
> onRowClick={handleOnRowClick}
<DataTableToolbar table={table} /> >
</DataTable> <DataTableToolbar table={table} />
</DataTable>
<QuotePDFPreview quote={focusedQuote} className='flex-1 ' /> </ResizablePanel>
</div> {preview && <ResizableHandle withHandle />}
{preview && (
<ResizablePanel defaultSize={25} className='flex items-stretch flex-1'>
<QuotePDFPreview quote={focusedQuote} className='flex-1' />
</ResizablePanel>
)}
</ResizablePanelGroup>
); );
}; };

View File

@ -2,12 +2,15 @@ import { DataTableProvider } from "@/lib/hooks";
import { Trans } from "react-i18next"; import { Trans } from "react-i18next";
import { QuotesDataTable } from "./components"; import { QuotesDataTable } from "./components";
import { Button, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui"; import { Button, Tabs, TabsContent, TabsList, TabsTrigger, Toggle } from "@/ui";
import { useToggle } from "@wojtekmaj/react-hooks";
import { t } from "i18next"; import { t } from "i18next";
import { InfoIcon } from "lucide-react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
export const QuotesList = () => { export const QuotesList = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [enabledPreview, toggleEnabledPreview] = useToggle(false);
return ( return (
<DataTableProvider> <DataTableProvider>
@ -27,33 +30,45 @@ export const QuotesList = () => {
<Tabs defaultValue='all'> <Tabs defaultValue='all'>
<div className='flex items-center'> <div className='flex items-center'>
<span className='mr-4 font-medium'>Status</span>
<TabsList> <TabsList>
<TabsTrigger value='all'> <TabsTrigger value='all'>
<Trans i18nKey='quotes.list.tabs.all' /> <Trans i18nKey='quotes.list.tabs.all' />
</TabsTrigger> </TabsTrigger>
<TabsTrigger value='emitted'>
<Trans i18nKey='quotes.list.tabs.emitted' />
</TabsTrigger>
<TabsTrigger value='draft'> <TabsTrigger value='draft'>
<Trans i18nKey='quotes.list.tabs.draft' /> <Trans i18nKey='quotes.list.tabs.draft' />
</TabsTrigger> </TabsTrigger>
<TabsTrigger value='emitted'>
<Trans i18nKey='quotes.list.tabs.emitted' />
</TabsTrigger>
<TabsTrigger value='archived' className='hidden sm:flex'> <TabsTrigger value='archived' className='hidden sm:flex'>
<Trans i18nKey='quotes.list.tabs.archived' /> <Trans i18nKey='quotes.list.tabs.archived' />
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<div className='flex items-center gap-2 ml-auto'></div> <div className='flex items-center gap-2 ml-auto'>
<Toggle
aria-label='Show quote preview'
variant={"outline"}
defaultPressed={false}
pressed={enabledPreview}
onPressedChange={toggleEnabledPreview}
>
<InfoIcon className='w-4 h-4 mr-2' />
Quote preview
</Toggle>
</div>
</div> </div>
<TabsContent value='all'> <TabsContent value='all'>
<QuotesDataTable status='all' /> <QuotesDataTable status='all' preview={enabledPreview} />
</TabsContent> </TabsContent>
<TabsContent value='draft'> <TabsContent value='draft'>
<QuotesDataTable status='draft' /> <QuotesDataTable status='draft' preview={enabledPreview} />
</TabsContent> </TabsContent>
<TabsContent value='archived'> <TabsContent value='archived'>
<QuotesDataTable status='archived' /> <QuotesDataTable status='archived' preview={enabledPreview} />
</TabsContent> </TabsContent>
<TabsContent value='emitted'> <TabsContent value='emitted'>
<QuotesDataTable status='emitted' /> <QuotesDataTable status='emitted' preview={enabledPreview} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</DataTableProvider> </DataTableProvider>

View File

@ -192,9 +192,9 @@ export function useDataTable<TData, TValue>({
debugColumns: false, debugColumns: false,
defaultColumn: { defaultColumn: {
size: 5, //starting column size size: 96, //starting column size
minSize: 0, //enforced during column resizing minSize: 96, //enforced during column resizing
maxSize: 96, //enforced during column resizing maxSize: 500, //enforced during column resizing
}, },
}); });

View File

@ -113,7 +113,10 @@
"reference": "Reference", "reference": "Reference",
"status": "Status", "status": "Status",
"customer_information": "Customer", "customer_information": "Customer",
"total_price": "Imp. total" "total_price": "Imp. total",
"actions": {
"edit": "Edit quote"
}
}, },
"preview": { "preview": {
"quote": "Quote", "quote": "Quote",