344 lines
9.6 KiB
TypeScript
344 lines
9.6 KiB
TypeScript
|
|
import {
|
||
|
|
FormControl,
|
||
|
|
FormField,
|
||
|
|
FormItem,
|
||
|
|
FormMessage,
|
||
|
|
Input,
|
||
|
|
Textarea,
|
||
|
|
} from "@repo/shadcn-ui/components";
|
||
|
|
|
||
|
|
import { ColumnDef } from "@tanstack/react-table";
|
||
|
|
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, Trash2Icon } from "lucide-react";
|
||
|
|
import { useState } from "react";
|
||
|
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
||
|
|
import { useTranslation } from "react-i18next";
|
||
|
|
import { useDetailColumns } from "../../hooks";
|
||
|
|
import { MODULE_NAME } from "../../manifest";
|
||
|
|
import { formatCurrency } from "../../pages/create/utils";
|
||
|
|
import {
|
||
|
|
CustomerInvoiceItemsSortableDataTable,
|
||
|
|
RowIdData,
|
||
|
|
} from "./customer-invoice-items-sortable-datatable";
|
||
|
|
|
||
|
|
export const CustomerInvoiceItemsCardEditor = ({
|
||
|
|
//currency,
|
||
|
|
//language,
|
||
|
|
defaultValues,
|
||
|
|
}: {
|
||
|
|
//currency: CurrencyData;
|
||
|
|
//language: Language;
|
||
|
|
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
||
|
|
}) => {
|
||
|
|
const { t } = useTranslation(MODULE_NAME);
|
||
|
|
|
||
|
|
const { control, watch, getValues } = useFormContext();
|
||
|
|
|
||
|
|
const watchedItems = watch("items");
|
||
|
|
|
||
|
|
//const [pickerMode] = useState<"dialog" | "panel">("dialog");
|
||
|
|
|
||
|
|
//const [articlePickerDialogOpen, setArticlePickerDialogOpen] = useState<boolean>(false);
|
||
|
|
//const [blockPickerDialogOpen, setBlockPickerDialogOpen] = useState<boolean>(false);
|
||
|
|
|
||
|
|
const { fields, ...fieldActions } = useFieldArray({
|
||
|
|
control,
|
||
|
|
name: "items",
|
||
|
|
});
|
||
|
|
|
||
|
|
const columns: ColumnDef<RowIdData, unknown>[] = useDetailColumns(
|
||
|
|
[
|
||
|
|
/*{
|
||
|
|
id: "row_id" as const,
|
||
|
|
header: () => (
|
||
|
|
<HashIcon aria-label='Orden de fila' className='items-center justify-center w-4 h-4' />
|
||
|
|
),
|
||
|
|
accessorFn: (_: unknown, index: number) => index + 1,
|
||
|
|
size: 5,
|
||
|
|
enableHiding: false,
|
||
|
|
enableSorting: false,
|
||
|
|
enableResizing: false,
|
||
|
|
},*/
|
||
|
|
/*{
|
||
|
|
id: "id_article" as const,
|
||
|
|
accessorKey: "id_article",
|
||
|
|
header: "artículo",
|
||
|
|
cell: ({ row: { index, original } }) => {
|
||
|
|
return (
|
||
|
|
<FormTextAreaField
|
||
|
|
readOnly={original?.id_article}
|
||
|
|
autoSize
|
||
|
|
{...register(`items.${index}.id_article`)}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
},
|
||
|
|
size: 500,
|
||
|
|
},*/
|
||
|
|
{
|
||
|
|
id: "description" as const,
|
||
|
|
accessorKey: "description",
|
||
|
|
header: t("customer_invoices.form_fields.description.label"),
|
||
|
|
cell: ({ row: { index, original } }) => (
|
||
|
|
<FormField
|
||
|
|
control={control}
|
||
|
|
name={`items.${index}.description`}
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem className='md:col-span-2'>
|
||
|
|
<FormControl>
|
||
|
|
<Textarea
|
||
|
|
placeholder={t("customer_invoices.form_fields.description.placeholder")}
|
||
|
|
{...field}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
),
|
||
|
|
minSize: 200,
|
||
|
|
size: 400,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "quantity" as const,
|
||
|
|
accessorKey: "quantity",
|
||
|
|
header: () => (
|
||
|
|
<div className='text-right'>{t("customer_invoices.form_fields.quantity.label")}</div>
|
||
|
|
),
|
||
|
|
cell: ({ row: { index } }) => (
|
||
|
|
<FormField
|
||
|
|
control={control}
|
||
|
|
name={`items.${index}.quantity.amount`}
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<FormControl>
|
||
|
|
<Input
|
||
|
|
type='number'
|
||
|
|
step='0.01'
|
||
|
|
min='0'
|
||
|
|
{...field}
|
||
|
|
onChange={(e) => field.onChange(Number(e.target.value) * 100)}
|
||
|
|
value={field.value / 100}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
),
|
||
|
|
size: 75,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "unit_price" as const,
|
||
|
|
accessorKey: "unit_price",
|
||
|
|
header: () => (
|
||
|
|
<div className='text-right'>
|
||
|
|
{t("customer_invoices.form_fields.items.unit_price.label")}
|
||
|
|
</div>
|
||
|
|
),
|
||
|
|
cell: ({ row: { index } }) => (
|
||
|
|
<FormField
|
||
|
|
control={control}
|
||
|
|
name={`items.${index}.unit_price.amount`}
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<FormControl>
|
||
|
|
<Input
|
||
|
|
type='number'
|
||
|
|
step='0.01'
|
||
|
|
min='0'
|
||
|
|
{...field}
|
||
|
|
onChange={(e) => field.onChange(Number(e.target.value) * 100)}
|
||
|
|
value={field.value / 100}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
),
|
||
|
|
size: 125,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "subtotal_price" as const,
|
||
|
|
accessorKey: "subtotal_price",
|
||
|
|
header: () => (
|
||
|
|
<div className='text-right'>
|
||
|
|
{t("customer_invoices.form_fields.items.subtotal_price.label")}
|
||
|
|
</div>
|
||
|
|
),
|
||
|
|
cell: ({ row: { index } }) => {
|
||
|
|
/*return (
|
||
|
|
<FormCurrencyField
|
||
|
|
variant='ghost'
|
||
|
|
currency={currency}
|
||
|
|
language={language}
|
||
|
|
scale={2}
|
||
|
|
readOnly
|
||
|
|
className='text-right'
|
||
|
|
{...register(`items.${index}.subtotal_price`)}
|
||
|
|
/>
|
||
|
|
);*/
|
||
|
|
return null;
|
||
|
|
},
|
||
|
|
size: 150,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "discount" as const,
|
||
|
|
accessorKey: "discount",
|
||
|
|
header: () => (
|
||
|
|
<div className='text-right'>
|
||
|
|
{t("customer_invoices.form_fields.items.discount.label")}
|
||
|
|
</div>
|
||
|
|
),
|
||
|
|
cell: ({ row: { index } }) => (
|
||
|
|
<FormField
|
||
|
|
control={control}
|
||
|
|
name={`items.${index}.discount.amount`}
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<FormControl>
|
||
|
|
<Input
|
||
|
|
type='number'
|
||
|
|
step='0.01'
|
||
|
|
min='0'
|
||
|
|
max='100'
|
||
|
|
{...field}
|
||
|
|
onChange={(e) => field.onChange(Number(e.target.value) * 100)}
|
||
|
|
value={field.value / 100}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
),
|
||
|
|
size: 100,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "total_price" as const,
|
||
|
|
accessorKey: "total_price",
|
||
|
|
header: () => (
|
||
|
|
<div className='text-right'>
|
||
|
|
{t("customer_invoices.form_fields.items.total_price.label")}
|
||
|
|
</div>
|
||
|
|
),
|
||
|
|
cell: ({ row: { index } }) => (
|
||
|
|
<>
|
||
|
|
{formatCurrency(
|
||
|
|
watchedItems[index]?.total_price?.amount || 0,
|
||
|
|
2,
|
||
|
|
getValues("currency")
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
),
|
||
|
|
size: 150,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
{
|
||
|
|
enableDragHandleColumn: true,
|
||
|
|
enableSelectionColumn: true,
|
||
|
|
enableActionsColumn: true,
|
||
|
|
rowActionFn: (props) => {
|
||
|
|
const { table, row } = props;
|
||
|
|
return [
|
||
|
|
{
|
||
|
|
label: t("common.duplicate_row"),
|
||
|
|
icon: <CopyIcon className='w-4 h-4 mr-2' />,
|
||
|
|
onClick: () => table.options.meta?.duplicateItems(row.index),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: t("common.insert_row_above"),
|
||
|
|
icon: <ChevronUpIcon className='w-4 h-4 mr-2' />,
|
||
|
|
onClick: () => table.options.meta?.insertItem(row.index),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: t("common.insert_row_below"),
|
||
|
|
icon: <ChevronDownIcon className='w-4 h-4 mr-2' />,
|
||
|
|
onClick: () => table.options.meta?.insertItem(row.index + 1),
|
||
|
|
},
|
||
|
|
|
||
|
|
{
|
||
|
|
label: "-",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: t("common.remove_row"),
|
||
|
|
//shortcut: "⌘⌫",
|
||
|
|
icon: <Trash2Icon className='w-4 h-4 mr-2' />,
|
||
|
|
onClick: () => {
|
||
|
|
table.options.meta?.deleteItems(row.index);
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/*const handleAppendCatalogArticle = useCallback(
|
||
|
|
(article: any, quantity = 1) => {
|
||
|
|
fieldActions.append({
|
||
|
|
...article,
|
||
|
|
quantity: {
|
||
|
|
amount: 100 * quantity,
|
||
|
|
scale: Quantity.DEFAULT_SCALE,
|
||
|
|
},
|
||
|
|
unit_price: article.retail_price,
|
||
|
|
discount: {
|
||
|
|
amount: null,
|
||
|
|
scale: 2,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
toast({
|
||
|
|
title: t("quotes.catalog_picker_dialog.toast_article_added"),
|
||
|
|
description: article.description,
|
||
|
|
});
|
||
|
|
},
|
||
|
|
[fieldActions, toast]
|
||
|
|
);
|
||
|
|
|
||
|
|
const handleAppendBlock = useCallback(
|
||
|
|
(block: any) => {
|
||
|
|
fieldActions.append({
|
||
|
|
description: `${block.title}\n${block.body}`,
|
||
|
|
quantity: {
|
||
|
|
amount: null,
|
||
|
|
scale: Quantity.DEFAULT_SCALE,
|
||
|
|
},
|
||
|
|
unit_price: {
|
||
|
|
amount: null,
|
||
|
|
scale: UnitPrice.DEFAULT_SCALE,
|
||
|
|
},
|
||
|
|
discount: {
|
||
|
|
amount: null,
|
||
|
|
scale: 2,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
toast({
|
||
|
|
title: t("quotes.blocks_picker_dialog.toast_article_added"),
|
||
|
|
description: block.title,
|
||
|
|
});
|
||
|
|
},
|
||
|
|
[fieldActions]
|
||
|
|
);*/
|
||
|
|
|
||
|
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||
|
|
|
||
|
|
const defaultLayout = [265, 440, 655];
|
||
|
|
const navCollapsedSize = 4;
|
||
|
|
|
||
|
|
console.log(columns);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className='relative'>
|
||
|
|
<CustomerInvoiceItemsSortableDataTable
|
||
|
|
actions={{
|
||
|
|
...fieldActions,
|
||
|
|
//pickCatalogArticle: () => setArticlePickerDialogOpen(true),
|
||
|
|
//pickBlock: () => setBlockPickerDialogOpen(true),
|
||
|
|
}}
|
||
|
|
columns={columns}
|
||
|
|
data={fields}
|
||
|
|
defaultValues={defaultValues}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|