This commit is contained in:
David Arranz 2025-01-15 17:26:28 +01:00
parent acdf1cd428
commit 3b53c89f5d
17 changed files with 667 additions and 154 deletions

View File

@ -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": {

View 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";

View File

@ -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";

View File

@ -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";

View File

@ -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>

View File

@ -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>

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -0,0 +1 @@
export * from "./BlocksPickerDialog";

View File

@ -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>
);

View 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>
);
}
);

View File

@ -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",

View File

@ -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

View File

@ -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>