Selector para idioma al genera el PDF, tanto en alta como en modificación de cotizaciones.

This commit is contained in:
David Arranz 2026-10-08 12:35:37 +02:00
parent e6001d62a9
commit 08f01e2b3b
9 changed files with 93 additions and 35 deletions

View File

@ -2,6 +2,7 @@ import { Container, FormTextField } from "@/components";
import { FormSubmitButton } from "@/components/Forms/FormSubmitButton";
import { UeckoLogo } from "@/components/UeckoLogo/UeckoLogo";
import { useLogin } from "@/lib/hooks";
import { documentedLanguages } from "@/lib/languages";
import {
Alert,
AlertDescription,
@ -29,17 +30,8 @@ import { SubmitHandler, useForm } from "react-hook-form";
import { Trans, useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import SpanishJoiMessages from "../../spanish-joi-messages.json";
import "/node_modules/flag-icons/css/flag-icons.min.css";
type LoginDataForm = ILogin_DTO;
const languages = [
{ code: "en", name: "English", flag: () => <span className='fi fi-gb'></span> },
{ code: "es", name: "Español", flag: () => <span className='fi fi-es'></span> },
//{ code: "fr", name: "Français", flag: () => <span className='fi fi-fr'></span> },
//{ code: "it", name: "Italiano", flag: () => <span className='fi fi-it'></span> },
];
export const LoginPageWithLanguageSelector = () => {
const { t, i18n } = useTranslation();
@ -49,7 +41,7 @@ export const LoginPageWithLanguageSelector = () => {
useEffect(() => {
const browserLang = navigator.language.split("-")[0];
const supportedLang = languages.find((lang) => lang.code === browserLang);
const supportedLang = documentedLanguages.find((lang) => lang.code === browserLang);
if (supportedLang) {
changeLanguage(supportedLang.code);
}
@ -113,16 +105,19 @@ export const LoginPageWithLanguageSelector = () => {
<DropdownMenuTrigger asChild>
<Button variant='outline' className='w-[140px] justify-start'>
<span className='mr-2'>
{languages.find((lang) => lang.code === language)?.flag()}
<span
aria-hidden='true'
className={documentedLanguages.find((lang) => lang.code === language)?.flagClassName}
/>
</span>
{languages.find((lang) => lang.code === language)?.name}
{documentedLanguages.find((lang) => lang.code === language)?.name}
<ChevronDownIcon className='w-4 h-4 ml-auto opacity-50' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='w-[140px]'>
{languages.map((lang) => (
{documentedLanguages.map((lang) => (
<DropdownMenuItem key={lang.code} onSelect={() => changeLanguage(lang.code)}>
<span className='mr-2'>{lang.flag()}</span>
<span aria-hidden='true' className={`mr-2 ${lang.flagClassName}`} />
{lang.name}
</DropdownMenuItem>
))}

View File

@ -1,10 +1,11 @@
import { FormDatePickerField, FormGroup, FormTextAreaField, FormTextField } from "@/components";
import { Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui";
import { documentedLanguages } from "@/lib/languages";
import { t } from "i18next";
import { useFormContext } from "react-hook-form";
import { Controller, useFormContext } from "react-hook-form";
export const QuoteGeneralCardEditor = () => {
const { formState } = useFormContext();
const { formState, control } = useFormContext();
return (
<div className='grid gap-6 md:grid-cols-6'>
@ -12,6 +13,35 @@ export const QuoteGeneralCardEditor = () => {
className='md:col-span-6'
title={t("quotes.create.form_groups.general.title")}
description={t("quotes.create.form_groups.general.desc")}
actions={
<div className='grid min-w-48 gap-1'>
<Label htmlFor='lang_code' className='text-xs text-muted-foreground'>
{t("quotes.form_fields.lang_code.label")}
</Label>
<Controller
control={control}
name='lang_code'
render={({ field }) => (
<Select value={field.value || "es"} onValueChange={field.onChange}>
<SelectTrigger
id='lang_code'
aria-label={t("quotes.form_fields.lang_code.label")}
>
<SelectValue placeholder={t("quotes.form_fields.lang_code.placeholder")} />
</SelectTrigger>
<SelectContent>
{documentedLanguages.map((language) => (
<SelectItem key={language.code} value={language.code}>
<span aria-hidden='true' className={`mr-2 ${language.flagClassName}`} />
{language.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</div>
}
>
<div className='grid grid-cols-6 gap-6'>
<FormTextField
@ -89,14 +119,6 @@ export const QuoteGeneralCardEditor = () => {
</SelectContent>
</Select>
</div>
<FormTextField
required
label={t("quotes.form_fields.lang_code.label")}
description={t("quotes.form_fields.lang_code.desc")}
//disabled={formState.disabled}
placeholder={t("quotes.form_fields.lang_code.placeholder")}
name='lang_code'
/>
<FormTextField
required
label={t("quotes.form_fields.currency_code.label")}

View File

@ -10,16 +10,17 @@ import { t } from "i18next";
import { SubmitButton } from "@/components";
import { useUnsavedChangesNotifier } from "@/lib/hooks";
import { Button, Form, Separator } from "@/ui";
import { Button, Form, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator } from "@/ui";
import { useToast } from "@/ui/use-toast";
import { joiResolver } from "@hookform/resolvers/joi";
import { ICreateQuote_Request_DTO } from "@shared/contexts";
import Joi from "joi";
import { useMemo } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { Controller, SubmitHandler, useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import SpanishJoiMessages from "../../spanish-joi-messages.json";
import { useQuotes } from "./hooks";
import { documentedLanguages } from "@/lib/languages";
interface QuoteDataForm extends ICreateQuote_Request_DTO {}
@ -32,6 +33,7 @@ export const QuoteCreate = () => {
const defaultValues = useMemo(
() => ({
date: new Date(Date.now()).toUTCString(),
lang_code: "es",
customer_reference: "",
customer_information: "",
//reference: "",
@ -141,6 +143,30 @@ export const QuoteCreate = () => {
name='date'
/>
<div className='grid gap-2'>
<Label htmlFor='lang_code'>{t("quotes.form_fields.lang_code.label")}</Label>
<Controller
control={form.control}
name='lang_code'
render={({ field }) => (
<Select value={field.value || "es"} onValueChange={field.onChange}>
<SelectTrigger id='lang_code' aria-label={t("quotes.form_fields.lang_code.label")}>
<SelectValue />
</SelectTrigger>
<SelectContent>
{documentedLanguages.map((language) => (
<SelectItem key={language.code} value={language.code}>
<span aria-hidden='true' className={`mr-2 ${language.flagClassName}`} />
{language.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
<p className='text-sm text-muted-foreground'>{t("quotes.form_fields.lang_code.desc")}</p>
</div>
<Separator />
<FormTextAreaField

View File

@ -0,0 +1,7 @@
import "/node_modules/flag-icons/css/flag-icons.min.css";
/** Presentation-only options shared by selectors for supported document languages. */
export const documentedLanguages = [
{ code: "es", name: "Español", flagClassName: "fi fi-es" },
{ code: "en", name: "English", flagClassName: "fi fi-gb" },
] as const;

View File

@ -339,9 +339,10 @@
"placeholder": ""
},
"lang_code": {
"label": "Language",
"desc": "Quote language",
"placeholder": ""
"label": "Document language",
"desc": "Language used to generate the quote PDF",
"placeholder": "",
"options": { "es": "Spanish", "en": "English" }
},
"currency_code": {
"label": "Currency",

View File

@ -339,9 +339,10 @@
"placeholder": ""
},
"lang_code": {
"label": "Idioma",
"desc": "Idioma de la cotización",
"placeholder": ""
"label": "Idioma del documento",
"desc": "Idioma utilizado para generar el presupuesto en PDF",
"placeholder": "",
"options": { "es": "Español", "en": "Inglés" }
},
"currency_code": {
"label": "Moneda",

View File

@ -150,7 +150,7 @@ export class CreateQuoteUseCase
}
const languageOrError = Language.createFromCode(
quoteDTO.lang_code ?? this._dealer?.language.code
quoteDTO.lang_code || Language.DEFAULT_LANGUAGE_CODE
);
if (languageOrError.isFailure) {
return Result.fail(languageOrError.error);

View File

@ -165,7 +165,9 @@ export class UpdateQuoteUseCase
return Result.fail(referenceOrError.error);
}
const languageOrError = Language.createFromCode(quoteDTO.lang_code);
const languageOrError = Language.createFromCode(
quoteDTO.lang_code || Language.DEFAULT_LANGUAGE_CODE
);
if (languageOrError.isFailure) {
return Result.fail(languageOrError.error);
}

View File

@ -50,7 +50,11 @@ class QuoteMapper
date: this.mapsValue(source, "date", UTCDateValue.create),
reference: this.mapsValue(source, "reference", QuoteReference.create),
currency: this.mapsValue(source, "currency_code", CurrencyData.createFromCode),
language: this.mapsValue(source, "lang_code", Language.createFromCode),
language: this.mapsValue(
source,
"lang_code",
(value) => Language.createFromCode(value || Language.DEFAULT_LANGUAGE_CODE)
),
customerReference: this.mapsValue(
source,
"customer_reference",