This commit is contained in:
parent
acdf1cd428
commit
3b53c89f5d
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@uecko-presupuestador/client",
|
"name": "@uecko-presupuestador/client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.8",
|
"version": "1.0.9",
|
||||||
"author": "Rodax Software <dev@rodax-software.com>",
|
"author": "Rodax Software <dev@rodax-software.com>",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppendCatalogArticleRowButton = forwardRef(
|
export const AppendCatalogArticleRowButton = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
AppendCatalogArticleRowButtonProps
|
||||||
|
>(
|
||||||
(
|
(
|
||||||
{ label = t("common.append_article"), className, ...props }: AppendCatalogArticleRowButtonProps,
|
{ label = t("common.append_article"), className, ...props }: AppendCatalogArticleRowButtonProps,
|
||||||
_ref
|
ref
|
||||||
): JSX.Element => (
|
): 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"} />
|
<PackagePlusIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||||
{label && <>{label}</>}
|
{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;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppendEmptyRowButton = forwardRef(
|
export const AppendEmptyRowButton = forwardRef<HTMLButtonElement, AppendEmptyRowButtonProps>(
|
||||||
(
|
(
|
||||||
{ label = t("common.append_empty_row"), className, ...props }: AppendEmptyRowButtonProps,
|
{ label = t("common.append_empty_row"), className, ...props }: AppendEmptyRowButtonProps,
|
||||||
_ref
|
ref
|
||||||
): JSX.Element => (
|
): 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"} />
|
<PlusCircleIcon className={label ? "w-4 h-4 mr-2" : "w-4 h-4"} />
|
||||||
{label && <>{label}</>}
|
{label && <>{label}</>}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
AppendEmptyRowButton.displayName = "AddNewRowButton";
|
AppendEmptyRowButton.displayName = "AppendEmptyRowButton";
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { FieldValues, UseFieldArrayReturn } from "react-hook-form";
|
import { FieldValues, UseFieldArrayReturn } from "react-hook-form";
|
||||||
|
import { AppendBlockRowButton } from "./AppendBlockRowButton";
|
||||||
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
||||||
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
||||||
import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar";
|
import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar";
|
||||||
@ -46,6 +47,7 @@ declare module "@tanstack/react-table" {
|
|||||||
insertItem: (rowIndex: number, data?: unknown) => void;
|
insertItem: (rowIndex: number, data?: unknown) => void;
|
||||||
appendItem: (data?: unknown) => void;
|
appendItem: (data?: unknown) => void;
|
||||||
pickCatalogArticle?: () => void;
|
pickCatalogArticle?: () => void;
|
||||||
|
pickBlock?: () => void;
|
||||||
duplicateItems: (rowIndex?: number) => void;
|
duplicateItems: (rowIndex?: number) => void;
|
||||||
deleteItems: (rowIndex?: number | number[]) => void;
|
deleteItems: (rowIndex?: number | number[]) => void;
|
||||||
updateItem: (
|
updateItem: (
|
||||||
@ -71,6 +73,7 @@ export type QuoteItemsSortableDataTableProps<
|
|||||||
initialState?: InitialTableState;
|
initialState?: InitialTableState;
|
||||||
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields"> & {
|
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields"> & {
|
||||||
pickCatalogArticle?: () => void;
|
pickCatalogArticle?: () => void;
|
||||||
|
pickBlock?: () => void;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,6 +166,11 @@ export function QuoteItemsSortableDataTable<TData extends RowData & RowIdData, T
|
|||||||
actions?.pickCatalogArticle();
|
actions?.pickCatalogArticle();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pickBlock: () => {
|
||||||
|
if (actions.pickBlock) {
|
||||||
|
actions?.pickBlock();
|
||||||
|
}
|
||||||
|
},
|
||||||
duplicateItems: (rowIndex?: number) => {
|
duplicateItems: (rowIndex?: number) => {
|
||||||
if (rowIndex != undefined) {
|
if (rowIndex != undefined) {
|
||||||
const originalData = table.getRowModel().rows[rowIndex].original;
|
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>
|
</ButtonGroup>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Button, Separator, Tooltip, TooltipContent, TooltipTrigger } from "@/ui
|
|||||||
import { Table } from "@tanstack/react-table";
|
import { Table } from "@tanstack/react-table";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { CopyPlusIcon, ScanIcon, Trash2Icon } from "lucide-react";
|
import { CopyPlusIcon, ScanIcon, Trash2Icon } from "lucide-react";
|
||||||
|
import { AppendBlockRowButton } from "./AppendBlockRowButton";
|
||||||
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton";
|
||||||
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
import { AppendEmptyRowButton } from "./AppendEmptyRowButton";
|
||||||
|
|
||||||
@ -89,6 +90,19 @@ export const QuoteItemsSortableDataTableToolbar = ({ table }: { table: Table<any
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
|
<TooltipContent>{t("common.append_article_tooltip")}</TooltipContent>
|
||||||
</Tooltip>
|
</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>
|
||||||
<div className='flex items-center gap-2 ml-auto'></div>
|
<div className='flex items-center gap-2 ml-auto'></div>
|
||||||
</nav>
|
</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 { useDetailColumns } from "../../hooks";
|
||||||
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
||||||
import { QuoteItemsSortableDataTable, RowIdData } from "../QuoteItemsSortableDataTable";
|
import { QuoteItemsSortableDataTable, RowIdData } from "../QuoteItemsSortableDataTable";
|
||||||
|
import { BlocksPickerDialog } from "./BlocksPickerDialog/BlocksPickerDialog";
|
||||||
import { CatalogPickerDialog } from "./CatalogPickerDialog";
|
import { CatalogPickerDialog } from "./CatalogPickerDialog";
|
||||||
|
|
||||||
export const QuoteDetailsCardEditor = ({
|
export const QuoteDetailsCardEditor = ({
|
||||||
@ -34,7 +35,8 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
|
|
||||||
const [pickerMode] = useState<"dialog" | "panel">("dialog");
|
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({
|
const { fields, ...fieldActions } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
@ -235,6 +237,19 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
[fieldActions]
|
[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 [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
|
||||||
const defaultLayout = [265, 440, 655];
|
const defaultLayout = [265, 440, 655];
|
||||||
@ -246,7 +261,8 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
<QuoteItemsSortableDataTable
|
<QuoteItemsSortableDataTable
|
||||||
actions={{
|
actions={{
|
||||||
...fieldActions,
|
...fieldActions,
|
||||||
pickCatalogArticle: () => setPickerDialogOpen(true),
|
pickCatalogArticle: () => setArticlePickerDialogOpen(true),
|
||||||
|
pickBlock: () => setBlockPickerDialogOpen(true),
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={fields}
|
data={fields}
|
||||||
@ -254,8 +270,14 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
/>
|
/>
|
||||||
<CatalogPickerDialog
|
<CatalogPickerDialog
|
||||||
onSelect={handleAppendCatalogArticle}
|
onSelect={handleAppendCatalogArticle}
|
||||||
isOpen={pickerDialogOpen}
|
isOpen={articlePickerDialogOpen}
|
||||||
onOpenChange={setPickerDialogOpen}
|
onOpenChange={setArticlePickerDialogOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BlocksPickerDialog
|
||||||
|
onSelect={handleAppendBlock}
|
||||||
|
isOpen={blockPickerDialogOpen}
|
||||||
|
onOpenChange={setBlockPickerDialogOpen}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { Badge } from "@/ui";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { RefreshCwIcon } from "lucide-react";
|
import { RefreshCwIcon } from "lucide-react";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
export type QuoteStatusBadgeProps = {
|
export type QuoteStatusBadgeProps = {
|
||||||
status: string;
|
status: string;
|
||||||
@ -53,28 +54,27 @@ const statusColorConfig: Record<
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QuoteStatusBadge = ({
|
export const QuoteStatusBadge = forwardRef<HTMLDivElement, QuoteStatusBadgeProps>(
|
||||||
status,
|
({ status, isEditable, className, ...props }, ref) => {
|
||||||
isEditable,
|
return (
|
||||||
className,
|
<div ref={ref}>
|
||||||
...props
|
<Badge
|
||||||
}: QuoteStatusBadgeProps) => {
|
className={cn(
|
||||||
return (
|
statusColorConfig[status as QuoteStatus].bgColor,
|
||||||
<Badge
|
statusColorConfig[status as QuoteStatus].color,
|
||||||
className={cn(
|
statusColorConfig[status as QuoteStatus].hoverBgColor,
|
||||||
statusColorConfig[status as QuoteStatus].bgColor,
|
statusColorConfig[status as QuoteStatus].hoverColor,
|
||||||
statusColorConfig[status as QuoteStatus].color,
|
"transition-colors duration-200 cursor-pointer flex items-center group",
|
||||||
statusColorConfig[status as QuoteStatus].hoverBgColor,
|
className
|
||||||
statusColorConfig[status as QuoteStatus].hoverColor,
|
)}
|
||||||
"transition-colors duration-200 cursor-pointer flex items-center group",
|
{...props}
|
||||||
className
|
>
|
||||||
)}
|
{t(`quotes.status.${status}`)}
|
||||||
{...props}
|
{isEditable && (
|
||||||
>
|
<RefreshCwIcon className='w-3 h-3 ml-2 transition-opacity opacity-0 group-hover:opacity-100' />
|
||||||
{t(`quotes.status.${status}`)}
|
)}
|
||||||
{isEditable && (
|
</Badge>
|
||||||
<RefreshCwIcon className='w-3 h-3 ml-2 transition-opacity opacity-0 group-hover:opacity-100' />
|
</div>
|
||||||
)}
|
);
|
||||||
</Badge>
|
}
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
@ -39,6 +39,8 @@
|
|||||||
"append_empty_row_tooltip": "Append a empty row",
|
"append_empty_row_tooltip": "Append a empty row",
|
||||||
"append_article": "Append article",
|
"append_article": "Append article",
|
||||||
"append_article_tooltip": "Select and add an item from the catalog",
|
"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_row": "Remove",
|
||||||
"remove_selected_rows": "Remove",
|
"remove_selected_rows": "Remove",
|
||||||
"remove_selected_rows_tooltip": "Remove selected row(s)",
|
"remove_selected_rows_tooltip": "Remove selected row(s)",
|
||||||
@ -234,6 +236,11 @@
|
|||||||
"description": "To complete your quote, you can add items from the catalog.",
|
"description": "To complete your quote, you can add items from the catalog.",
|
||||||
"toast_article_added": "Catalog item added:"
|
"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": {
|
"quote_status_editor": {
|
||||||
"trigger_button": "Change quote status",
|
"trigger_button": "Change quote status",
|
||||||
"title": "Change quote status",
|
"title": "Change quote status",
|
||||||
|
|||||||
@ -39,6 +39,8 @@
|
|||||||
"append_empty_row_tooltip": "Añadir una fila vacía",
|
"append_empty_row_tooltip": "Añadir una fila vacía",
|
||||||
"append_article": "Añadir artículo",
|
"append_article": "Añadir artículo",
|
||||||
"append_article_tooltip": "Elegir un artículo del catálogo y añadirlo",
|
"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_row": "Eliminar",
|
||||||
"remove_selected_rows": "Eliminar",
|
"remove_selected_rows": "Eliminar",
|
||||||
"remove_selected_rows_tooltip": "Elimina las fila(s) seleccionadas(s)",
|
"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.",
|
"description": "Para rellenar su cotización, puede añadir artículos del catálogo.",
|
||||||
"toast_article_added": "Artículo del catálogo añadido:"
|
"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": {
|
"quote_sent_to_editor": {
|
||||||
"trigger_button": "Enviar a Uecko",
|
"trigger_button": "Enviar a Uecko",
|
||||||
"title": "Enviar la cotización 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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
||||||
<title>Uecko</title>
|
<title>Uecko</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DJAOJNo1.js"></script>
|
<script type="module" crossorigin src="/assets/index-D_pSQVNi.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C6azqQuI.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B5XW7DrB.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user