v1.2.7
This commit is contained in:
parent
5934773331
commit
c5a40e32a3
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@uecko-presupuestador/client",
|
"name": "@uecko-presupuestador/client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"author": "Rodax Software <dev@rodax-software.com>",
|
"author": "Rodax Software <dev@rodax-software.com>",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { ScrollArea } from "@/ui";
|
import { ScrollArea } from "@/ui";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import esBlocks from "./locales/es.json";
|
import esBlocks from "./locales/es.json";
|
||||||
import enBlocks from "./locales/en.json";
|
import enBlocks from "./locales/en.json";
|
||||||
import frBlocks from "./locales/fr.json";
|
import frBlocks from "./locales/fr.json";
|
||||||
@ -15,12 +14,12 @@ const blockData: Record<string, BlockDataType[]> = { es: esBlocks, en: enBlocks,
|
|||||||
|
|
||||||
export const BlockList = ({
|
export const BlockList = ({
|
||||||
onSelect,
|
onSelect,
|
||||||
|
languageCode,
|
||||||
}: {
|
}: {
|
||||||
|
languageCode: string;
|
||||||
onSelect: (data: BlockDataType, quantity: number) => void;
|
onSelect: (data: BlockDataType, quantity: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { i18n } = useTranslation();
|
const listState = blockData[languageCode.toLowerCase()] ?? blockData.es;
|
||||||
const language = i18n.resolvedLanguage || "es";
|
|
||||||
const listState = blockData[language.toLowerCase()] ?? blockData.es;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className='h-96'>
|
<ScrollArea className='h-96'>
|
||||||
|
|||||||
@ -0,0 +1,105 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { act } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { useMediaQuery } from "@/lib/hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { BlocksPickerDialog } from "./BlocksPickerDialog";
|
||||||
|
import esBlocks from "./locales/es.json";
|
||||||
|
import enBlocks from "./locales/en.json";
|
||||||
|
import frBlocks from "./locales/fr.json";
|
||||||
|
|
||||||
|
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
jest.mock("@/lib/hooks", () => ({ useMediaQuery: jest.fn() }));
|
||||||
|
jest.mock("react-i18next", () => ({ useTranslation: jest.fn() }));
|
||||||
|
jest.mock("i18next", () => ({ t: (key: string) => key }));
|
||||||
|
jest.mock("@/ui", () => {
|
||||||
|
const Container = ({ children }: { children: ReactNode }) => <div>{children}</div>;
|
||||||
|
return {
|
||||||
|
Button: Container,
|
||||||
|
Dialog: Container,
|
||||||
|
DialogContent: Container,
|
||||||
|
DialogDescription: Container,
|
||||||
|
DialogFooter: Container,
|
||||||
|
DialogHeader: Container,
|
||||||
|
DialogTitle: Container,
|
||||||
|
Drawer: Container,
|
||||||
|
DrawerClose: Container,
|
||||||
|
DrawerContent: Container,
|
||||||
|
DrawerDescription: Container,
|
||||||
|
DrawerFooter: Container,
|
||||||
|
DrawerHeader: Container,
|
||||||
|
DrawerTitle: Container,
|
||||||
|
ScrollArea: Container,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([true, false])("text block picker (desktop: %s)", (isDesktop) => {
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
let root: ReturnType<typeof createRoot>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.mocked(useMediaQuery).mockReturnValue(isDesktop);
|
||||||
|
container = document.createElement("div");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
root = createRoot(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
act(() => root.unmount());
|
||||||
|
container.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ languageCode: "es", interfaceLanguage: "en", blocks: esBlocks },
|
||||||
|
{ languageCode: "en", interfaceLanguage: "es", blocks: enBlocks },
|
||||||
|
{ languageCode: "FR", interfaceLanguage: "es", blocks: frBlocks },
|
||||||
|
])("displays and selects document text in $languageCode with the interface in $interfaceLanguage", ({
|
||||||
|
languageCode, interfaceLanguage, blocks,
|
||||||
|
}) => {
|
||||||
|
jest.mocked(useTranslation).mockReturnValue({
|
||||||
|
i18n: { resolvedLanguage: interfaceLanguage },
|
||||||
|
} as unknown as ReturnType<typeof useTranslation>);
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
act(() => {
|
||||||
|
root.render(
|
||||||
|
<BlocksPickerDialog
|
||||||
|
isOpen
|
||||||
|
languageCode={languageCode}
|
||||||
|
onOpenChange={jest.fn()}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedBlocks = blocks.filter(({ title, body }) => title || body);
|
||||||
|
const buttons = Array.from(container.querySelectorAll("button"));
|
||||||
|
expect(buttons).toHaveLength(expectedBlocks.length);
|
||||||
|
expect(buttons.map((button) => button.querySelector(".font-semibold")?.textContent).sort())
|
||||||
|
.toEqual(expectedBlocks.map(({ title }) => title).sort());
|
||||||
|
const selectedBlock = expectedBlocks[0];
|
||||||
|
const selectedButton = buttons.find((button) =>
|
||||||
|
button.querySelector(".font-semibold")?.textContent === selectedBlock.title
|
||||||
|
);
|
||||||
|
expect(selectedButton).toBeDefined();
|
||||||
|
act(() => selectedButton!.click());
|
||||||
|
|
||||||
|
// The insertion handler receives the full localized title and body, including line breaks.
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(selectedBlock, 1);
|
||||||
|
expect(container.textContent).toContain("quotes.blocks_picker_dialog.title");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates available blocks when the document language changes while open", () => {
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
const props = { isOpen: true, onOpenChange: jest.fn(), onSelect };
|
||||||
|
act(() => root.render(<BlocksPickerDialog {...props} languageCode='en' />));
|
||||||
|
act(() => root.render(<BlocksPickerDialog {...props} languageCode='fr' />));
|
||||||
|
|
||||||
|
const selectedButton = Array.from(container.querySelectorAll("button")).find((button) =>
|
||||||
|
button.querySelector(".font-semibold")?.textContent === frBlocks[0].title
|
||||||
|
);
|
||||||
|
expect(selectedButton).toBeDefined();
|
||||||
|
act(() => selectedButton!.click());
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(frBlocks[0], 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -23,7 +23,9 @@ export const BlocksPickerDialog = ({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
languageCode,
|
||||||
}: {
|
}: {
|
||||||
|
languageCode: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
onSelect: (data: unknown) => void;
|
onSelect: (data: unknown) => void;
|
||||||
@ -39,7 +41,7 @@ export const BlocksPickerDialog = ({
|
|||||||
<DialogDescription>{t("quotes.blocks_picker_dialog.description")}</DialogDescription>
|
<DialogDescription>{t("quotes.blocks_picker_dialog.description")}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<BlockList onSelect={onSelect} />
|
<BlockList languageCode={languageCode} onSelect={onSelect} />
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button type='submit' onClick={() => onOpenChange(false)}>
|
<Button type='submit' onClick={() => onOpenChange(false)}>
|
||||||
@ -59,7 +61,7 @@ export const BlocksPickerDialog = ({
|
|||||||
<DrawerDescription>{t("quotes.blocks_picker_dialog.description")}</DrawerDescription>
|
<DrawerDescription>{t("quotes.blocks_picker_dialog.description")}</DrawerDescription>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
|
|
||||||
<BlockList onSelect={onSelect} />
|
<BlockList languageCode={languageCode} onSelect={onSelect} />
|
||||||
|
|
||||||
<DrawerFooter className='pt-2'>
|
<DrawerFooter className='pt-2'>
|
||||||
<DrawerClose asChild>
|
<DrawerClose asChild>
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { control, getValues, setValue, watch } = useFormContext();
|
const { control, getValues, setValue, watch } = useFormContext();
|
||||||
|
const documentLanguageCode = watch("lang_code") || Language.DEFAULT_LANGUAGE_CODE;
|
||||||
const showLineDiscountColumn = watch("show_line_discount_column") ?? true;
|
const showLineDiscountColumn = watch("show_line_discount_column") ?? true;
|
||||||
const hasLineDiscounts = Boolean(
|
const hasLineDiscounts = Boolean(
|
||||||
getValues("items")?.some((item: { discount?: { amount?: number | null } }) =>
|
getValues("items")?.some((item: { discount?: { amount?: number | null } }) =>
|
||||||
@ -279,6 +280,7 @@ export const QuoteDetailsCardEditor = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<BlocksPickerDialog
|
<BlocksPickerDialog
|
||||||
|
languageCode={documentLanguageCode}
|
||||||
onSelect={handleAppendBlock}
|
onSelect={handleAppendBlock}
|
||||||
isOpen={blockPickerDialogOpen}
|
isOpen={blockPickerDialogOpen}
|
||||||
onOpenChange={setBlockPickerDialogOpen}
|
onOpenChange={setBlockPickerDialogOpen}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
dist/client/index.html
vendored
2
dist/client/index.html
vendored
@ -8,7 +8,7 @@
|
|||||||
<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>Presupuestador Uecko</title>
|
<title>Presupuestador Uecko</title>
|
||||||
<script type="module" crossorigin src="/assets/index-AMb79QFB.js"></script>
|
<script type="module" crossorigin src="/assets/index-zvH6W8yt.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Cipqnmx2.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Cipqnmx2.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
2
dist/client/version.json
vendored
2
dist/client/version.json
vendored
@ -1 +1 @@
|
|||||||
{"buildId":"dfdc0c24-169e-420e-b97a-aa406689e0d0"}
|
{"buildId":"073e7786-0ae3-4df4-8650-2f0d78bdc9e9"}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uecko-presupuestador",
|
"name": "uecko-presupuestador",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"author": "Rodax Software <dev@rodax-software.com>",
|
"author": "Rodax Software <dev@rodax-software.com>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@uecko-presupuestador/server",
|
"name": "@uecko-presupuestador/server",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"author": "Rodax Software <dev@rodax-software.com>",
|
"author": "Rodax Software <dev@rodax-software.com>",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@uecko-presupuestador/shared",
|
"name": "@uecko-presupuestador/shared",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"main": "./index.ts",
|
"main": "./index.ts",
|
||||||
"author": "Rodax Software <dev@rodax-software.com>",
|
"author": "Rodax Software <dev@rodax-software.com>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user