feat: internationalize button labels and UI components
- Updated CancelButton and SubmitButton to use i18next for localization. - Modified DataTableColumnHeader and DataTableFacetedFilter to include translations for sorting and filtering messages. - Enhanced DataTableSelectionColumn with localized aria-labels for accessibility. - Localized titles and messages in SimpleEmptyState and ErrorOverlay components. - Updated LayoutHeader to use i18next for navigation toggle label. - Integrated i18next into useLogout for error messages. - Localized alert dialog actions in useRemoveAlertDialog. - Added new translations in en.json and es.json for various UI components. - Updated breadcrumb and carousel components to use localized strings. - Enhanced pagination component with i18next for navigation labels. - Localized sheet component close button text.
This commit is contained in:
parent
988790f904
commit
c77a3a3af5
@ -73,7 +73,7 @@ export const LoginPageWithLanguageSelector = () => {
|
|||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const { message } = error;
|
const { message } = error;
|
||||||
toast({ description: message, title: "Error" });
|
toast({ description: message, title: t("common.error") });
|
||||||
console.log(error);
|
console.log(error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export const CatalogDataTable = () => {
|
|||||||
if (data?.total_items === 0 && !isFiltered) {
|
if (data?.total_items === 0 && !isFiltered) {
|
||||||
return (
|
return (
|
||||||
<SimpleEmptyState
|
<SimpleEmptyState
|
||||||
subtitle='Empieza cargando los artículos del catálogo'
|
subtitle={t("catalog.list.empty_state.subtitle")}
|
||||||
buttonText=''
|
buttonText=''
|
||||||
onButtonClick={() => navigate("/catalog/add")}
|
onButtonClick={() => navigate("/catalog/add")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -80,10 +80,10 @@ export function CatalogDataTableFilter<TData>({
|
|||||||
<XIcon className='w-4 h-4' />
|
<XIcon className='w-4 h-4' />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("catalog.filter.badget_remove_tooltip")}</p>
|
<p>{t("common.filter.badget_remove_tooltip")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span className='sr-only'>{t("catalog.filter.badget_remove_tooltip")}</span>
|
<span className='sr-only'>{t("common.filter.badget_remove_tooltip")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
|||||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||||
import { IListArticles_Response_DTO } from "@shared/contexts";
|
import { IListArticles_Response_DTO } from "@shared/contexts";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { t } from "i18next";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useDealerList } from "../hooks";
|
import { useDealerList } from "../hooks";
|
||||||
@ -70,7 +71,7 @@ export const DealerDataTable = () => {
|
|||||||
if (data?.total_items === 0 && !isFiltered) {
|
if (data?.total_items === 0 && !isFiltered) {
|
||||||
return (
|
return (
|
||||||
<SimpleEmptyState
|
<SimpleEmptyState
|
||||||
subtitle='Empieza cargando los artículos del catálogo'
|
subtitle={t("catalog.list.empty_state.subtitle")}
|
||||||
buttonText=''
|
buttonText=''
|
||||||
onButtonClick={() => navigate("/catalog/add")}
|
onButtonClick={() => navigate("/catalog/add")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -254,7 +254,7 @@ export const CatalogPickerDataTable = ({
|
|||||||
if (data?.total_items === 0 && !isFiltered) {
|
if (data?.total_items === 0 && !isFiltered) {
|
||||||
return (
|
return (
|
||||||
<SimpleEmptyState
|
<SimpleEmptyState
|
||||||
subtitle='Empieza cargando los artículos del catálogo'
|
subtitle={t("catalog.list.empty_state.subtitle")}
|
||||||
buttonText=''
|
buttonText=''
|
||||||
onButtonClick={() => navigate("/catalog/add")}
|
onButtonClick={() => navigate("/catalog/add")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Button, Card, CardContent, CardHeader, CardTitle, Progress } from "@/ui";
|
import { Button, Card, CardContent, CardHeader, CardTitle, Progress } from "@/ui";
|
||||||
import { FileTextIcon, Loader2Icon, XCircleIcon } from "lucide-react";
|
import { FileTextIcon, Loader2Icon, XCircleIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
interface PDFGenerationLoadingProps {
|
interface PDFGenerationLoadingProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -38,7 +39,7 @@ export const PDFGenerationLoading = ({
|
|||||||
<Card className={cn("overflow-hidden", className)}>
|
<Card className={cn("overflow-hidden", className)}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className='flex items-center justify-between'>
|
<CardTitle className='flex items-center justify-between'>
|
||||||
{error?.errorMessage ? "Error en la generación del PDF" : "Generando PDF"}
|
{error?.errorMessage ? t("quotes.pdf_generation.error") : t("quotes.pdf_generation.title")}
|
||||||
{isInProgress && <Loader2Icon className='w-4 h-4 animate-spin' />}
|
{isInProgress && <Loader2Icon className='w-4 h-4 animate-spin' />}
|
||||||
{error?.errorMessage && <XCircleIcon className='w-4 h-4 text-destructive' />}
|
{error?.errorMessage && <XCircleIcon className='w-4 h-4 text-destructive' />}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@ -60,14 +61,14 @@ export const PDFGenerationLoading = ({
|
|||||||
</div>
|
</div>
|
||||||
<Progress value={percentage} className='w-full' />
|
<Progress value={percentage} className='w-full' />
|
||||||
<div className='text-sm text-center text-muted-foreground'>
|
<div className='text-sm text-center text-muted-foreground'>
|
||||||
<p>Progreso: {percentage}%</p>
|
<p>{t("quotes.pdf_generation.progress", { percentage })}</p>
|
||||||
<div className='hidden'>
|
<div className='hidden'>
|
||||||
<p>Tiempo transcurrido: {elapsed} segundos</p>
|
<p>{t("quotes.pdf_generation.elapsed", { elapsed })}</p>
|
||||||
{estimatedTime > 0 && <p>Tiempo estimado restante: {estimatedTime} segundos</p>}
|
{estimatedTime > 0 && <p>{t("quotes.pdf_generation.remaining", { estimatedTime })}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button variant='destructive' onClick={cancel} className='hidden'>
|
<Button variant='destructive' onClick={cancel} className='hidden'>
|
||||||
Cancelar generación
|
{t("quotes.pdf_generation.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Card, CardContent } from "@/ui";
|
import { Card, CardContent } from "@/ui";
|
||||||
import { IListQuotes_Response_DTO } from "@shared/contexts";
|
import { IListQuotes_Response_DTO } from "@shared/contexts";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { t } from "i18next";
|
||||||
import { useQuotes } from "../hooks";
|
import { useQuotes } from "../hooks";
|
||||||
import { PDFGenerationLoading } from "./PDFGenerationLoading";
|
import { PDFGenerationLoading } from "./PDFGenerationLoading";
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ const QuotePDFPreview = ({
|
|||||||
return (
|
return (
|
||||||
<Card className={cn("overflow-hidden", className)}>
|
<Card className={cn("overflow-hidden", className)}>
|
||||||
<CardContent className='px-4 py-6 text-center'>
|
<CardContent className='px-4 py-6 text-center'>
|
||||||
<p className='mx-auto'>Select a quote</p>
|
<p className='mx-auto'>{t("quotes.list.resume.select_quote")}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -134,7 +134,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
return (
|
return (
|
||||||
<Card className={cn("overflow-hidden", className)}>
|
<Card className={cn("overflow-hidden", className)}>
|
||||||
<CardContent className='px-4 py-6 text-center'>
|
<CardContent className='px-4 py-6 text-center'>
|
||||||
<p className='mx-auto'>Select a quote</p>
|
<p className='mx-auto'>{t("quotes.list.resume.select_quote")}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@ -204,11 +204,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon className='w-4 h-4' />
|
<EditIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Editar</span>
|
<span className='sr-only'>{t("common.edit")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Editar</p>
|
<p>{t("common.edit")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -223,11 +223,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className='w-4 h-4' />
|
<CopyIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Duplicar</span>
|
<span className='sr-only'>{t("common.duplicate")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Duplicar</p>
|
<p>{t("common.duplicate")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -235,11 +235,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant='outline' size='icon' onClick={handleDownload}>
|
<Button variant='outline' size='icon' onClick={handleDownload}>
|
||||||
<DownloadIcon className='w-4 h-4' />
|
<DownloadIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Descargar</span>
|
<span className='sr-only'>{t("common.download")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Descargar</p>
|
<p>{t("common.download")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -255,11 +255,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArchiveIcon className='w-4 h-4' />
|
<ArchiveIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Archivar</span>
|
<span className='sr-only'>{t("common.archive")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Archivar</p>
|
<p>{t("common.archive")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|||||||
@ -277,11 +277,11 @@ export const QuotesDataTable = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon className='w-4 h-4' />
|
<EditIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Editar</span>
|
<span className='sr-only'>{t("common.edit")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Editar</p>
|
<p>{t("common.edit")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -296,11 +296,11 @@ export const QuotesDataTable = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className='w-4 h-4' />
|
<CopyIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Duplicar</span>
|
<span className='sr-only'>{t("common.duplicate")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Duplicar</p>
|
<p>{t("common.duplicate")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -314,11 +314,11 @@ export const QuotesDataTable = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DownloadIcon className='w-4 h-4' />
|
<DownloadIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Descargar</span>
|
<span className='sr-only'>{t("common.download")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Descargar</p>
|
<p>{t("common.download")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -334,11 +334,11 @@ export const QuotesDataTable = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArchiveIcon className='w-4 h-4' />
|
<ArchiveIcon className='w-4 h-4' />
|
||||||
<span className='sr-only'>Archivar</span>
|
<span className='sr-only'>{t("common.archive")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Archivar</p>
|
<p>{t("common.archive")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
@ -398,7 +398,7 @@ export const QuotesDataTable = ({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<SimpleEmptyState
|
<SimpleEmptyState
|
||||||
subtitle='Empieza creando alguna cotización'
|
subtitle={t("quotes.list.empty_state.subtitle")}
|
||||||
buttonText={t("quotes.create.title")}
|
buttonText={t("quotes.create.title")}
|
||||||
onButtonClick={() => navigate("add", { relative: "path" })}
|
onButtonClick={() => navigate("add", { relative: "path" })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export const QuoteDocumentsCardEditor = () => {
|
|||||||
render={() => (
|
render={() => (
|
||||||
<div className='space-y-6'>
|
<div className='space-y-6'>
|
||||||
<FormItem className='w-full'>
|
<FormItem className='w-full'>
|
||||||
<FormLabel>Images</FormLabel>
|
<FormLabel>{t("quotes.create.form_groups.documents.images")}</FormLabel>
|
||||||
<FormControl></FormControl>
|
<FormControl></FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -32,7 +32,7 @@ export const QuoteDocumentsCardEditor = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Button className='w-fit' disabled={formState.disabled}>
|
<Button className='w-fit' disabled={formState.disabled}>
|
||||||
Save
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -77,15 +77,15 @@ export const QuoteGeneralCardEditor = () => {
|
|||||||
>
|
>
|
||||||
<div className='grid gap-6'>
|
<div className='grid gap-6'>
|
||||||
<div className='grid gap-3'>
|
<div className='grid gap-3'>
|
||||||
<Label htmlFor='status'>Status</Label>
|
<Label htmlFor='status'>{t("quotes.create.form_groups.status.title")}</Label>
|
||||||
<Select>
|
<Select>
|
||||||
<SelectTrigger id='status' aria-label='Select status'>
|
<SelectTrigger id='status' aria-label={t("quotes.list.select_status")}>
|
||||||
<SelectValue placeholder='Select status' />
|
<SelectValue placeholder={t("quotes.list.select_status")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value='draft'>Draft</SelectItem>
|
<SelectItem value='draft'>{t("quotes.status.draft")}</SelectItem>
|
||||||
<SelectItem value='published'>Active</SelectItem>
|
<SelectItem value='published'>{t("quotes.status.ready")}</SelectItem>
|
||||||
<SelectItem value='archived'>Archived</SelectItem>
|
<SelectItem value='archived'>{t("quotes.status.archived")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export const QuoteCreate = () => {
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.debug(error);
|
console.debug(error);
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: t("common.error"),
|
||||||
description: error.message,
|
description: error.message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
@ -77,7 +77,7 @@ export const QuoteCreate = () => {
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
reset(getValues());
|
reset(getValues());
|
||||||
toast({
|
toast({
|
||||||
title: "Cotización creada",
|
title: t("quotes.notifications.created"),
|
||||||
variant: "default",
|
variant: "default",
|
||||||
});
|
});
|
||||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||||
@ -89,7 +89,7 @@ export const QuoteCreate = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isSubmitting) {
|
if (isSubmitting) {
|
||||||
return <LoadingOverlay title='Guardando ajustes' />;
|
return <LoadingOverlay title={t("quotes.notifications.saving")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (isError) {
|
/*if (isError) {
|
||||||
|
|||||||
@ -314,13 +314,13 @@ export const QuoteEdit = () => {
|
|||||||
mutate(data, {
|
mutate(data, {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast({ title: "Error", description: error.message, variant: "destructive" });
|
toast({ title: t("common.error"), description: error.message, variant: "destructive" });
|
||||||
//alert(error.message);
|
//alert(error.message);
|
||||||
},
|
},
|
||||||
//onSettled: () => {},
|
//onSettled: () => {},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
reset(getValues());
|
reset(getValues());
|
||||||
toast({ title: "Cotización guardada", variant: "success" });
|
toast({ title: t("quotes.notifications.saved"), variant: "success" });
|
||||||
if (shouldRedirect) {
|
if (shouldRedirect) {
|
||||||
navigate("/quotes");
|
navigate("/quotes");
|
||||||
}
|
}
|
||||||
@ -354,7 +354,7 @@ export const QuoteEdit = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(isSubmitting || isPending) && <LoadingOverlay title='Guardando cotización' />}
|
{(isSubmitting || isPending) && <LoadingOverlay title={t("quotes.notifications.saving")} />}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={handleSubmit((data) => onSubmit(data, false))}>
|
<form onSubmit={handleSubmit((data) => onSubmit(data, false))}>
|
||||||
<div className='mx-auto grid max-w-[90rem] flex-1 auto-rows-max gap-6'>
|
<div className='mx-auto grid max-w-[90rem] flex-1 auto-rows-max gap-6'>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { Checkbox } from "@/ui";
|
|||||||
import { ColumnDef, Row } from "@tanstack/react-table";
|
import { ColumnDef, Row } from "@tanstack/react-table";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { t } from "i18next";
|
||||||
import { QuoteItemsSortableDragHandleCell } from "../components/QuoteItemsSortableDragHandleCell";
|
import { QuoteItemsSortableDragHandleCell } from "../components/QuoteItemsSortableDragHandleCell";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@ -53,7 +54,7 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
|
|||||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
}
|
}
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
aria-label='Seleccionar todo'
|
aria-label={t("common.select_all")}
|
||||||
className='translate-y-[0px]'
|
className='translate-y-[0px]'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -64,7 +65,7 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
|
|||||||
checked={row.getIsSelected()}
|
checked={row.getIsSelected()}
|
||||||
disabled={!row.getCanSelect()}
|
disabled={!row.getCanSelect()}
|
||||||
onCheckedChange={row.getToggleSelectedHandler()}
|
onCheckedChange={row.getToggleSelectedHandler()}
|
||||||
aria-label='Seleccionar fila'
|
aria-label={t("common.select_row")}
|
||||||
className='mt-2'
|
className='mt-2'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { DataTableProvider } from "@/lib/hooks";
|
import { DataTableProvider } from "@/lib/hooks";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { QuotesDataTable } from "./components";
|
import { QuotesDataTable } from "./components";
|
||||||
|
|
||||||
import { HelpButton } from "@/components";
|
import { HelpButton } from "@/components";
|
||||||
@ -19,12 +19,12 @@ import {
|
|||||||
Toggle,
|
Toggle,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import { useToggle } from "@wojtekmaj/react-hooks";
|
import { useToggle } from "@wojtekmaj/react-hooks";
|
||||||
import { t } from "i18next";
|
|
||||||
import { EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react";
|
import { EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export const QuotesList = () => {
|
export const QuotesList = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [status, setStatus] = useState("all");
|
const [status, setStatus] = useState("all");
|
||||||
const [enabledPreview, toggleEnabledPreview] = useToggle(true);
|
const [enabledPreview, toggleEnabledPreview] = useToggle(true);
|
||||||
@ -72,7 +72,7 @@ export const QuotesList = () => {
|
|||||||
<Label>{t("quotes.list.tabs_title")}</Label>
|
<Label>{t("quotes.list.tabs_title")}</Label>
|
||||||
<Select value={status} onValueChange={setStatus}>
|
<Select value={status} onValueChange={setStatus}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder='Seleccionar estado' />
|
<SelectValue placeholder={t("quotes.list.select_status")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{quoteStatuses.map((s) => (
|
{quoteStatuses.map((s) => (
|
||||||
@ -86,14 +86,14 @@ export const QuotesList = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className='flex items-baseline justify-center mr-4 font-medium'>
|
<div className='flex items-baseline justify-center mr-4 font-medium'>
|
||||||
<HelpButton
|
<HelpButton
|
||||||
buttonText='Ayuda'
|
buttonText={t("common.help")}
|
||||||
title='Ayuda sobre el Estado de Cotizaciones'
|
title={t("quotes.list.status_help.title")}
|
||||||
content={HelpContent}
|
content={<HelpContent />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 ml-auto'>
|
<div className='flex items-center gap-2 ml-auto'>
|
||||||
<Toggle
|
<Toggle
|
||||||
aria-label='Show quote preview'
|
aria-label={t("quotes.list.preview_aria")}
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
defaultPressed={false}
|
defaultPressed={false}
|
||||||
pressed={enabledPreview}
|
pressed={enabledPreview}
|
||||||
@ -123,35 +123,32 @@ export const QuotesList = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const HelpContent = (
|
const HelpContent = () => {
|
||||||
<ScrollArea className='grid gap-4 py-4'>
|
const { t } = useTranslation();
|
||||||
<p>El estado de una cotización indica su posición actual en el proceso de negocio.</p>
|
|
||||||
<p>Los estados posibles son:</p>
|
return <ScrollArea className='grid gap-4 py-4'>
|
||||||
|
<p>{t("quotes.list.status_help.intro")}</p>
|
||||||
|
<p>{t("quotes.list.status_help.possible")}</p>
|
||||||
<ul className='pl-6 space-y-2 list-disc'>
|
<ul className='pl-6 space-y-2 list-disc'>
|
||||||
<li>
|
<li>
|
||||||
<strong>Borrador:</strong> La cotización está en proceso de creación o edición.
|
<strong>{t("quotes.status.draft")}:</strong> {t("quotes.quote_status_editor.status.draft.description")}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Preparado:</strong> La cotización está lista para ser enviada al cliente.
|
<strong>{t("quotes.status.ready")}:</strong> {t("quotes.quote_status_editor.status.ready.description")}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Entregado:</strong> La cotización ha sido enviada al cliente.
|
<strong>{t("quotes.status.delivered")}:</strong> {t("quotes.quote_status_editor.status.delivered.description")}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Aceptado:</strong> El cliente ha aprobado la cotización.
|
<strong>{t("quotes.status.accepted")}:</strong> {t("quotes.quote_status_editor.status.accepted.description")}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Rechazado:</strong> El cliente no ha aceptado la cotización.
|
<strong>{t("quotes.status.rejected")}:</strong> {t("quotes.quote_status_editor.status.rejected.description")}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Arcivado:</strong> La cotización ha sido guardada para referencia futura y ya no
|
<strong>{t("quotes.status.archived")}:</strong> {t("quotes.quote_status_editor.status.archived.description")}
|
||||||
está activa.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>{t("quotes.list.status_help.outro")}</p>
|
||||||
Utiliza estos estados para hacer un seguimiento eficiente de tus cotizaciones y optimizar tu
|
</ScrollArea>;
|
||||||
proceso de ventas. El estado <strong>Archivado</strong> es útil para mantener un historial de
|
};
|
||||||
cotizaciones pasadas sin que interfieran con las cotizaciones activas.
|
|
||||||
</p>
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
|
|||||||
@ -133,7 +133,7 @@ export const SettingsEditor = () => {
|
|||||||
mutate(data, {
|
mutate(data, {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.debug(error);
|
console.debug(error);
|
||||||
toast({ title: "Error", description: error.message, variant: "destructive" });
|
toast({ title: t("common.error"), description: error.message, variant: "destructive" });
|
||||||
//alert(error.message);
|
//alert(error.message);
|
||||||
},
|
},
|
||||||
//onSettled: () => {},
|
//onSettled: () => {},
|
||||||
@ -142,13 +142,13 @@ export const SettingsEditor = () => {
|
|||||||
void i18n.changeLanguage(data.lang_code ?? "es");
|
void i18n.changeLanguage(data.lang_code ?? "es");
|
||||||
}
|
}
|
||||||
reset(getValues());
|
reset(getValues());
|
||||||
toast({ description: "Ajustes guardados", variant: "success" });
|
toast({ description: t("settings.notifications.saved"), variant: "success" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isSubmitting) {
|
if (isSubmitting) {
|
||||||
return <LoadingOverlay title='Guardando ajustes' />;
|
return <LoadingOverlay title={t("settings.notifications.saving")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === "error") {
|
if (status === "error") {
|
||||||
@ -219,8 +219,8 @@ export const SettingsEditor = () => {
|
|||||||
<SelectValue placeholder={t("settings.form_fields.language.placeholder")} />
|
<SelectValue placeholder={t("settings.form_fields.language.placeholder")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value='es'>Español</SelectItem>
|
<SelectItem value='es'>{t("settings.form_fields.language.options.es")}</SelectItem>
|
||||||
<SelectItem value='en'>English</SelectItem>
|
<SelectItem value='en'>{t("settings.form_fields.language.options.en")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -30,12 +30,12 @@ import Joi from "joi";
|
|||||||
import { HelpCircleIcon } from "lucide-react";
|
import { HelpCircleIcon } from "lucide-react";
|
||||||
import { useSupport } from "../hooks";
|
import { useSupport } from "../hooks";
|
||||||
|
|
||||||
const formSchema = Joi.object({
|
const createFormSchema = () => Joi.object({
|
||||||
incidence: Joi.string().min(10).required().messages({
|
incidence: Joi.string().min(10).required().messages({
|
||||||
"string.empty": "Debe escribir algo antes de enviar",
|
"string.empty": t("support.modal.validation.empty"),
|
||||||
"string.min": "El texto es demasiado corto. Debe tener al menos 10 caracteres",
|
"string.min": t("support.modal.validation.min"),
|
||||||
"string.max": "El texto es demasiado largo.",
|
"string.max": t("support.modal.validation.max"),
|
||||||
"any.required": "La descripción es requerida",
|
"any.required": t("support.modal.validation.required"),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export default function SupportModal() {
|
|||||||
|
|
||||||
const form = useForm<SupportDataForm>({
|
const form = useForm<SupportDataForm>({
|
||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
resolver: joiResolver(formSchema),
|
resolver: joiResolver(createFormSchema()),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
incidence: "",
|
incidence: "",
|
||||||
},
|
},
|
||||||
@ -63,8 +63,8 @@ export default function SupportModal() {
|
|||||||
mutateOptions: {
|
mutateOptions: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast({
|
toast({
|
||||||
title: "Incidencia enviada",
|
title: t("support.modal.notifications.sent_title"),
|
||||||
description: "La incidencia se ha enviado correctamente",
|
description: t("support.modal.notifications.sent_description"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
});
|
});
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
@ -72,9 +72,8 @@ export default function SupportModal() {
|
|||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({
|
toast({
|
||||||
title: "Error en el envío",
|
title: t("support.modal.notifications.error_title"),
|
||||||
description:
|
description: t("support.modal.notifications.error_description"),
|
||||||
"No se ha podido enviar la incidencia correctamente. Por favor, inténtalo de nuevo.",
|
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -111,7 +110,7 @@ export default function SupportModal() {
|
|||||||
onClick={() => setIsOpen(true)}
|
onClick={() => setIsOpen(true)}
|
||||||
>
|
>
|
||||||
<HelpCircleIcon className='w-5 h-5' />
|
<HelpCircleIcon className='w-5 h-5' />
|
||||||
<span className='sr-only'>Abrir ventana de soporte</span>
|
<span className='sr-only'>{t("support.modal.open")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className='sm:max-w-xl'>
|
<DialogContent className='sm:max-w-xl'>
|
||||||
@ -123,14 +122,14 @@ export default function SupportModal() {
|
|||||||
<form onSubmit={handleSubmit(onSubmit)} className='space-y-4'>
|
<form onSubmit={handleSubmit(onSubmit)} className='space-y-4'>
|
||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
name='incidence'
|
name='incidence'
|
||||||
placeholder='Describe la incidencia aquí...'
|
placeholder={t("support.modal.placeholder")}
|
||||||
className='min-h-96'
|
className='min-h-96'
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button type='button' variant='outline' onClick={handleClose}>
|
<Button type='button' variant='outline' onClick={handleClose}>
|
||||||
Cancelar
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type='submit'>Enviar incidencia</Button>
|
<Button type='submit'>{t("support.modal.submit")}</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
@ -140,17 +139,16 @@ export default function SupportModal() {
|
|||||||
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>¿Estás seguro de que quieres cancelar?</AlertDialogTitle>
|
<AlertDialogTitle>{t("support.modal.cancel_confirmation.title")}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Has escrito texto en el campo de descripción. Si cierras la ventana, perderás los
|
{t("support.modal.cancel_confirmation.description")}
|
||||||
cambios no guardados.
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel onClick={() => setShowConfirmDialog(false)}>
|
<AlertDialogCancel onClick={() => setShowConfirmDialog(false)}>
|
||||||
Volver al formulario
|
{t("support.modal.cancel_confirmation.back")}
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction onClick={confirmClose}>Sí, cerrar</AlertDialogAction>
|
<AlertDialogAction onClick={confirmClose}>{t("support.modal.cancel_confirmation.confirm")}</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Button, ButtonProps } from "@/ui";
|
import { Button, ButtonProps } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
export interface CancelButtonProps extends ButtonProps {
|
export interface CancelButtonProps extends ButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CancelButton = ({ label = "Cancelar", ...props }: CancelButtonProps): JSX.Element => {
|
export const CancelButton = ({ label = t("common.cancel"), ...props }: CancelButtonProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Button type='button' variant='secondary' {...props}>
|
<Button type='button' variant='secondary' {...props}>
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { ButtonProps } from "@/ui";
|
import { ButtonProps } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
import { CustomButton } from "./CustomButton";
|
import { CustomButton } from "./CustomButton";
|
||||||
|
|
||||||
export interface SubmitButtonProps extends ButtonProps {
|
export interface SubmitButtonProps extends ButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubmitButton = ({ label = "Enviar", ...props }: SubmitButtonProps) => (
|
export const SubmitButton = ({ label = t("common.submit"), ...props }: SubmitButtonProps) => (
|
||||||
<CustomButton type='submit' label={label} variant='default' {...props} />
|
<CustomButton type='submit' label={label} variant='default' {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
|||||||
? t("common.sort_desc_description")
|
? t("common.sort_desc_description")
|
||||||
: header.column.getIsSorted() === "asc"
|
: header.column.getIsSorted() === "asc"
|
||||||
? t("common.sort_asc_description")
|
? t("common.sort_asc_description")
|
||||||
: t("sort_none_description")
|
: t("common.sort_none_description")
|
||||||
}
|
}
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@ -121,13 +121,13 @@ export function DataTableColumnHeader<TData, TValue>({
|
|||||||
{header.column.getCanHide() && (
|
{header.column.getCanHide() && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => header.column.toggleVisibility(false)}
|
onClick={() => header.column.toggleVisibility(false)}
|
||||||
aria-label={t("Hide")}
|
aria-label={t("common.hide")}
|
||||||
>
|
>
|
||||||
<EyeOffIcon
|
<EyeOffIcon
|
||||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
||||||
aria-hidden='true'
|
aria-hidden='true'
|
||||||
/>
|
/>
|
||||||
{t("Hide")}
|
{t("common.hide")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
|
|
||||||
import { Separator } from "@/ui"
|
import { Separator } from "@/ui"
|
||||||
import { CheckIcon, PlusCircleIcon } from 'lucide-react'
|
import { CheckIcon, PlusCircleIcon } from 'lucide-react'
|
||||||
|
import { t } from "i18next"
|
||||||
|
|
||||||
interface DataTableFacetedFilterProps<TData, TValue> {
|
interface DataTableFacetedFilterProps<TData, TValue> {
|
||||||
column?: Column<TData, TValue>
|
column?: Column<TData, TValue>
|
||||||
@ -62,7 +63,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="px-1 font-normal rounded-sm"
|
className="px-1 font-normal rounded-sm"
|
||||||
>
|
>
|
||||||
{selectedValues.size} selected
|
{t("components.data_table.selected_count", { count: selectedValues.size })}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
options
|
options
|
||||||
@ -86,7 +87,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
|||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder={title} />
|
<CommandInput placeholder={title} />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
<CommandEmpty>{t("components.data_table.no_results")}</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{options.map((option) => {
|
{options.map((option) => {
|
||||||
const isSelected = selectedValues.has(option.value)
|
const isSelected = selectedValues.has(option.value)
|
||||||
@ -136,7 +137,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
|||||||
onSelect={() => column?.setFilterValue(undefined)}
|
onSelect={() => column?.setFilterValue(undefined)}
|
||||||
className="justify-center text-center"
|
className="justify-center text-center"
|
||||||
>
|
>
|
||||||
Clear filters
|
{t("components.data_table.clear_filters")}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Checkbox } from "@/ui";
|
import { Checkbox } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
import { DataTableColumnProps } from "./DataTable";
|
import { DataTableColumnProps } from "./DataTable";
|
||||||
|
|
||||||
export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnProps<TData, TError> {
|
export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnProps<TData, TError> {
|
||||||
@ -11,7 +12,7 @@ export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnPro
|
|||||||
table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")
|
table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
}
|
}
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
aria-label='Seleccionar todo'
|
aria-label={t("common.select_all")}
|
||||||
className='translate-y-[2px]'
|
className='translate-y-[2px]'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -23,7 +24,7 @@ export function getDataTableSelectionColumn<TData, TError>(): DataTableColumnPro
|
|||||||
onCheckedChange={(value) => {
|
onCheckedChange={(value) => {
|
||||||
row.toggleSelected(!!value);
|
row.toggleSelected(!!value);
|
||||||
}}
|
}}
|
||||||
aria-label='Seleccionar file'
|
aria-label={t("common.select_row")}
|
||||||
className='translate-y-[2px]'
|
className='translate-y-[2px]'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import { Settings2Icon } from "lucide-react";
|
import { Settings2Icon } from "lucide-react";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
interface DataTableColumnOptionsProps<TData> {
|
interface DataTableColumnOptionsProps<TData> {
|
||||||
table: Table<TData>;
|
table: Table<TData>;
|
||||||
@ -27,11 +28,11 @@ export function DataTableColumnOptions<TData>({
|
|||||||
className="hidden h-8 ml-auto lg:flex"
|
className="hidden h-8 ml-auto lg:flex"
|
||||||
>
|
>
|
||||||
<Settings2Icon className="w-4 h-4 mr-2" />
|
<Settings2Icon className="w-4 h-4 mr-2" />
|
||||||
Columnas
|
{t("components.data_table.columns")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[150px]">
|
<DropdownMenuContent align="end" className="w-[150px]">
|
||||||
<DropdownMenuLabel>Columnas</DropdownMenuLabel>
|
<DropdownMenuLabel>{t("components.data_table.columns")}</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{table
|
{table
|
||||||
.getAllColumns()
|
.getAllColumns()
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Button } from "@/ui";
|
import { Button } from "@/ui";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
export const SimpleEmptyState = ({
|
export const SimpleEmptyState = ({
|
||||||
title = "Esto está muy vacío",
|
title = t("components.empty_state.title"),
|
||||||
subtitle = "Empieza solicitando la carga del catálogo",
|
subtitle = t("components.empty_state.subtitle"),
|
||||||
buttonText = "Solicitar carga del catálogo",
|
buttonText = t("components.empty_state.button"),
|
||||||
onButtonClick = () => {},
|
onButtonClick = () => {},
|
||||||
actions = () => <></>,
|
actions = () => <></>,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Button } from "@/ui";
|
import { Button } from "@/ui";
|
||||||
import { HomeIcon } from "lucide-react";
|
import { HomeIcon } from "lucide-react";
|
||||||
|
import { t } from "i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { BackHistoryButton } from "../CustomButtons";
|
import { BackHistoryButton } from "../CustomButtons";
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ interface ErrorOverlayProps {
|
|||||||
};*/
|
};*/
|
||||||
|
|
||||||
export const ErrorOverlay = ({
|
export const ErrorOverlay = ({
|
||||||
title = "Se ha producido un error",
|
title = t("components.error_overlay.title"),
|
||||||
subtitle = undefined,
|
subtitle = undefined,
|
||||||
description = undefined,
|
description = undefined,
|
||||||
errorMessage = undefined,
|
errorMessage = undefined,
|
||||||
@ -42,7 +43,7 @@ ErrorOverlayProps): JSX.Element => {
|
|||||||
<div className='mt-6 space-x-8 md:ml-4'>
|
<div className='mt-6 space-x-8 md:ml-4'>
|
||||||
<BackHistoryButton />
|
<BackHistoryButton />
|
||||||
<Button onClick={() => navigate("/")}>
|
<Button onClick={() => navigate("/")}>
|
||||||
<HomeIcon className='w-4 h-4 mr-2' /> Ir al inicio
|
<HomeIcon className='w-4 h-4 mr-2' /> {t("components.error_overlay.go_home")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Button, Sheet, SheetContent, SheetTrigger } from "@/ui";
|
|||||||
import SupportModal from "@/app/support/components/SupportModal";
|
import SupportModal from "@/app/support/components/SupportModal";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { MenuIcon, Package2Icon } from "lucide-react";
|
import { MenuIcon, Package2Icon } from "lucide-react";
|
||||||
|
import { t } from "i18next";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
@ -58,7 +59,7 @@ export const LayoutHeader = () => {
|
|||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button variant='outline' size='icon' className='shrink-0 md:hidden'>
|
<Button variant='outline' size='icon' className='shrink-0 md:hidden'>
|
||||||
<MenuIcon className='w-5 h-5' />
|
<MenuIcon className='w-5 h-5' />
|
||||||
<span className='sr-only'>Toggle navigation menu</span>
|
<span className='sr-only'>{t("main_menu.toggle_navigation")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent side='left'>
|
<SheetContent side='left'>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
import { useToast } from "@/ui/use-toast";
|
import { useToast } from "@/ui/use-toast";
|
||||||
import { useQueryKey } from "../useQueryKey";
|
import { useQueryKey } from "../useQueryKey";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>) => {
|
export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>) => {
|
||||||
const { onSuccess, onError, ...restParams } = params || {};
|
const { onSuccess, onError, ...restParams } = params || {};
|
||||||
@ -31,7 +32,7 @@ export const useLogout = (params?: UseMutationOptions<AuthActionResponse, Error>
|
|||||||
onError: (error, variables, context) => {
|
onError: (error, variables, context) => {
|
||||||
const { message } = error;
|
const { message } = error;
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: t("common.error"),
|
||||||
description: message,
|
description: message,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
} from "@/ui/alert-dialog";
|
} from "@/ui/alert-dialog";
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
interface UseRemoveAlertDialogProps {
|
interface UseRemoveAlertDialogProps {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
@ -75,7 +76,7 @@ export const useRemoveAlertDialog = ({
|
|||||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel onClick={onCancel}>Cancelar</AlertDialogCancel>
|
<AlertDialogCancel onClick={onCancel}>{t("common.cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant: "destructive" }),
|
buttonVariants({ variant: "destructive" }),
|
||||||
@ -83,7 +84,7 @@ export const useRemoveAlertDialog = ({
|
|||||||
)}
|
)}
|
||||||
onClick={(event) => (onRemove ? onRemove(payload, event) : null)}
|
onClick={(event) => (onRemove ? onRemove(payload, event) : null)}
|
||||||
>
|
>
|
||||||
Eliminar
|
{t("common.remove")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
{
|
{
|
||||||
"translation": {
|
"translation": {
|
||||||
"common": {
|
"common": {
|
||||||
"loading": "Loading...",
|
|
||||||
"required": "required",
|
"required": "required",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"save_close": "Save and Close",
|
"save_close": "Save and Close",
|
||||||
"accept": "OK",
|
|
||||||
"discard": "Discard",
|
"discard": "Discard",
|
||||||
"hide": "Hide",
|
"hide": "Hide",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
@ -16,7 +14,19 @@
|
|||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
|
"pagination": "Pagination",
|
||||||
|
"breadcrumb": "Breadcrumb",
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next",
|
||||||
|
"more_pages": "More pages",
|
||||||
|
"previous_slide": "Previous slide",
|
||||||
|
"next_slide": "Next slide",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"submit": "Submit",
|
||||||
|
"download": "Download",
|
||||||
|
"help": "Help",
|
||||||
|
"select_all": "Select all",
|
||||||
|
"select_row": "Select row",
|
||||||
"sort_asc": "Asc",
|
"sort_asc": "Asc",
|
||||||
"sort_asc_description": "In ascending order. Click to sort descending order.",
|
"sort_asc_description": "In ascending order. Click to sort descending order.",
|
||||||
"sort_desc": "Desc",
|
"sort_desc": "Desc",
|
||||||
@ -52,21 +62,18 @@
|
|||||||
"insert_row_below": "Insert row below",
|
"insert_row_below": "Insert row below",
|
||||||
"pick_date": "Select a date",
|
"pick_date": "Select a date",
|
||||||
"required_field": "This field is required",
|
"required_field": "This field is required",
|
||||||
"unsaved_changes_prompt": "There are unsaved changes. If you leave, you'll lose your changes.",
|
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"archive": "Archive",
|
"archive": "Archive",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"print": "Print",
|
"disable_preview": "Hide preview",
|
||||||
"disable_preview": "Disable preview",
|
"enable_preview": "Show preview",
|
||||||
"enable_preview": "Enable preview",
|
|
||||||
"filter": {
|
"filter": {
|
||||||
"reset_filter": "Reset filter",
|
"reset_filter": "Reset filter",
|
||||||
"badget_remove_tooltip": "Remove this filter term",
|
"badget_remove_tooltip": "Remove this filter term",
|
||||||
"input_placeholder": "Type here to filter...",
|
"input_placeholder": "Type here to filter...",
|
||||||
"button_add_term": "Add filter term",
|
"button_add_term": "Add filter term",
|
||||||
"button_add_term_tooltip": "Add filter term (or press Enter)",
|
"button_add_term_tooltip": "Add filter term (or press Enter)",
|
||||||
"button_remove_all": "Remove all terms from filter",
|
|
||||||
"help_text": "Press Enter or click the + button to add a term to the filter. Use multiple terms for a broader search."
|
"help_text": "Press Enter or click the + button to add a term to the filter. Use multiple terms for a broader search."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -77,6 +84,21 @@
|
|||||||
"loading_overlay": {
|
"loading_overlay": {
|
||||||
"title": "Loading...",
|
"title": "Loading...",
|
||||||
"subtitle": "This may take a few seconds. Please do not close this page."
|
"subtitle": "This may take a few seconds. Please do not close this page."
|
||||||
|
},
|
||||||
|
"error_overlay": {
|
||||||
|
"title": "An error occurred",
|
||||||
|
"go_home": "Go home"
|
||||||
|
},
|
||||||
|
"empty_state": {
|
||||||
|
"title": "There is nothing here yet",
|
||||||
|
"subtitle": "Start by requesting the catalog upload",
|
||||||
|
"button": "Request catalog upload"
|
||||||
|
},
|
||||||
|
"data_table": {
|
||||||
|
"columns": "Columns",
|
||||||
|
"no_results": "No results found.",
|
||||||
|
"clear_filters": "Clear filters",
|
||||||
|
"selected_count": "{{count}} selected"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@ -88,13 +110,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"main_menu": {
|
"main_menu": {
|
||||||
"home": "Home",
|
"toggle_navigation": "Toggle navigation menu",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"dealers": "Dealers",
|
|
||||||
"catalog": "Catalog",
|
"catalog": "Catalog",
|
||||||
"quotes": "Quotes",
|
"quotes": "Quotes",
|
||||||
"orders": "Orders",
|
"search_placeholder": "Search products, quotes, etc.",
|
||||||
"search_placeholder": "Type here for search quotes and articles",
|
|
||||||
"user": {
|
"user": {
|
||||||
"user_menu": "User menu",
|
"user_menu": "User menu",
|
||||||
"my_account": "My account",
|
"my_account": "My account",
|
||||||
@ -104,14 +124,14 @@
|
|||||||
"logout": "Logout"
|
"logout": "Logout"
|
||||||
},
|
},
|
||||||
"logout_dialog": {
|
"logout_dialog": {
|
||||||
"title": "Confirm",
|
"title": "Log out",
|
||||||
"description": "Are you sure you want to log out?",
|
"description": "Are you sure you want to log out?",
|
||||||
"confirm_label": "Log out",
|
"confirm_label": "Log out",
|
||||||
"cancel_label": "Cancel"
|
"cancel_label": "Cancel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"login_page": {
|
"login_page": {
|
||||||
"title": "Partner intranet",
|
"title": "Quoting tool for distributors",
|
||||||
"description": "Enter your email address and password to login",
|
"description": "Enter your email address and password to login",
|
||||||
"email_label": "Email",
|
"email_label": "Email",
|
||||||
"email_placeholder": "myemail@sample.com",
|
"email_placeholder": "myemail@sample.com",
|
||||||
@ -127,11 +147,14 @@
|
|||||||
"catalog": {
|
"catalog": {
|
||||||
"list": {
|
"list": {
|
||||||
"title": "Catalog of articles",
|
"title": "Catalog of articles",
|
||||||
|
"empty_state": {
|
||||||
|
"subtitle": "Start by loading catalog items"
|
||||||
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"points": "Points",
|
"points": "Points",
|
||||||
"retail_price": "Retail price",
|
"retail_price": "Retail price",
|
||||||
"quantity": "quantity"
|
"quantity": "Quantity"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -139,7 +162,18 @@
|
|||||||
"list": {
|
"list": {
|
||||||
"title": "Quotes",
|
"title": "Quotes",
|
||||||
"subtitle": "",
|
"subtitle": "",
|
||||||
"tabs_title": "Status",
|
"tabs_title": "Statuses",
|
||||||
|
"select_status": "Select status",
|
||||||
|
"preview_aria": "Show quote preview",
|
||||||
|
"empty_state": {
|
||||||
|
"subtitle": "Start by creating a quote"
|
||||||
|
},
|
||||||
|
"status_help": {
|
||||||
|
"title": "Quote status help",
|
||||||
|
"intro": "A quote status indicates its current position in the business process.",
|
||||||
|
"possible": "The possible statuses are:",
|
||||||
|
"outro": "Use these statuses to track quotes efficiently and improve your sales process. The Archived status helps keep history without interfering with active quotes."
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"draft": "Draft",
|
"draft": "Draft",
|
||||||
@ -156,58 +190,49 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"customer_reference": "Customer Ref.",
|
"customer_reference": "Customer Ref.",
|
||||||
"customer_information": "Customer",
|
"customer_information": "Customer",
|
||||||
"total_price": "Imp. total",
|
"total_price": "Total amount"
|
||||||
"actions": {
|
|
||||||
"edit": "Edit quote",
|
|
||||||
"sent_to": "Send to Uecko"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"resume": {
|
"resume": {
|
||||||
"title": "Quote",
|
"title": "Quote",
|
||||||
"download_quote": "Download quote",
|
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"resume": "Resume",
|
"resume": "Summary",
|
||||||
"preview": "Preview"
|
"preview": "Preview"
|
||||||
},
|
},
|
||||||
"quote_information": "Quote Information",
|
"quote_information": "Quote Information",
|
||||||
"customer_information": "Customer Information",
|
"customer_information": "Customer Information",
|
||||||
"payment_information": "Payment Information",
|
"price_information": "Quote totals",
|
||||||
"price_information": "Quote totals"
|
"select_quote": "Select a quote"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"created": "Quote created",
|
||||||
|
"saved": "Quote saved",
|
||||||
|
"saving": "Saving quote"
|
||||||
|
},
|
||||||
|
"pdf_generation": {
|
||||||
|
"title": "Generating PDF",
|
||||||
|
"error": "Error generating PDF",
|
||||||
|
"progress": "Progress: {{percentage}}%",
|
||||||
|
"elapsed": "Elapsed time: {{elapsed}} seconds",
|
||||||
|
"remaining": "Estimated time remaining: {{estimatedTime}} seconds",
|
||||||
|
"cancel": "Cancel generation"
|
||||||
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "New quote",
|
"title": "New quote",
|
||||||
"tabs": {
|
|
||||||
"general": "General data",
|
|
||||||
"items": "Quote items",
|
|
||||||
"preview": "Quote preview",
|
|
||||||
"documents": "Documents",
|
|
||||||
"history": "History"
|
|
||||||
},
|
|
||||||
"form_groups": {
|
"form_groups": {
|
||||||
"general": {
|
"general": {
|
||||||
"title": "General Data",
|
"title": "General Data",
|
||||||
"desc": "General data and quote customer"
|
"desc": "General information and the quote's customer"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"title": "Status",
|
"title": "Status",
|
||||||
"desc": "Quote status"
|
"desc": "Quote status"
|
||||||
},
|
},
|
||||||
"items": {
|
|
||||||
"title": "Quote Items",
|
|
||||||
"desc": "Quote detail lines. Use the catalog to make it easier to fill in the content."
|
|
||||||
},
|
|
||||||
"documents": {
|
"documents": {
|
||||||
"title": "Attached Documents",
|
"title": "Attached Documents",
|
||||||
"desc": "Attach documents such as photos, drawings, sketches, etc. to your quotation."
|
"desc": "Attach documents such as photos, drawings, sketches, etc. to your quotation.",
|
||||||
},
|
"images": "Images"
|
||||||
"history": {
|
|
||||||
"title": "History",
|
|
||||||
"desc": "Quote history"
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"edit": {
|
|
||||||
"title": "Quote"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
@ -225,8 +250,7 @@
|
|||||||
"progress": {
|
"progress": {
|
||||||
"label": "Download is in {{status}}",
|
"label": "Download is in {{status}}",
|
||||||
"status_in_progress": "in progress",
|
"status_in_progress": "in progress",
|
||||||
"status_stopped": "stopped",
|
"status_stopped": "stopped"
|
||||||
"value_label": "Elapsed time in seconds {{elapsed}}"
|
|
||||||
},
|
},
|
||||||
"cancel_button": "Cancel the download",
|
"cancel_button": "Cancel the download",
|
||||||
"toast_success": "Quote downloaded"
|
"toast_success": "Quote downloaded"
|
||||||
@ -238,9 +262,15 @@
|
|||||||
},
|
},
|
||||||
"blocks_picker_dialog": {
|
"blocks_picker_dialog": {
|
||||||
"title": "Select a text block",
|
"title": "Select a text block",
|
||||||
"description": "To complete your quote, you can default text blocks.",
|
"description": "To help complete your quote, you can add predefined text blocks from this list.",
|
||||||
"toast_article_added": "Text block added:"
|
"toast_article_added": "Text block added:"
|
||||||
},
|
},
|
||||||
|
"quote_sent_to_editor": {
|
||||||
|
"trigger_button": "Send to Uecko",
|
||||||
|
"title": "Send quote to Uecko",
|
||||||
|
"description": "Do you want to send this quote to Uecko? This action cannot be undone.",
|
||||||
|
"toast_status_changed": "Quote sent to Uecko"
|
||||||
|
},
|
||||||
"quote_status_editor": {
|
"quote_status_editor": {
|
||||||
"trigger_button": "Change quote status",
|
"trigger_button": "Change quote status",
|
||||||
"title": "Change quote status",
|
"title": "Change quote status",
|
||||||
@ -267,7 +297,7 @@
|
|||||||
},
|
},
|
||||||
"archived": {
|
"archived": {
|
||||||
"title": "Archived",
|
"title": "Archived",
|
||||||
"description": "The quote is archived."
|
"description": "The quote has been saved for future reference."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"submit_button": "Change status",
|
"submit_button": "Change status",
|
||||||
@ -308,11 +338,6 @@
|
|||||||
"desc": "Quote reference",
|
"desc": "Quote reference",
|
||||||
"placeholder": ""
|
"placeholder": ""
|
||||||
},
|
},
|
||||||
"status": {
|
|
||||||
"label": "Status",
|
|
||||||
"desc": "Quote status",
|
|
||||||
"placeholder": ""
|
|
||||||
},
|
|
||||||
"lang_code": {
|
"lang_code": {
|
||||||
"label": "Language",
|
"label": "Language",
|
||||||
"desc": "Quote language",
|
"desc": "Quote language",
|
||||||
@ -329,13 +354,13 @@
|
|||||||
"placeholder": ""
|
"placeholder": ""
|
||||||
},
|
},
|
||||||
"customer_information": {
|
"customer_information": {
|
||||||
"label": "Customer's contact data",
|
"label": "Customer details",
|
||||||
"desc": "Recommendation: enter the customer's name on the first line, the address on the second line, and the zip code and city/state on the third line.",
|
"desc": "Recommendation: enter the customer's name on the first line, the address on the second line, and the zip code and city/state on the third line.",
|
||||||
"placeholder": "Name and surname\nStreet and number\nzip code and city or state..."
|
"placeholder": "Name and surname\nStreet and number\nzip code and city or state..."
|
||||||
},
|
},
|
||||||
"payment_method": {
|
"payment_method": {
|
||||||
"label": "Payment method",
|
"label": "Payment method",
|
||||||
"placeholder": "",
|
"placeholder": "placeholder",
|
||||||
"desc": "Method of payment for this quote"
|
"desc": "Method of payment for this quote"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
@ -364,14 +389,14 @@
|
|||||||
"desc": "Percentage discount"
|
"desc": "Percentage discount"
|
||||||
},
|
},
|
||||||
"discount_price": {
|
"discount_price": {
|
||||||
"label": "Discount price",
|
"label": "Discount amount",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Percentage discount price"
|
"desc": "Discount amount"
|
||||||
},
|
},
|
||||||
"before_tax_price": {
|
"before_tax_price": {
|
||||||
"label": "Before tax price",
|
"label": "Taxable amount",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Before tax price"
|
"desc": "Taxable amount"
|
||||||
},
|
},
|
||||||
"tax": {
|
"tax": {
|
||||||
"label": "Tax (%)",
|
"label": "Tax (%)",
|
||||||
@ -386,7 +411,7 @@
|
|||||||
"tax_price": {
|
"tax_price": {
|
||||||
"label": "Tax amount",
|
"label": "Tax amount",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Percentage tax price"
|
"desc": "Tax amount"
|
||||||
},
|
},
|
||||||
"total_price": {
|
"total_price": {
|
||||||
"label": "Total price",
|
"label": "Total price",
|
||||||
@ -415,7 +440,7 @@
|
|||||||
"desc": ""
|
"desc": ""
|
||||||
},
|
},
|
||||||
"discount": {
|
"discount": {
|
||||||
"label": "Dto (%)",
|
"label": "Discount (%)",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Percentage discount"
|
"desc": "Percentage discount"
|
||||||
},
|
},
|
||||||
@ -430,7 +455,6 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"edit": {
|
"edit": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"subtitle": "",
|
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"profile": "Profile settings",
|
"profile": "Profile settings",
|
||||||
"quotes": "Quote settings",
|
"quotes": "Quote settings",
|
||||||
@ -441,12 +465,19 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"label": "Language",
|
"label": "Language",
|
||||||
"placeholder": "Select a language",
|
"placeholder": "Select a language",
|
||||||
"desc": "Preferred language for the interface"
|
"desc": "Preferred language for the interface",
|
||||||
|
"options": {
|
||||||
|
"es": "Spanish",
|
||||||
|
"en": "English"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"image": {
|
"logo": {
|
||||||
"label": "Logotype",
|
"label": "Logo",
|
||||||
"placeholder": "",
|
"desc": "This logo will appear in PDF-exported proposals",
|
||||||
"desc": ""
|
"requirements": "JPG or PNG files are allowed. The logo dimensions must be 200x100 pixels or proportional.",
|
||||||
|
"upload": "Upload logo",
|
||||||
|
"invalid_format": "Only JPG or PNG files are allowed",
|
||||||
|
"invalid_dimensions": "The logo dimensions must be 200x100 pixels or proportional."
|
||||||
},
|
},
|
||||||
"contact_information": {
|
"contact_information": {
|
||||||
"label": "Your contact information",
|
"label": "Your contact information",
|
||||||
@ -459,7 +490,6 @@
|
|||||||
"desc": "Default tax rate for your quotes"
|
"desc": "Default tax rate for your quotes"
|
||||||
},
|
},
|
||||||
"default_tax_label": {
|
"default_tax_label": {
|
||||||
"label": "Tax label",
|
|
||||||
"placeholder": "% VAT",
|
"placeholder": "% VAT",
|
||||||
"desc": "Name or label shown alongside the tax"
|
"desc": "Name or label shown alongside the tax"
|
||||||
},
|
},
|
||||||
@ -470,7 +500,7 @@
|
|||||||
"default_legal_terms": {
|
"default_legal_terms": {
|
||||||
"label": "Legal terms",
|
"label": "Legal terms",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Legal information to be included at the end of your quotes"
|
"desc": "Legal text to include in all new quotes"
|
||||||
},
|
},
|
||||||
"default_payment_method": {
|
"default_payment_method": {
|
||||||
"label": "Payment method",
|
"label": "Payment method",
|
||||||
@ -487,7 +517,39 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Default validity time to be used for new quotes"
|
"desc": "Default validity time to be used for new quotes"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"saved": "Settings saved",
|
||||||
|
"saving": "Saving settings"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"modal": {
|
||||||
|
"title": "Send an issue",
|
||||||
|
"subtitle": "Use this form to report any problem you encountered while using the application. Our development team will review your issue and try to resolve it.",
|
||||||
|
"open": "Open support window",
|
||||||
|
"placeholder": "Describe the issue here...",
|
||||||
|
"submit": "Send issue",
|
||||||
|
"validation": {
|
||||||
|
"empty": "Write something before sending",
|
||||||
|
"min": "The text is too short. It must contain at least 10 characters",
|
||||||
|
"max": "The text is too long.",
|
||||||
|
"required": "A description is required"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sent_title": "Issue sent",
|
||||||
|
"sent_description": "The issue was sent successfully",
|
||||||
|
"error_title": "Sending error",
|
||||||
|
"error_description": "The issue could not be sent. Please try again."
|
||||||
|
},
|
||||||
|
"cancel_confirmation": {
|
||||||
|
"title": "Are you sure you want to cancel?",
|
||||||
|
"description": "You have entered text in the description field. Closing the window will discard your unsaved changes.",
|
||||||
|
"back": "Return to form",
|
||||||
|
"confirm": "Yes, close"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form_fields": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
{
|
{
|
||||||
"translation": {
|
"translation": {
|
||||||
"common": {
|
"common": {
|
||||||
"loading": "Cargando...",
|
|
||||||
"required": "obligatorio",
|
"required": "obligatorio",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"yes": "Sí",
|
"yes": "Sí",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"save_close": "Guardar y Cerrar",
|
"save_close": "Guardar y Cerrar",
|
||||||
"accept": "Aceptar",
|
|
||||||
"discard": "Descartar",
|
"discard": "Descartar",
|
||||||
"hide": "Ocultar",
|
"hide": "Ocultar",
|
||||||
"back": "Volver",
|
"back": "Volver",
|
||||||
@ -16,7 +14,19 @@
|
|||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
|
"pagination": "Paginación",
|
||||||
|
"breadcrumb": "Migas de pan",
|
||||||
|
"previous": "Anterior",
|
||||||
|
"next": "Siguiente",
|
||||||
|
"more_pages": "Más páginas",
|
||||||
|
"previous_slide": "Diapositiva anterior",
|
||||||
|
"next_slide": "Diapositiva siguiente",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
|
"submit": "Enviar",
|
||||||
|
"download": "Descargar",
|
||||||
|
"help": "Ayuda",
|
||||||
|
"select_all": "Seleccionar todo",
|
||||||
|
"select_row": "Seleccionar fila",
|
||||||
"sort_asc": "Asc",
|
"sort_asc": "Asc",
|
||||||
"sort_asc_description": "En order ascendente. Click para ordenar descendentemente.",
|
"sort_asc_description": "En order ascendente. Click para ordenar descendentemente.",
|
||||||
"sort_desc": "Desc",
|
"sort_desc": "Desc",
|
||||||
@ -52,12 +62,10 @@
|
|||||||
"insert_row_below": "Insertar fila debajo",
|
"insert_row_below": "Insertar fila debajo",
|
||||||
"pick_date": "Elige una fecha",
|
"pick_date": "Elige una fecha",
|
||||||
"required_field": "Este campo es obligatorio",
|
"required_field": "Este campo es obligatorio",
|
||||||
"unsaved_changes_prompt": "Los últimos cambios no se han guardado. Si continúas, se perderán.",
|
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"archive": "Archivar",
|
"archive": "Archivar",
|
||||||
"duplicate": "Duplicar",
|
"duplicate": "Duplicar",
|
||||||
"print": "Imprimir",
|
|
||||||
"disable_preview": "Ocultar vista previa",
|
"disable_preview": "Ocultar vista previa",
|
||||||
"enable_preview": "Mostrar vista previa",
|
"enable_preview": "Mostrar vista previa",
|
||||||
"filter": {
|
"filter": {
|
||||||
@ -66,17 +74,31 @@
|
|||||||
"input_placeholder": "Escribe aquí para filtrar...",
|
"input_placeholder": "Escribe aquí para filtrar...",
|
||||||
"button_add_term": "Añadir término al filtro",
|
"button_add_term": "Añadir término al filtro",
|
||||||
"button_add_term_tooltip": "Añadir término al filtro (o pulsa Enter)",
|
"button_add_term_tooltip": "Añadir término al filtro (o pulsa Enter)",
|
||||||
"button_remove_all": "Quitar todos los términos del filtro",
|
|
||||||
"help_text": "Presiona Enter o haz clic en el botón + para añadir un término al filtro. Usa múltiples términos para una búsqueda más amplia."
|
"help_text": "Presiona Enter o haz clic en el botón + para añadir un término al filtro. Usa múltiples términos para una búsqueda más amplia."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"LoadingIndicator": {
|
"loading_indicator": {
|
||||||
"title": "Cargando..."
|
"title": "Cargando..."
|
||||||
},
|
},
|
||||||
"loading_overlay": {
|
"loading_overlay": {
|
||||||
"title": "Cargando...",
|
"title": "Cargando...",
|
||||||
"subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página."
|
"subtitle": "Esto puede tardar unos segundos. Por favor, no cierre esta página."
|
||||||
|
},
|
||||||
|
"error_overlay": {
|
||||||
|
"title": "Se ha producido un error",
|
||||||
|
"go_home": "Ir al inicio"
|
||||||
|
},
|
||||||
|
"empty_state": {
|
||||||
|
"title": "Esto está muy vacío",
|
||||||
|
"subtitle": "Empieza solicitando la carga del catálogo",
|
||||||
|
"button": "Solicitar carga del catálogo"
|
||||||
|
},
|
||||||
|
"data_table": {
|
||||||
|
"columns": "Columnas",
|
||||||
|
"no_results": "No se han encontrado resultados.",
|
||||||
|
"clear_filters": "Limpiar filtros",
|
||||||
|
"selected_count": "{{count}} seleccionados"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@ -88,12 +110,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"main_menu": {
|
"main_menu": {
|
||||||
"home": "Inicio",
|
"toggle_navigation": "Alternar el menú de navegación",
|
||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"dealers": "Distribuidores",
|
|
||||||
"catalog": "Catálogo",
|
"catalog": "Catálogo",
|
||||||
"quotes": "Cotizaciones",
|
"quotes": "Cotizaciones",
|
||||||
"orders": "Pedidos",
|
|
||||||
"search_placeholder": "Buscar productos, cotizaciones, etc...",
|
"search_placeholder": "Buscar productos, cotizaciones, etc...",
|
||||||
"user": {
|
"user": {
|
||||||
"user_menu": "Menú del usuario",
|
"user_menu": "Menú del usuario",
|
||||||
@ -127,6 +147,9 @@
|
|||||||
"catalog": {
|
"catalog": {
|
||||||
"list": {
|
"list": {
|
||||||
"title": "Catálogo de artículos",
|
"title": "Catálogo de artículos",
|
||||||
|
"empty_state": {
|
||||||
|
"subtitle": "Empieza cargando los artículos del catálogo"
|
||||||
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"points": "Puntos",
|
"points": "Puntos",
|
||||||
@ -140,6 +163,17 @@
|
|||||||
"title": "Cotizaciones",
|
"title": "Cotizaciones",
|
||||||
"subtitle": "",
|
"subtitle": "",
|
||||||
"tabs_title": "Estados",
|
"tabs_title": "Estados",
|
||||||
|
"select_status": "Seleccionar estado",
|
||||||
|
"preview_aria": "Mostrar vista previa de la cotización",
|
||||||
|
"empty_state": {
|
||||||
|
"subtitle": "Empieza creando alguna cotización"
|
||||||
|
},
|
||||||
|
"status_help": {
|
||||||
|
"title": "Ayuda sobre el estado de las cotizaciones",
|
||||||
|
"intro": "El estado de una cotización indica su posición actual en el proceso de negocio.",
|
||||||
|
"possible": "Los estados posibles son:",
|
||||||
|
"outro": "Utiliza estos estados para hacer un seguimiento eficiente de tus cotizaciones y optimizar tu proceso de ventas. El estado Archivado ayuda a conservar el historial sin interferir con las cotizaciones activas."
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"all": "Todas",
|
"all": "Todas",
|
||||||
"draft": "Borradores",
|
"draft": "Borradores",
|
||||||
@ -156,34 +190,35 @@
|
|||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"customer_reference": "Ref. cliente",
|
"customer_reference": "Ref. cliente",
|
||||||
"customer_information": "Cliente",
|
"customer_information": "Cliente",
|
||||||
"total_price": "Imp. total",
|
"total_price": "Imp. total"
|
||||||
"actions": {
|
|
||||||
"edit": "Editar",
|
|
||||||
"sent_to": "Enviar a Uecko"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"resume": {
|
"resume": {
|
||||||
"title": "Cotización",
|
"title": "Cotización",
|
||||||
"download_quote": "Descargar",
|
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"resume": "Resumen",
|
"resume": "Resumen",
|
||||||
"preview": "Previsualización"
|
"preview": "Previsualización"
|
||||||
},
|
},
|
||||||
"quote_information": "Información",
|
"quote_information": "Información",
|
||||||
"customer_information": "Cliente",
|
"customer_information": "Cliente",
|
||||||
"payment_information": "Pago",
|
"price_information": "Importes",
|
||||||
"price_information": "Importes"
|
"select_quote": "Selecciona una cotización"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"created": "Cotización creada",
|
||||||
|
"saved": "Cotización guardada",
|
||||||
|
"saving": "Guardando cotización"
|
||||||
|
},
|
||||||
|
"pdf_generation": {
|
||||||
|
"title": "Generando PDF",
|
||||||
|
"error": "Error al generar el PDF",
|
||||||
|
"progress": "Progreso: {{percentage}}%",
|
||||||
|
"elapsed": "Tiempo transcurrido: {{elapsed}} segundos",
|
||||||
|
"remaining": "Tiempo estimado restante: {{estimatedTime}} segundos",
|
||||||
|
"cancel": "Cancelar generación"
|
||||||
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Nueva cotización",
|
"title": "Nueva cotización",
|
||||||
"tabs": {
|
|
||||||
"general": "Datos generales",
|
|
||||||
"items": "Contenido",
|
|
||||||
"preview": "Vista previa",
|
|
||||||
"documents": "Documentos",
|
|
||||||
"history": "Historial"
|
|
||||||
},
|
|
||||||
"form_groups": {
|
"form_groups": {
|
||||||
"general": {
|
"general": {
|
||||||
"title": "Datos generales",
|
"title": "Datos generales",
|
||||||
@ -193,21 +228,11 @@
|
|||||||
"title": "Estado",
|
"title": "Estado",
|
||||||
"desc": "Estado de la cotización"
|
"desc": "Estado de la cotización"
|
||||||
},
|
},
|
||||||
"items": {
|
|
||||||
"title": "Contenido de la cotización",
|
|
||||||
"desc": "Líneas de detalle de la cotización. Ayúdese del catálogo para rellenar más fácilmente el contenido."
|
|
||||||
},
|
|
||||||
"documents": {
|
"documents": {
|
||||||
"title": "Documentos",
|
"title": "Documentos",
|
||||||
"desc": "Añada adjuntar con su cotización documentos como fotos, planos, croquis, etc."
|
"desc": "Añada adjuntar con su cotización documentos como fotos, planos, croquis, etc.",
|
||||||
},
|
"images": "Imágenes"
|
||||||
"history": {
|
|
||||||
"title": "Historial",
|
|
||||||
"desc": "Historial de la cotización"
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"edit": {
|
|
||||||
"title": "Cotización"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
@ -225,8 +250,7 @@
|
|||||||
"progress": {
|
"progress": {
|
||||||
"label": "La descarga está {{status}}",
|
"label": "La descarga está {{status}}",
|
||||||
"status_in_progress": "en marcha",
|
"status_in_progress": "en marcha",
|
||||||
"status_stopped": "parada",
|
"status_stopped": "parada"
|
||||||
"value_label": "Elapsed time in seconds {{elapsed}}"
|
|
||||||
},
|
},
|
||||||
"cancel_button": "Cancelar la descarga",
|
"cancel_button": "Cancelar la descarga",
|
||||||
"toast_success": "Cotización descargada"
|
"toast_success": "Cotización descargada"
|
||||||
@ -245,7 +269,6 @@
|
|||||||
"trigger_button": "Enviar a Uecko",
|
"trigger_button": "Enviar a Uecko",
|
||||||
"title": "Enviar la cotización a Uecko",
|
"title": "Enviar la cotización a Uecko",
|
||||||
"description": "¿Desea enviar esta cotización a Uecko? Esta acción no se puede deshacer.",
|
"description": "¿Desea enviar esta cotización a Uecko? Esta acción no se puede deshacer.",
|
||||||
"submit_button": "Enviar",
|
|
||||||
"toast_status_changed": "Cotización enviada a Uecko"
|
"toast_status_changed": "Cotización enviada a Uecko"
|
||||||
},
|
},
|
||||||
"quote_status_editor": {
|
"quote_status_editor": {
|
||||||
@ -432,7 +455,6 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"edit": {
|
"edit": {
|
||||||
"title": "Ajustes",
|
"title": "Ajustes",
|
||||||
"subtitle": "",
|
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"profile": "Ajustes de perfil",
|
"profile": "Ajustes de perfil",
|
||||||
"quotes": "Ajustes para cotizaciones",
|
"quotes": "Ajustes para cotizaciones",
|
||||||
@ -443,11 +465,14 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"label": "Idioma",
|
"label": "Idioma",
|
||||||
"placeholder": "Selecciona un idioma",
|
"placeholder": "Selecciona un idioma",
|
||||||
"desc": "Idioma preferido para la interfaz"
|
"desc": "Idioma preferido para la interfaz",
|
||||||
|
"options": {
|
||||||
|
"es": "Español",
|
||||||
|
"en": "Inglés"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"logo": {
|
"logo": {
|
||||||
"label": "Logotipo",
|
"label": "Logotipo",
|
||||||
"placeholder": "",
|
|
||||||
"desc": "Este logotipo aparecerá en las propuestas exportadas en PDF",
|
"desc": "Este logotipo aparecerá en las propuestas exportadas en PDF",
|
||||||
"requirements": "Se permiten ficheros JPG o PNG. Las dimensiones del logotipo deben ser 200x100 píxeles o proporcionales.",
|
"requirements": "Se permiten ficheros JPG o PNG. Las dimensiones del logotipo deben ser 200x100 píxeles o proporcionales.",
|
||||||
"upload": "Subir logotipo",
|
"upload": "Subir logotipo",
|
||||||
@ -465,7 +490,6 @@
|
|||||||
"desc": "Porcentaje de impuesto por defecto en las cotizaciones"
|
"desc": "Porcentaje de impuesto por defecto en las cotizaciones"
|
||||||
},
|
},
|
||||||
"default_tax_label": {
|
"default_tax_label": {
|
||||||
"label": "Etiqueta del impuesto",
|
|
||||||
"placeholder": "% IVA",
|
"placeholder": "% IVA",
|
||||||
"desc": "Nombre o etiqueta que se mostrará junto al impuesto"
|
"desc": "Nombre o etiqueta que se mostrará junto al impuesto"
|
||||||
},
|
},
|
||||||
@ -493,12 +517,37 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Texto para indicar el tiempo de validez de la cotización"
|
"desc": "Texto para indicar el tiempo de validez de la cotización"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"saved": "Ajustes guardados",
|
||||||
|
"saving": "Guardando ajustes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"support": {
|
"support": {
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Enviar una incidencia",
|
"title": "Enviar una incidencia",
|
||||||
"subtitle": "Utiliza este formulario para informar sobre cualquier problema que hayas encontrado mientras usabas la aplicación. Nuestro equipo de desarrollo revisará tu incidencia y tratará de resolverla."
|
"subtitle": "Utiliza este formulario para informar sobre cualquier problema que hayas encontrado mientras usabas la aplicación. Nuestro equipo de desarrollo revisará tu incidencia y tratará de resolverla.",
|
||||||
|
"open": "Abrir ventana de soporte",
|
||||||
|
"placeholder": "Describe la incidencia aquí...",
|
||||||
|
"submit": "Enviar incidencia",
|
||||||
|
"validation": {
|
||||||
|
"empty": "Debe escribir algo antes de enviar",
|
||||||
|
"min": "El texto es demasiado corto. Debe tener al menos 10 caracteres",
|
||||||
|
"max": "El texto es demasiado largo.",
|
||||||
|
"required": "La descripción es requerida"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sent_title": "Incidencia enviada",
|
||||||
|
"sent_description": "La incidencia se ha enviado correctamente",
|
||||||
|
"error_title": "Error en el envío",
|
||||||
|
"error_description": "No se ha podido enviar la incidencia correctamente. Por favor, inténtalo de nuevo."
|
||||||
|
},
|
||||||
|
"cancel_confirmation": {
|
||||||
|
"title": "¿Estás seguro de que quieres cancelar?",
|
||||||
|
"description": "Has escrito texto en el campo de descripción. Si cierras la ventana, perderás los cambios no guardados.",
|
||||||
|
"back": "Volver al formulario",
|
||||||
|
"confirm": "Sí, cerrar"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"form_fields": {}
|
"form_fields": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
import { t } from "i18next"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ const Breadcrumb = React.forwardRef<
|
|||||||
React.ComponentPropsWithoutRef<"nav"> & {
|
React.ComponentPropsWithoutRef<"nav"> & {
|
||||||
separator?: React.ReactNode
|
separator?: React.ReactNode
|
||||||
}
|
}
|
||||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
>(({ ...props }, ref) => <nav ref={ref} aria-label={t("common.breadcrumb")} {...props} />)
|
||||||
Breadcrumb.displayName = "Breadcrumb"
|
Breadcrumb.displayName = "Breadcrumb"
|
||||||
|
|
||||||
const BreadcrumbList = React.forwardRef<
|
const BreadcrumbList = React.forwardRef<
|
||||||
@ -99,7 +100,7 @@ const BreadcrumbEllipsis = ({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">{t("common.more")}</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import useEmblaCarousel, {
|
|||||||
} from "embla-carousel-react";
|
} from "embla-carousel-react";
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
@ -215,7 +216,7 @@ const CarouselPrevious = React.forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
<span className="sr-only">Previous slide</span>
|
<span className="sr-only">{t("common.previous_slide")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -244,7 +245,7 @@ const CarouselNext = React.forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowRight className="w-4 h-4" />
|
<ArrowRight className="w-4 h-4" />
|
||||||
<span className="sr-only">Next slide</span>
|
<span className="sr-only">{t("common.next_slide")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { t } from "i18next"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@ -44,7 +45,7 @@ const DialogContent = React.forwardRef<
|
|||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">{t("common.close")}</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
import { t } from "i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@ -7,7 +8,7 @@ import { ButtonProps, buttonVariants } from "./button";
|
|||||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
<nav
|
<nav
|
||||||
role="navigation"
|
role="navigation"
|
||||||
aria-label="pagination"
|
aria-label={t("common.pagination")}
|
||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@ -64,13 +65,13 @@ const PaginationPrevious = ({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to previous page"
|
aria-label={t("common.go_to_prev_page")}
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 pl-2.5", className)}
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-4 h-4" />
|
<ChevronLeft className="w-4 h-4" />
|
||||||
<span>Previous</span>
|
<span>{t("common.previous")}</span>
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
);
|
);
|
||||||
PaginationPrevious.displayName = "PaginationPrevious";
|
PaginationPrevious.displayName = "PaginationPrevious";
|
||||||
@ -80,12 +81,12 @@ const PaginationNext = ({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to next page"
|
aria-label={t("common.go_to_next_page")}
|
||||||
size="default"
|
size="default"
|
||||||
className={cn("gap-1 pr-2.5", className)}
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span>Next</span>
|
<span>{t("common.next")}</span>
|
||||||
<ChevronRight className="w-4 h-4" />
|
<ChevronRight className="w-4 h-4" />
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
);
|
);
|
||||||
@ -101,7 +102,7 @@ const PaginationEllipsis = ({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="w-4 h-4" />
|
<MoreHorizontal className="w-4 h-4" />
|
||||||
<span className="sr-only">More pages</span>
|
<span className="sr-only">{t("common.more_pages")}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
PaginationEllipsis.displayName = "PaginationEllipsis";
|
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { t } from "i18next"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ const SheetContent = React.forwardRef<
|
|||||||
{children}
|
{children}
|
||||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">{t("common.close")}</span>
|
||||||
</SheetPrimitive.Close>
|
</SheetPrimitive.Close>
|
||||||
</SheetPrimitive.Content>
|
</SheetPrimitive.Content>
|
||||||
</SheetPortal>
|
</SheetPortal>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user