This commit is contained in:
parent
acdf1cd428
commit
3b53c89f5d
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@uecko-presupuestador/client",
|
||||
"private": true,
|
||||
"version": "1.0.8",
|
||||
"version": "1.0.9",
|
||||
"author": "Rodax Software <dev@rodax-software.com>",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
24
client/src/app/quotes/components/AppendBlockRowButton.tsx
Normal file
24
client/src/app/quotes/components/AppendBlockRowButton.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Button, ButtonProps } from "@/ui";
|
||||
import { t } from "i18next";
|
||||
import { PackagePlusIcon } from "lucide-react";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export interface AppendBlockRowButtonProps extends ButtonProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AppendBlockRowButton = forwardRef<HTMLButtonElement, AppendBlockRowButtonProps>(
|
||||
(
|
||||
{ label = t("common.append_block"), className, ...props }: AppendBlockRowButtonProps,
|
||||
ref
|
||||
): JSX.Element => (
|
||||
<Button type='button' variant='outline' ref={ref} {...props}>
|
||||
{" "}
|
||||
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||
{label && <>{label}</>}
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
AppendBlockRowButton.displayName = "AppendBlockRowButton";
|
||||
@ -8,12 +8,15 @@ export interface AppendCatalogArticleRowButtonProps extends ButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AppendCatalogArticleRowButton = forwardRef(
|
||||
export const AppendCatalogArticleRowButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
AppendCatalogArticleRowButtonProps
|
||||
>(
|
||||
(
|
||||
{ label = t("common.append_article"), className, ...props }: AppendCatalogArticleRowButtonProps,
|
||||
_ref
|
||||
ref
|
||||
): JSX.Element => (
|
||||
<Button type='button' variant='outline' {...props}>
|
||||
<Button type='button' variant='outline' ref={ref} {...props}>
|
||||
{" "}
|
||||
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||
{label && <>{label}</>}
|
||||
@ -21,4 +24,4 @@ export const AppendCatalogArticleRowButton = forwardRef(
|
||||
)
|
||||
);
|
||||
|
||||
AppendCatalogArticleRowButton.displayName = "AddNewRowButton";
|
||||
AppendCatalogArticleRowButton.displayName = "AppendCatalogArticleRowButton";
|
||||
|
||||
@ -8,16 +8,16 @@ export interface AppendEmptyRowButtonProps extends ButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AppendEmptyRowButton = forwardRef(
|
||||
export const AppendEmptyRowButton = forwardRef<HTMLButtonElement, AppendEmptyRowButtonProps>(
|
||||
(
|
||||
{ label = t("common.append_empty_row"), className, ...props }: AppendEmptyRowButtonProps,
|
||||
_ref
|
||||
ref
|
||||
): JSX.Element => (
|
||||
<Button type='button' variant='outline' {...props}>
|
||||
<Button type='button' variant='outline' ref={ref} {...props}>
|
||||
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||
{label && <>{label}</>}
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
AppendEmptyRowButton.displayName = "AddNewRowButton";
|
||||
AppendEmptyRowButton.displayName = "AppendEmptyRowButton";
|
||||
|
||||
@ -34,6 +34,7 @@ import {
|
||||
import { useMemo, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { FieldValues, UseFieldArrayReturn } from "react-hook-form";
|
||||
import { AppendBlockRowButton } from "./AppendBlockRowButton";
|
||||
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
||||
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
||||
import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar";
|
||||
@ -46,6 +47,7 @@ declare module "@tanstack/react-table" {
|
||||
insertItem: (rowIndex: number, data?: unknown) => void;
|
||||
appendItem: (data?: unknown) => void;
|
||||
pickCatalogArticle?: () => void;
|
||||
pickBlock?: () => void;
|
||||
duplicateItems: (rowIndex?: number) => void;
|
||||
deleteItems: (rowIndex?: number | number[]) => void;
|
||||
updateItem: (
|
||||
@ -71,6 +73,7 @@ export type QuoteItemsSortableDataTableProps<
|
||||
initialState?: InitialTableState;
|
||||
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields"> & {
|
||||
pickCatalogArticle?: () => void;
|
||||
pickBlock?: () => void;
|
||||
};
|
||||
};
|
||||
|
||||
@ -163,6 +166,11 @@ export function QuoteItemsSortableDataTable<TData extends RowData & RowIdData, T
|
||||
actions?.pickCatalogArticle();
|
||||
}
|
||||
},
|
||||
pickBlock: () => {
|
||||
if (actions.pickBlock) {
|
||||
actions?.pickBlock();
|
||||
}
|
||||
},
|
||||
duplicateItems: (rowIndex?: number) => {
|
||||
if (rowIndex != undefined) {
|
||||
const originalData = table.getRowModel().rows[rowIndex].original;
|
||||
@ -467,6 +475,13 @@ export function QuoteItemsSortableDataTable<TData extends RowData & RowIdData, T
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<AppendBlockRowButton
|
||||
onClick={() => {
|
||||
if (table.options.meta && table.options.meta.pickBlock) {
|
||||
table.options.meta?.pickBlock();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
@ -2,6 +2,7 @@ import { Button, Separator, Tooltip, TooltipContent, TooltipTrigger } from "@/ui
|
||||
import { Table } from "@tanstack/react-table";
|
||||
import { t } from "i18next";
|
||||
import { CopyPlusIcon, ScanIcon, Trash2Icon } from "lucide-react";
|
||||
import { AppendBlockRowButton } from "./AppendBlockRowButton";
|
||||
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
||||
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
||||
|
||||
@ -89,6 +90,19 @@ export const QuoteItemsSortableDataTableToolbar = ({ table }: { table: Table<any
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<AppendBlockRowButton
|
||||
variant='ghost'
|
||||
onClick={() => {
|
||||
if (table.options.meta && table.options.meta.pickBlock) {
|
||||
table.options.meta?.pickBlock();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("common.append_block_tooltip")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 ml-auto'></div>
|
||||
</nav>
|
||||
|
||||
@ -0,0 +1,268 @@
|
||||
import { ScrollArea } from "@/ui";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type BlockDataType = {
|
||||
id: number;
|
||||
code: string;
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
const blockData: Record<string, BlockDataType[]> = {
|
||||
en: [
|
||||
{
|
||||
id: 2000000098,
|
||||
code: "CAP_CLA_SEMI_SUSPENDIDO_EN",
|
||||
title: "Finish: MELAMINE / colour: TO BE DETERMINED",
|
||||
body: 'Thickness block: 19 mm / Thickness complements: 19 mm.\r\nHanging rods: CHROME/ BLACK\r\nGlass drawer fronts: STRAIGHT / "U" - FLOAT BRONZE C-424 \r\nDrawer handle: xxxxx / Drawers: 4 DE 180 MM.\r\nShoe rack door: YES/NO / Profile finish: BLACK LACQUER / SILVER MATTE\r\nShoe cabinet door glass finish: FLOAT BRONZE C-424\r\nComposed of:',
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000097,
|
||||
code: "CAP_CLA_SEMI_SUSPENDIDO_TITULO_EN",
|
||||
title: "CLASSIC DRESSER / SEMI-HUNG SUSPENDED / SUSPENDED",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000084,
|
||||
code: "CAP_FRE_ARM_ABATIBLE_EN",
|
||||
title: "CHARACTERISTICS OF THE FRONT OF THE HINGED WARDROBE",
|
||||
body: "Size of opening: 2275x1390 mm.\r\nModel: ARGO / Finish: LACQUER / Colour: TO BE DETERMINED\r\nDoor thickness: 22 or 19 mm\r\nDoor finish: SMOOTH \r\nHandle: KNOB TO BE DETERMINED\r\nHardware finish: CHROME OR ANTHRACITE\r\nPerimetral doorposts: VISIBLE, REMETED OR COMBI TO BE DETERMINED.\r\nNumber of doors: 2 \r\nComposed of:",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000086,
|
||||
code: "CAP_FRE_ARM_ABATIBLE_TITULO_EN",
|
||||
title: "FRONT OF THE HINGED WARDROBE",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000089,
|
||||
code: "CAP_FRE_ARM_CORREDERA_EN",
|
||||
title: "Size of opening: 2440x5180mm. ",
|
||||
body: "Model: PANORAMIC P.O. / Finish: LACQUER / Colour: WHITE\r\nNo. of listers: NO LISTELS / Panels finish: LACQUERED PANEL \r\nHandle: \r\nPerimeter frame: \r\nNº of doors: \r\nINCORPORATED HYDRAULIC BRAKE (not available in lower doors of 650 mm.) OR INCORPORATED MAGNETIC BRAKE. \r\nComposed of:",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000088,
|
||||
code: "CAP_FRE_ARM_CORREDERA_TITULO_EN",
|
||||
title: "SLIDING WARDROBE FRONT",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000113,
|
||||
code: "CAP_INSTALACION_EN",
|
||||
title: "Installation Cabinet x",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000115,
|
||||
code: "CAP_INSTALACION_TITULO_EN",
|
||||
title: "INSTALLATION SERVICE - NET PRICE",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000092,
|
||||
code: "CAP_INT_ARM_FORRADO_EN",
|
||||
title: "Finish: MELAMINE / colour: TO BE DETERMINED",
|
||||
body: 'Thickness block: 19 mm / Thickness complements: 19 mm.\r\nHanging rods: CHROME/ BLACK\r\nGlass drawer fronts: STRAIGHT / "U" - FLOAT BRONZE C-424 \r\nDrawer handle: xxxxx / Drawers: 4 DE 180 MM.\r\nComposed of:',
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000091,
|
||||
code: "CAP_INT_ARM_FORRADO_TITULO_EN",
|
||||
title: "INTERIOR OF THE LINED WARDROBE",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000109,
|
||||
code: "CAP_INT_EST_CRISTAL_EN",
|
||||
title: "Frames finish: BLACK ANODIZED",
|
||||
body: 'Side frame panel finish: BRONZE FLOAT BRONZE GLASS C-424\r\nRear panel finish: BRONZE FLOAT GLASS C-424\r\nComplements finish: MELAMIN / Colour: DENBER OAK M-242\r\nThickness of accessories: 25 mm.\r\nHanging bars: BLACK\r\nGlass drawer fronts: STRAIGHT / "U" - FLOAT BRONZE C-424 \r\nDrawer handle: xxxxx / Drawers: 4 DE 180 MM.\r\nComposed of:',
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000107,
|
||||
code: "CAP_INT_EST_CRISTAL_TITULO_EN",
|
||||
title: "INSIDE CHARACTERISTICS OF WARDROBE GLASS STRUCTURE",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000095,
|
||||
code: "CAP_SEP_AMBIENTES_EN",
|
||||
title: "CHARACTERISTICS SEPARATION OF ROOMS",
|
||||
body: "Size of opening: 2450x3000 mm.\r\nModel: LYN / Finish: ANODISED / Colour: BLACK\r\nNo. of crosspieces: 2 HORIZONTAL CROSSBARS\r\nPanel finish: TRANSPARENT TEMPLARED \r\nHandle: DOUBLE PER DOOR\r\nBrake: DOUBLE PER DOOR (OPENING AND CLOSING)\r\nGuide: VISTA (DOUBLE EMBELLECEDOR)\r\nNumber of panels: 2 SLIDING + 2 FIXED",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000094,
|
||||
code: "CAP_SEP_AMBIENTES_TITULO_EN",
|
||||
title: "SEPARATION OF ROOMS",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000110,
|
||||
code: "CAP_SUMINISTRO_EN",
|
||||
title: "Delivery of material to the customer's home",
|
||||
body: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: 2000000112,
|
||||
code: "CAP_SUMINISTRO_TITULO_EN",
|
||||
title: "SUPPLY SERVICE - NET PRICE",
|
||||
body: "",
|
||||
},
|
||||
],
|
||||
es: [
|
||||
{
|
||||
id: 54,
|
||||
code: "CAP_CLA_SEMI_SUSPENDIDO_ES",
|
||||
title: "Acabado: MELAMINA / color: A DETERMINAR",
|
||||
body: "Espesor bloque: 25 mm. / Espesor complementos: 25 mm.\r\nBarras de colgar: CROMO/ NEGRA/ BRONCE\r\nFrentes cristal cajones: RECTO / “U” – FLOAT BRONCE C-424\r\nTirador cajones: FRENTE BAJO / Cajones: 4 DE 180 MM. A DETERMINAR\r\nBaldas deslizantes: X DE 145 MM. A DETERMINAR\r\nCompuesto por:",
|
||||
},
|
||||
{
|
||||
id: 2000000096,
|
||||
code: "CAP_CLA_SEMI_SUSPENDIDO_TITULO_ES",
|
||||
title: "CLASICO / SEMISUSPENDIDO / SUSPENDIDO",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 52,
|
||||
code: "CAP_FRE_ARM_ABATIBLE_ES",
|
||||
title: "CARACTERISTICAS FRENTE DE ARMARIO",
|
||||
body: "Medida de hueco: 2275x1390 mm.\r\nModelo: ARGO / Acabado: LACADO / Color: A DETERMINAR\r\nGrosor hoja: 22 o 19 mm\r\nAcabado de hojas: LISAS\r\nTirador: UÑERO A DETERMINAR\r\nAcabado herrajes: CROMADO O ANTRACITA\r\nJambas perimetrales: VISTAS, REMETIDAS O COMBI A DETERMINAR.\r\nNº de hojas: 2\r\nCompuesto por:",
|
||||
},
|
||||
{
|
||||
id: 2000000085,
|
||||
code: "CAP_FRE_ARM_ABATIBLE_TITULO_ES",
|
||||
title: "FRENTE DE ARMARIO ABATIBLE",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 51,
|
||||
code: "CAP_FRE_ARM_CORREDERA_ES",
|
||||
title: "CARACTERISTICAS FRENTE DE ARMARIO",
|
||||
body: "Medida de Hueco:2440x5180mm.\r\nModelo: PANORAMIC P.O. / Acabado: LACADO / Color: BLANCO\r\nNº de lístelos: SIN LISTELOS / Acabado paneles: PANEL LACADO\r\nTirador:\r\nCerco perimetral: \r\nNº de hojas:\r\nFRENO HIDRAULICO INCORPORADO O FRENO MAGNETICO INCORPORADO\r\nCompuesto por:",
|
||||
},
|
||||
{
|
||||
id: 2000000087,
|
||||
code: "CAP_FRE_ARM_CORREDERA_TITULO_ES",
|
||||
title: "FRENTE DE ARMARIO CORREDERA",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 55,
|
||||
code: "CAP_INSTALACION_ES",
|
||||
title: "Instalación Armario x",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 2000000114,
|
||||
code: "CAP_INSTALACION_TITULO_ES",
|
||||
title: "SERVICIO DE INSTALACIÓN - PRECIO NETO",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 53,
|
||||
code: "CAP_INT_ARM_FORRADO_ES",
|
||||
title: "CARACTERISTICAS INTERIOR DE ARMARIO",
|
||||
body: "Acabado: MELAMINA / color: A DETERMINAR\r\nEspesor bloque: 19 mm. / Espesor complementos: 19 mm.\r\nBarras de colgar: CROMO/ NEGRA/ BRONCE\r\nFrentes cristal cajones: RECTO / “U” – FLOAT BRONCE C-424\r\nTirador cajones: FRENTE BAJO / Cajones: 4 DE 180 MM. A DETERMINAR\r\nBaldas deslizantes: X DE 145 MM. A DETERMINAR\r\nCompuesto por:",
|
||||
},
|
||||
{
|
||||
id: 2000000090,
|
||||
code: "CAP_INT_ARM_FORRADO_TITULO_ES",
|
||||
title: "INTERIOR DE ARMARIO FORRADO",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 2000000108,
|
||||
code: "CAP_INT_EST_CRISTAL_ES",
|
||||
title: "Acabado bastidores: ANODIZADO NEGRO",
|
||||
body: "Acabado panel bastidores laterales: CRISTAL TEMPLADO FLOAT BRONCE C-424\r\nAcabado trasera: CRISTAL TEMPLADO FLOAT BRONCE C-424\r\nAcabado complementos: MELAMINA / Color: ROBLE DENBER M-242\r\nEspesor complementos: 25 mm.\r\nBarras de colgar: NEGRA\r\nFrentes cristal cajones: RECTO / “U” – FLOAT BRONCE C-424 \r\nTirador cajones: xxxxx / Cajones: 4 DE 180 MM.\r\nCompuesto por:",
|
||||
},
|
||||
{
|
||||
id: 2000000106,
|
||||
code: "CAP_INT_EST_CRISTAL_TITULO_ES",
|
||||
title: "INTERIOR DE ARMARIO ESTRUCTURA DE CRISTAL",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 50,
|
||||
code: "CAP_SEP_AMBIENTES_ES",
|
||||
title: "CARACTERISTICAS SEPARACIÓN DE AMBIENTES",
|
||||
body: "Medida de Hueco: 2450x3000 mm.\r\nModelo: LYN / Acabado: ANODIZADO / Color: NEGRO\r\nNº de travesaños: 2 TRAVESAÑOS HORIZONTALES\r\nAcabado paneles: TRANSPARENTE TEMPLADO \r\nTirador: DOBLE POR PUERTA\r\nFreno: DOBLE POR PUERTA (APERTURA Y CIERRE)\r\nGuía: VISTA (DOBLE EMBELLECEDOR)\r\nNº de hojas: 2 CORREDERAS + 2 FIJOS",
|
||||
},
|
||||
{
|
||||
id: 2000000093,
|
||||
code: "CAP_SEP_AMBIENTES_TITULO_ES",
|
||||
title: "SEPARACIÓN DE AMBIENTES",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 56,
|
||||
code: "CAP_SUMINISTRO_ES",
|
||||
title: "Entrega de material en domicilio cliente",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
id: 2000000111,
|
||||
code: "CAP_SUMINISTRO_TITULO_ES",
|
||||
title: "SERVICIO DE SUMINISTRO - PRECIO NETO",
|
||||
body: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const BlockList = ({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (data: BlockDataType, quantity: number) => void;
|
||||
}) => {
|
||||
const { i18n } = useTranslation();
|
||||
const [language] = useState(i18n.language);
|
||||
const [listState] = useState(blockData[language.toLowerCase()]);
|
||||
|
||||
return (
|
||||
<ScrollArea className='h-96'>
|
||||
<div className='flex flex-col gap-2 p-4 pt-0'>
|
||||
{listState
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.map((_block, _blockIdx) => {
|
||||
return (
|
||||
<button
|
||||
key={_block.id}
|
||||
className='flex flex-col items-start gap-2 p-3 text-sm text-left transition-all border rounded-lg hover:bg-accent'
|
||||
onClick={() => onSelect && onSelect(_block, 1)}
|
||||
>
|
||||
<div className='flex flex-col w-full gap-1'>
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='font-semibold'>{_block.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='text-xs line-clamp-2 text-muted-foreground'>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: _block.body.substring(0, 300).replace("\r\n", "<br>"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,72 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from "@/ui";
|
||||
|
||||
import { useMediaQuery } from "@/lib/hooks";
|
||||
import { t } from "i18next";
|
||||
import { BlockList } from "./BlockList";
|
||||
|
||||
export const BlocksPickerDialog = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
onSelect,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSelect: (data: unknown) => void;
|
||||
}) => {
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<Dialog modal open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className='w-full max-w-full md:w-9/12 lg:w-10/12'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("quotes.blocks_picker_dialog.title")}</DialogTitle>
|
||||
<DialogDescription>{t("quotes.blocks_picker_dialog.description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<BlockList onSelect={onSelect} />
|
||||
|
||||
<DialogFooter>
|
||||
<Button type='submit' onClick={() => onOpenChange(false)}>
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className='text-left'>
|
||||
<DrawerTitle>{t("quotes.blocks_picker_dialog.title")}</DrawerTitle>
|
||||
<DrawerDescription>{t("quotes.blocks_picker_dialog.description")}</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
|
||||
<BlockList onSelect={onSelect} />
|
||||
|
||||
<DrawerFooter className='pt-2'>
|
||||
<DrawerClose asChild>
|
||||
<Button variant='outline'>{t("common.close")}</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./BlocksPickerDialog";
|
||||
@ -18,6 +18,7 @@ import { useFieldArray, useFormContext } from "react-hook-form";
|
||||
import { useDetailColumns } from "../../hooks";
|
||||
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
||||
import { QuoteItemsSortableDataTable, RowIdData } from "../QuoteItemsSortableDataTable";
|
||||
import { BlocksPickerDialog } from "./BlocksPickerDialog/BlocksPickerDialog";
|
||||
import { CatalogPickerDialog } from "./CatalogPickerDialog";
|
||||
|
||||
export const QuoteDetailsCardEditor = ({
|
||||
@ -34,7 +35,8 @@ export const QuoteDetailsCardEditor = ({
|
||||
|
||||
const [pickerMode] = useState<"dialog" | "panel">("dialog");
|
||||
|
||||
const [pickerDialogOpen, setPickerDialogOpen] = useState<boolean>(false);
|
||||
const [articlePickerDialogOpen, setArticlePickerDialogOpen] = useState<boolean>(false);
|
||||
const [blockPickerDialogOpen, setBlockPickerDialogOpen] = useState<boolean>(false);
|
||||
|
||||
const { fields, ...fieldActions } = useFieldArray({
|
||||
control,
|
||||
@ -235,6 +237,19 @@ export const QuoteDetailsCardEditor = ({
|
||||
[fieldActions]
|
||||
);
|
||||
|
||||
const handleAppendBlock = useCallback(
|
||||
(block: any) => {
|
||||
fieldActions.append({
|
||||
description: block.body,
|
||||
});
|
||||
toast({
|
||||
title: t("quotes.blocks_picker_dialog.toast_article_added"),
|
||||
description: block.title,
|
||||
});
|
||||
},
|
||||
[fieldActions]
|
||||
);
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
|
||||
const defaultLayout = [265, 440, 655];
|
||||
@ -246,7 +261,8 @@ export const QuoteDetailsCardEditor = ({
|
||||
<QuoteItemsSortableDataTable
|
||||
actions={{
|
||||
...fieldActions,
|
||||
pickCatalogArticle: () => setPickerDialogOpen(true),
|
||||
pickCatalogArticle: () => setArticlePickerDialogOpen(true),
|
||||
pickBlock: () => setBlockPickerDialogOpen(true),
|
||||
}}
|
||||
columns={columns}
|
||||
data={fields}
|
||||
@ -254,8 +270,14 @@ export const QuoteDetailsCardEditor = ({
|
||||
/>
|
||||
<CatalogPickerDialog
|
||||
onSelect={handleAppendCatalogArticle}
|
||||
isOpen={pickerDialogOpen}
|
||||
onOpenChange={setPickerDialogOpen}
|
||||
isOpen={articlePickerDialogOpen}
|
||||
onOpenChange={setArticlePickerDialogOpen}
|
||||
/>
|
||||
|
||||
<BlocksPickerDialog
|
||||
onSelect={handleAppendBlock}
|
||||
isOpen={blockPickerDialogOpen}
|
||||
onOpenChange={setBlockPickerDialogOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
0
client/src/app/quotes/hooks/useBlocks.tsx
Normal file
0
client/src/app/quotes/hooks/useBlocks.tsx
Normal file
@ -2,6 +2,7 @@ import { cn } from "@/lib/utils";
|
||||
import { Badge } from "@/ui";
|
||||
import { t } from "i18next";
|
||||
import { RefreshCwIcon } from "lucide-react";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export type QuoteStatusBadgeProps = {
|
||||
status: string;
|
||||
@ -53,28 +54,27 @@ const statusColorConfig: Record<
|
||||
},
|
||||
};
|
||||
|
||||
export const QuoteStatusBadge = ({
|
||||
status,
|
||||
isEditable,
|
||||
className,
|
||||
...props
|
||||
}: QuoteStatusBadgeProps) => {
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
statusColorConfig[status as QuoteStatus].bgColor,
|
||||
statusColorConfig[status as QuoteStatus].color,
|
||||
statusColorConfig[status as QuoteStatus].hoverBgColor,
|
||||
statusColorConfig[status as QuoteStatus].hoverColor,
|
||||
"transition-colors duration-200 cursor-pointer flex items-center group",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{t(`quotes.status.${status}`)}
|
||||
{isEditable && (
|
||||
<RefreshCwIcon className='w-3 h-3 ml-2 transition-opacity opacity-0 group-hover:opacity-100' />
|
||||
)}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
export const QuoteStatusBadge = forwardRef<HTMLDivElement, QuoteStatusBadgeProps>(
|
||||
({ status, isEditable, className, ...props }, ref) => {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<Badge
|
||||
className={cn(
|
||||
statusColorConfig[status as QuoteStatus].bgColor,
|
||||
statusColorConfig[status as QuoteStatus].color,
|
||||
statusColorConfig[status as QuoteStatus].hoverBgColor,
|
||||
statusColorConfig[status as QuoteStatus].hoverColor,
|
||||
"transition-colors duration-200 cursor-pointer flex items-center group",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{t(`quotes.status.${status}`)}
|
||||
{isEditable && (
|
||||
<RefreshCwIcon className='w-3 h-3 ml-2 transition-opacity opacity-0 group-hover:opacity-100' />
|
||||
)}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@ -39,6 +39,8 @@
|
||||
"append_empty_row_tooltip": "Append a empty row",
|
||||
"append_article": "Append article",
|
||||
"append_article_tooltip": "Select and add an item from the catalog",
|
||||
"append_block": "Append text block",
|
||||
"append_block_tooltip": "Select and add a text block",
|
||||
"remove_row": "Remove",
|
||||
"remove_selected_rows": "Remove",
|
||||
"remove_selected_rows_tooltip": "Remove selected row(s)",
|
||||
@ -234,6 +236,11 @@
|
||||
"description": "To complete your quote, you can add items from the catalog.",
|
||||
"toast_article_added": "Catalog item added:"
|
||||
},
|
||||
"blocks_picker_dialog": {
|
||||
"title": "Select a text block",
|
||||
"description": "To complete your quote, you can default text blocks.",
|
||||
"toast_article_added": "Text block added:"
|
||||
},
|
||||
"quote_status_editor": {
|
||||
"trigger_button": "Change quote status",
|
||||
"title": "Change quote status",
|
||||
|
||||
@ -39,6 +39,8 @@
|
||||
"append_empty_row_tooltip": "Añadir una fila vacía",
|
||||
"append_article": "Añadir artículo",
|
||||
"append_article_tooltip": "Elegir un artículo del catálogo y añadirlo",
|
||||
"append_block": "Añadir bloque de texto",
|
||||
"append_block_tooltip": "Elegir un bloque de texto y añadirlo",
|
||||
"remove_row": "Eliminar",
|
||||
"remove_selected_rows": "Eliminar",
|
||||
"remove_selected_rows_tooltip": "Elimina las fila(s) seleccionadas(s)",
|
||||
@ -234,6 +236,12 @@
|
||||
"description": "Para rellenar su cotización, puede añadir artículos del catálogo.",
|
||||
"toast_article_added": "Artículo del catálogo añadido:"
|
||||
},
|
||||
"blocks_picker_dialog": {
|
||||
"title": "Selecctionar bloques de texto",
|
||||
"description": "Para ayudar a rellenar la cotización, puede añadir bloques de texto predefinidos de esta lista.",
|
||||
"toast_article_added": "Bloque de texto añadido:"
|
||||
},
|
||||
|
||||
"quote_sent_to_editor": {
|
||||
"trigger_button": "Enviar a Uecko",
|
||||
"title": "Enviar la cotización a Uecko",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
dist/client/index.html
vendored
4
dist/client/index.html
vendored
@ -8,8 +8,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
||||
<title>Uecko</title>
|
||||
<script type="module" crossorigin src="/assets/index-DJAOJNo1.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C6azqQuI.css">
|
||||
<script type="module" crossorigin src="/assets/index-D_pSQVNi.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B5XW7DrB.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user