This commit is contained in:
David Arranz 2024-08-25 14:14:21 +02:00
parent e1ae090c9f
commit 6a3e42acbb
12 changed files with 119 additions and 100 deletions

View File

@ -9,6 +9,7 @@ import {
} from "@/ui"; } from "@/ui";
import { DataTableProvider } from "@/lib/hooks"; import { DataTableProvider } from "@/lib/hooks";
import { t } from "i18next";
import { CatalogPickerDataTable } from "../CatalogPickerDataTable"; import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
export const CatalogPickerDialog = ({ export const CatalogPickerDialog = ({
@ -24,18 +25,15 @@ export const CatalogPickerDialog = ({
<Dialog modal open={isOpen} onOpenChange={onOpenChange}> <Dialog modal open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className='w-11/12 max-w-full'> <DialogContent className='w-11/12 max-w-full'>
<DialogHeader> <DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle> <DialogTitle>{t("quotes.catalog_picker_dialog.title")}</DialogTitle>
<DialogDescription> <DialogDescription>{t("quotes.catalog_picker_dialog.subtitle")}</DialogDescription>
This action cannot be undone. This will permanently delete your account and remove your
data from our servers.
</DialogDescription>
</DialogHeader> </DialogHeader>
<DataTableProvider syncWithLocation={false} initialPageSize={5}> <DataTableProvider syncWithLocation={false} initialPageSize={5}>
<CatalogPickerDataTable onSelect={onSelect} /> <CatalogPickerDataTable onSelect={onSelect} />
</DataTableProvider> </DataTableProvider>
<DialogFooter> <DialogFooter>
<Button type='submit'>Choose</Button> <Button type='submit'>{t("common.close")}</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -11,6 +11,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/ui";
import { CurrencyData, Language, Quantity } from "@shared/contexts"; import { CurrencyData, Language, Quantity } from "@shared/contexts";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import { t } from "i18next"; import { t } from "i18next";
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useFieldArray, useFormContext } from "react-hook-form"; import { useFieldArray, useFormContext } from "react-hook-form";
import { useDetailColumns } from "../../hooks"; import { useDetailColumns } from "../../hooks";
@ -51,6 +52,16 @@ export const QuoteDetailsCardEditor = ({
enableSorting: false, enableSorting: false,
enableResizing: false, enableResizing: false,
},*/ },*/
{
id: "description" as const,
accessorKey: "description",
header: t("quotes.form_fields.items.description.label"),
size: 24,
cell: ({ row: { index } }) => {
return <FormTextAreaField autoSize {...register(`items.${index}.description`)} />;
},
},
{ {
id: "quantity" as const, id: "quantity" as const,
accessorKey: "quantity", accessorKey: "quantity",
@ -61,7 +72,6 @@ export const QuoteDetailsCardEditor = ({
cell: ({ row: { index } }) => { cell: ({ row: { index } }) => {
return ( return (
<FormQuantityField <FormQuantityField
variant='outline'
scale={0} scale={0}
className='text-right' className='text-right'
{...register(`items.${index}.quantity`)} {...register(`items.${index}.quantity`)}
@ -69,21 +79,6 @@ export const QuoteDetailsCardEditor = ({
); );
}, },
}, },
{
id: "description" as const,
accessorKey: "description",
header: t("quotes.form_fields.items.description.label"),
size: 24,
cell: ({ row: { index } }) => {
return (
<FormTextAreaField
variant='outline'
autoSize
{...register(`items.${index}.description`)}
/>
);
},
},
{ {
id: "unit_price" as const, id: "unit_price" as const,
accessorKey: "unit_price", accessorKey: "unit_price",
@ -93,7 +88,6 @@ export const QuoteDetailsCardEditor = ({
cell: ({ row: { index } }) => { cell: ({ row: { index } }) => {
return ( return (
<FormCurrencyField <FormCurrencyField
variant='outline'
currency={currency} currency={currency}
language={language} language={language}
scale={4} scale={4}
@ -113,7 +107,6 @@ export const QuoteDetailsCardEditor = ({
cell: ({ row: { index } }) => { cell: ({ row: { index } }) => {
return ( return (
<FormCurrencyField <FormCurrencyField
variant='outline'
currency={currency} currency={currency}
language={language} language={language}
scale={4} scale={4}
@ -134,7 +127,6 @@ export const QuoteDetailsCardEditor = ({
cell: ({ row: { index } }) => { cell: ({ row: { index } }) => {
return ( return (
<FormPercentageField <FormPercentageField
variant='outline'
scale={2} scale={2}
className='text-right' className='text-right'
{...register(`items.${index}.discount`)} {...register(`items.${index}.discount`)}
@ -152,7 +144,7 @@ export const QuoteDetailsCardEditor = ({
cell: ({ row: { index } }) => { cell: ({ row: { index } }) => {
return ( return (
<FormCurrencyField <FormCurrencyField
variant='outline' variant='ghost'
currency={currency} currency={currency}
language={language} language={language}
scale={4} scale={4}
@ -172,15 +164,18 @@ export const QuoteDetailsCardEditor = ({
const { table, row } = props; const { table, row } = props;
return [ return [
{ {
label: t("common.duplicate_rows"), label: t("common.duplicate_row"),
icon: <CopyIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.duplicateItems(row.index), onClick: () => table.options.meta?.duplicateItems(row.index),
}, },
{ {
label: t("common.insert_row_above"), label: t("common.insert_row_above"),
icon: <ChevronUpIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.insertItem(row.index), onClick: () => table.options.meta?.insertItem(row.index),
}, },
{ {
label: t("common.insert_row_below"), label: t("common.insert_row_below"),
icon: <ChevronDownIcon className='w-4 h-4 mr-2' />,
onClick: () => table.options.meta?.insertItem(row.index + 1), onClick: () => table.options.meta?.insertItem(row.index + 1),
}, },
@ -189,7 +184,8 @@ export const QuoteDetailsCardEditor = ({
}, },
{ {
label: t("common.remove_row"), label: t("common.remove_row"),
shortcut: "⌘⌫", //shortcut: "⌘⌫",
icon: <Trash2Icon className='w-4 h-4 mr-2' />,
onClick: () => { onClick: () => {
table.options.meta?.deleteItems(row.index); table.options.meta?.deleteItems(row.index);
}, },

View File

@ -1,6 +1,7 @@
import { import {
BackHistoryButton, BackHistoryButton,
FormDatePickerField, FormDatePickerField,
FormGroup,
FormTextAreaField, FormTextAreaField,
FormTextField, FormTextField,
LoadingOverlay, LoadingOverlay,
@ -9,7 +10,7 @@ import { t } from "i18next";
import { SubmitButton } from "@/components"; import { SubmitButton } from "@/components";
import { useUnsavedChangesNotifier } from "@/lib/hooks"; import { useUnsavedChangesNotifier } from "@/lib/hooks";
import { Button, Form } from "@/ui"; import { Button, Form, Separator } from "@/ui";
import { joiResolver } from "@hookform/resolvers/joi"; import { joiResolver } from "@hookform/resolvers/joi";
import { ICreateQuote_Request_DTO } from "@shared/contexts"; import { ICreateQuote_Request_DTO } from "@shared/contexts";
import Joi from "joi"; import Joi from "joi";
@ -17,6 +18,7 @@ import { useMemo } from "react";
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import SpanishJoiMessages from "../../spanish-joi-messages.json";
import { useQuotes } from "./hooks"; import { useQuotes } from "./hooks";
interface QuoteDataForm extends ICreateQuote_Request_DTO {} interface QuoteDataForm extends ICreateQuote_Request_DTO {}
@ -41,13 +43,14 @@ export const QuoteCreate = () => {
defaultValues, defaultValues,
resolver: joiResolver( resolver: joiResolver(
Joi.object({ Joi.object({
//reference: Joi.string().required(),
customer_information: Joi.string().required(),
date: Joi.date().required(),
customer_reference: Joi.string(), customer_reference: Joi.string(),
date: Joi.date().required(),
customer_information: Joi.string().required(),
}), }),
{ {
//messages: SpanishJoiMessages, messages: {
es: SpanishJoiMessages,
},
} }
), ),
}); });
@ -101,49 +104,47 @@ export const QuoteCreate = () => {
</div> </div>
<div className='grid w-6/12 gap-6 mx-auto'> <div className='grid w-6/12 gap-6 mx-auto'>
{/*<FormTextField <FormGroup
className='row-span-2' className='md:col-span-4'
name='reference' title={t("quotes.create.form_groups.general.title")}
required description={t("quotes.create.form_groups.general.desc")}
label={t("quotes.form_fields.reference.label")} footerActions={
description={t("quotes.form_fields.reference.desc")} <div className='flex items-stretch justify-between flex-1'>
placeholder={t("quotes.form_fields.reference.placeholder")} <Button size='sm' variant={"ghost"} onClick={() => navigate("/quotes")}>
/>*/} {t("common.discard")}
</Button>
<SubmitButton size='sm' label={t("common.continue")}></SubmitButton>
</div>
}
>
<FormTextField
required
name='customer_reference'
label={t("quotes.form_fields.customer_reference.label")}
description={t("quotes.form_fields.customer_reference.desc")}
placeholder={t("quotes.form_fields.customer_reference.placeholder")}
/>
<FormTextField <FormDatePickerField
className='row-span-2' required
name='customer_reference' label={t("quotes.form_fields.date.label")}
required description={t("quotes.form_fields.date.desc")}
label={t("quotes.form_fields.customer_reference.label")} placeholder={t("quotes.form_fields.date.placeholder")}
description={t("quotes.form_fields.customer_reference.desc")} name='date'
placeholder={t("quotes.form_fields.customer_reference.placeholder")} />
/>
<FormDatePickerField <Separator />
required
label={t("quotes.form_fields.date.label")}
description={t("quotes.form_fields.date.desc")}
placeholder={t("quotes.form_fields.date.placeholder")}
name='date'
/>
<FormTextAreaField <FormTextAreaField
rows={4} rows={4}
className='row-span-2' className='row-span-2'
name='customer_information' name='customer_information'
required required
label={t("quotes.form_fields.customer_information.label")} label={t("quotes.form_fields.customer_information.label")}
description={t("quotes.form_fields.customer_information.desc")} description={t("quotes.form_fields.customer_information.desc")}
placeholder={t("quotes.form_fields.customer_information.placeholder")} placeholder={t("quotes.form_fields.customer_information.placeholder")}
/> />
</FormGroup>
<div className='flex items-center justify-around gap-2'>
<Button size='sm' variant={"outline"} onClick={() => navigate("/quotes")}>
{t("common.discard")}
</Button>
<SubmitButton size='sm' label={t("common.continue")}></SubmitButton>
</div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -28,6 +28,23 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
// const lastSelectedId = ""; // const lastSelectedId = "";
return useMemo(() => { return useMemo(() => {
if (enableDragHandleColumn) {
columns.unshift({
id: "row_drag_handle",
/*header: () => (
<UnfoldVertical aria-label='Mover fila' className='items-center justify-center w-4 h-4' />
),*/
header: () => null,
cell: (info) => <DataTableRowDragHandleCell rowId={info.row.id} />,
size: 2,
minSize: 2,
maxSize: 2,
enableSorting: false,
enableHiding: false,
});
}
if (enableSelectionColumn) { if (enableSelectionColumn) {
columns.unshift({ columns.unshift({
id: "select", id: "select",
@ -63,23 +80,6 @@ export function useDetailColumns<TData = unknown, TValue = unknown>(
}); });
} }
if (enableDragHandleColumn) {
columns.unshift({
id: "row_drag_handle",
/*header: () => (
<UnfoldVertical aria-label='Mover fila' className='items-center justify-center w-4 h-4' />
),*/
header: () => null,
cell: (info) => <DataTableRowDragHandleCell rowId={info.row.id} />,
size: 2,
minSize: 2,
maxSize: 2,
enableSorting: false,
enableHiding: false,
});
}
if (enableActionsColumn) { if (enableActionsColumn) {
columns.push({ columns.push({
id: "row_actions", id: "row_actions",

View File

@ -13,7 +13,8 @@ import {
} from "@/ui"; } from "@/ui";
import { CellContext } from "@tanstack/react-table"; import { CellContext } from "@tanstack/react-table";
import { t } from "i18next"; import { t } from "i18next";
import { MoreHorizontalIcon } from "lucide-react"; import { MoreVerticalIcon } from "lucide-react";
import { ReactElement } from "react";
export type DataTablaRowActionFunction<TData> = ( export type DataTablaRowActionFunction<TData> = (
props: CellContext<TData, unknown> props: CellContext<TData, unknown>
@ -21,6 +22,7 @@ export type DataTablaRowActionFunction<TData> = (
export type DataTableRowActionDefinition<TData> = { export type DataTableRowActionDefinition<TData> = {
label: string | "-"; label: string | "-";
icon?: ReactElement<any, any>;
shortcut?: string; shortcut?: string;
onClick?: (props: CellContext<TData, unknown>, e: React.BaseSyntheticEvent) => void; onClick?: (props: CellContext<TData, unknown>, e: React.BaseSyntheticEvent) => void;
}; };
@ -46,7 +48,7 @@ export function DataTableRowActions<TData = any, TValue = unknown>({
variant='link' variant='link'
className={cn("w-4 h-4 mt-2 text-ring hover:text-muted-foreground", className)} className={cn("w-4 h-4 mt-2 text-ring hover:text-muted-foreground", className)}
> >
<MoreHorizontalIcon className='w-4 h-4' /> <MoreVerticalIcon className='w-4 h-4' />
<span className='sr-only'>{t("common.open_menu")}</span> <span className='sr-only'>{t("common.open_menu")}</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@ -62,8 +64,9 @@ export function DataTableRowActions<TData = any, TValue = unknown>({
key={index} key={index}
onClick={(event) => (action.onClick ? action.onClick(rowContext, event) : null)} onClick={(event) => (action.onClick ? action.onClick(rowContext, event) : null)}
> >
{action.icon && <>{action.icon}</>}
{action.label} {action.label}
<DropdownMenuShortcut>{action.shortcut}</DropdownMenuShortcut> {action.shortcut && <DropdownMenuShortcut>{action.shortcut}</DropdownMenuShortcut>}
</DropdownMenuItem> </DropdownMenuItem>
) )
)} )}

View File

@ -5,7 +5,7 @@ import { DataTableFilterField, useDataTableContext } from "@/lib/hooks";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button, Input } from "@/ui"; import { Button, Input } from "@/ui";
import { t } from "i18next"; import { t } from "i18next";
import { XIcon } from "lucide-react"; import { SearchIcon, XIcon } from "lucide-react";
import { DataTableColumnOptions } from "./DataTableColumnOptions"; import { DataTableColumnOptions } from "./DataTableColumnOptions";
interface DataTableToolbarProps<TData> extends React.HTMLAttributes<HTMLDivElement> { interface DataTableToolbarProps<TData> extends React.HTMLAttributes<HTMLDivElement> {
@ -32,6 +32,7 @@ export function DataTableToolbar<TData>({
{...props} {...props}
> >
<div className='flex items-center flex-1 space-x-2'> <div className='flex items-center flex-1 space-x-2'>
<SearchIcon className='w-4 h-4 text-gray-500' />
<Input <Input
key='global-filter' key='global-filter'
placeholder={t("common.filter_placeholder")} placeholder={t("common.filter_placeholder")}

View File

@ -17,6 +17,7 @@ const formCurrencyFieldVariants = cva(
default: default:
"border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ", "border border-input ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ",
outline: "focus-visible:border focus-visible:border-input", outline: "focus-visible:border focus-visible:border-input",
ghost: "bg-transparent",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@ -66,7 +66,6 @@ export const FormDatePickerField = React.forwardRef<
<PopoverTrigger asChild> <PopoverTrigger asChild>
<FormControl> <FormControl>
<Button <Button
variant={"outline"}
className={cn( className={cn(
"pl-3 text-left font-normal", "pl-3 text-left font-normal",
!field.value && "text-muted-foreground" !field.value && "text-muted-foreground"
@ -79,7 +78,7 @@ export const FormDatePickerField = React.forwardRef<
) : ( ) : (
<span>{t("common.pick_date")}</span> <span>{t("common.pick_date")}</span>
)} )}
<CalendarIcon className='w-4 h-4 ml-auto text-input' /> <CalendarIcon className='w-4 h-4 ml-auto text-' />
</Button> </Button>
</FormControl> </FormControl>
</PopoverTrigger> </PopoverTrigger>

View File

@ -13,7 +13,7 @@ const formTextFieldVariants = cva("", {
variants: { variants: {
variant: { variant: {
default: "", default: "",
outline: ghost:
"border-0 focus-visible:border focus-visible:border-input focus-visible:ring-0 focus-visible:ring-offset-0 ", "border-0 focus-visible:border focus-visible:border-input focus-visible:ring-0 focus-visible:ring-offset-0 ",
}, },
}, },
@ -48,6 +48,7 @@ export const FormTextField = React.forwardRef<HTMLInputElement, FormTextFieldPro
rules, rules,
type, type,
variant, variant,
required,
button, button,
leadIcon, leadIcon,
@ -62,12 +63,19 @@ export const FormTextField = React.forwardRef<HTMLInputElement, FormTextFieldPro
control={control} control={control}
name={name} name={name}
disabled={disabled} disabled={disabled}
rules={rules} rules={{
required,
...rules,
}}
render={({ field, fieldState }) => { render={({ field, fieldState }) => {
return ( return (
<FormItem ref={ref} className={cn(className, "space-y-3")}> <FormItem ref={ref} className={cn(className, "space-y-3")}>
{label && ( {label && (
<FormLabel label={label} hint={hint} required={Boolean(rules?.required ?? false)} /> <FormLabel
label={label}
hint={hint}
required={Boolean(rules?.required ?? required)}
/>
)} )}
<div className={cn(button ? "flex" : null)}> <div className={cn(button ? "flex" : null)}>
<div <div

View File

@ -62,7 +62,7 @@
--card: 334 62% 100%; --card: 334 62% 100%;
--card-foreground: 334 55% 1%; --card-foreground: 334 55% 1%;
--border: 334 5% 95%; --border: 334 5% 95%;
--input: 334 5% 95%; --input: 214.29 31.82% 91.37%;
--primary: 242.93 100% 67.84%; --primary: 242.93 100% 67.84%;
--primary-foreground: 0 0% 100%; --primary-foreground: 0 0% 100%;
--secondary: 213.75 20.25% 69.02%; --secondary: 213.75 20.25% 69.02%;

View File

@ -12,6 +12,7 @@
"back": "Back", "back": "Back",
"upload": "Upload", "upload": "Upload",
"continue": "Continue", "continue": "Continue",
"close": "Close",
"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",
@ -30,6 +31,7 @@
"error": "Error", "error": "Error",
"actions": "Actions", "actions": "Actions",
"open_menu": "Open menu", "open_menu": "Open menu",
"duplicate_row": "Duplicate",
"duplicate_selected_rows": "Duplicate", "duplicate_selected_rows": "Duplicate",
"duplicate_selected_rows_tooltip": "Duplicate selected row(s)", "duplicate_selected_rows_tooltip": "Duplicate selected row(s)",
"append_empty_row": "Append row", "append_empty_row": "Append row",
@ -183,6 +185,10 @@
"cancel_button": "Cancel the download", "cancel_button": "Cancel the download",
"toast_success": "Quote downloaded" "toast_success": "Quote downloaded"
}, },
"catalog_picker_dialog": {
"title": "Select catalog items",
"description": "To complete your quote, you can add items from the catalog."
},
"status": { "status": {
"draft": "Draft" "draft": "Draft"
}, },

View File

@ -12,6 +12,7 @@
"back": "Volver", "back": "Volver",
"upload": "Cargar", "upload": "Cargar",
"continue": "Continuar", "continue": "Continuar",
"close": "Cerrar",
"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",
@ -30,6 +31,7 @@
"error": "Error", "error": "Error",
"actions": "Acciones", "actions": "Acciones",
"open_menu": "Abrir el menú", "open_menu": "Abrir el menú",
"duplicate_row": "Duplicar",
"duplicate_selected_rows": "Duplicar", "duplicate_selected_rows": "Duplicar",
"duplicate_selected_rows_tooltip": "Duplica las fila(s) seleccionadas(s)", "duplicate_selected_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
"append_empty_row": "Añadir fila", "append_empty_row": "Añadir fila",
@ -179,6 +181,10 @@
"cancel_button": "Cancelar la descarga", "cancel_button": "Cancelar la descarga",
"toast_success": "Quote downloaded" "toast_success": "Quote downloaded"
}, },
"catalog_picker_dialog": {
"title": "Seleccionar artículos del catálogo",
"description": "Para rellenar su cotización, puede añadir artículos del catálogo."
},
"status": { "status": {
"draft": "Borrador" "draft": "Borrador"
}, },