feat: deal product services
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {ActionIcon, Button, Flex, Input, rem} from "@mantine/core";
|
||||
import {ActionIcon, Button, ComboboxItem, ComboboxItemGroup, Flex, Input, OptionsFilter, rem} from "@mantine/core";
|
||||
import {BaseFormInputProps} from "../../../../types/utils.ts";
|
||||
import {DealProductServiceSchema, ServiceSchema} from "../../../../client";
|
||||
import {FC, useEffect, useState} from "react";
|
||||
@@ -32,6 +32,16 @@ const DealProductServiceTable: FC<Props> = (props: Props) => {
|
||||
setInnerValue(oldValue => oldValue.filter((_, i) => i !== idx));
|
||||
|
||||
}
|
||||
|
||||
const serviceOptionsFilter = ({options}: { options: ComboboxItemGroup[] }) => {
|
||||
const productServiceIds = innerValue.map(service => service.service?.id);
|
||||
return (options as ComboboxItemGroup[]).map(({items, group}) => {
|
||||
return {
|
||||
group,
|
||||
items: items.filter(item => !productServiceIds.includes(parseInt((item as ComboboxItem).value)))
|
||||
}
|
||||
})
|
||||
};
|
||||
useEffect(() => {
|
||||
onChange(innerValue as DealProductServiceSchema[]);
|
||||
}, [innerValue]);
|
||||
@@ -39,7 +49,8 @@ const DealProductServiceTable: FC<Props> = (props: Props) => {
|
||||
<Input.Wrapper error={error}>
|
||||
<Flex direction={"column"} gap={rem(10)}>
|
||||
{innerValue.map((service, idx) => (
|
||||
<Flex key={idx} direction={"row"} gap={rem(10)} align={"center"} justify={"stretch"}>
|
||||
<Flex key={service.service?.name || idx} direction={"row"} gap={rem(10)} align={"center"}
|
||||
justify={"stretch"}>
|
||||
<ActionIcon onClick={() => onDelete(idx)} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
@@ -48,7 +59,8 @@ const DealProductServiceTable: FC<Props> = (props: Props) => {
|
||||
onChange: (event) => onServiceChange(idx, event),
|
||||
value: service.service,
|
||||
placeholder: "Выберите услугу",
|
||||
style: {width: "100%"}
|
||||
style: {width: "100%"},
|
||||
filter: serviceOptionsFilter as OptionsFilter
|
||||
}}
|
||||
priceProps={{
|
||||
onChange: (event) => onQuantityChange(idx, event),
|
||||
|
||||
@@ -40,7 +40,7 @@ const DealProductsTable: FC<Props> = (props: Props) => {
|
||||
onCreate: (product) => onCreate(product as DealProductSchema),
|
||||
clientId
|
||||
},
|
||||
size:"lg"
|
||||
size: "lg"
|
||||
})
|
||||
}
|
||||
const onPrintBarcodeClick = (product: DealProductSchema) => {
|
||||
@@ -73,6 +73,7 @@ const DealProductsTable: FC<Props> = (props: Props) => {
|
||||
})
|
||||
}
|
||||
const onEditClick = (product: DealProductSchema) => {
|
||||
if (!onChange) return;
|
||||
modals.openContextModal({
|
||||
modal: "addDealProduct",
|
||||
title: 'Создание товара',
|
||||
@@ -80,10 +81,9 @@ const DealProductsTable: FC<Props> = (props: Props) => {
|
||||
innerProps: {
|
||||
clientId: clientId,
|
||||
element: product,
|
||||
onChange: () => {
|
||||
}
|
||||
onChange: onChange
|
||||
},
|
||||
size:"lg"
|
||||
size: "lg"
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {useMemo} from "react";
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {DealProductSchema} from "../../../../client";
|
||||
import PlusMinusInput from "../../../../components/PlusMinusInput/PlusMinusInput.tsx";
|
||||
import {List} from "@mantine/core";
|
||||
|
||||
type Props = {
|
||||
@@ -11,7 +10,7 @@ type Props = {
|
||||
const useDealProductsTableColumns = (props: Props) => {
|
||||
const {onChange, data} = props;
|
||||
const totalQuantity = useMemo(() => data.reduce((acc, row) => acc + row.quantity, 0), [data]);
|
||||
|
||||
const totalPrice = useMemo(() => data.reduce((totalAcc, row) => totalAcc + row.services.reduce((singleAcc, service) => singleAcc + service.price * row.quantity, 0), 0), [data]);
|
||||
return useMemo<MRT_ColumnDef<DealProductSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "product.article",
|
||||
@@ -50,14 +49,24 @@ const useDealProductsTableColumns = (props: Props) => {
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
Footer: <>Всего товаров: {totalQuantity} </>,
|
||||
Cell: ({row}) => {
|
||||
return (
|
||||
<PlusMinusInput
|
||||
value={row.original.quantity}
|
||||
onChange={(value) => onChange(row.original, value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
header: "Услуги",
|
||||
Cell: ({row}) => <List size={"sm"}>{
|
||||
row.original.services.map(service => `${service.service.name} (${service.price}₽ за шт)`)
|
||||
.map(serviceText => <List.Item key={serviceText}>
|
||||
{serviceText}
|
||||
</List.Item>
|
||||
)}
|
||||
</List>,
|
||||
enableColumnActions: false,
|
||||
},
|
||||
{
|
||||
header: "Итоговая стоимость услуг",
|
||||
Cell: ({row}) => <>
|
||||
{row.original.services.reduce((acc, service) => acc + row.original.quantity * service.price, 0)}</>,
|
||||
enableColumnActions: false,
|
||||
Footer: <>Всего стоимость услуг: {totalPrice}</>
|
||||
}
|
||||
], [onChange, data])
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
import {ActionIcon, Button, Flex, rem, Tooltip} from "@mantine/core";
|
||||
import {openContextModal} from "@mantine/modals";
|
||||
import {IconTrash} from "@tabler/icons-react";
|
||||
import {IconEdit, IconTrash} from "@tabler/icons-react";
|
||||
|
||||
type RestProps = {
|
||||
onMultipleDelete?: (items: DealServiceSchema[]) => void;
|
||||
@@ -22,13 +22,15 @@ const DealServicesTable: FC<Props> = (
|
||||
onMultipleDelete,
|
||||
tableRef
|
||||
}) => {
|
||||
const serviceIds = items.map(item => item.service.id);
|
||||
const onQuantityChange = (service: DealServiceSchema, quantity: number) => {
|
||||
if (!onChange) return;
|
||||
if (quantity <= 0 && onDelete) {
|
||||
onDelete(service);
|
||||
return;
|
||||
}
|
||||
onChange({...service, quantity});
|
||||
return;
|
||||
// if (!onChange) return;
|
||||
// if (quantity <= 0 && onDelete) {
|
||||
// onDelete(service);
|
||||
// return;
|
||||
// }
|
||||
// onChange({...service, quantity});
|
||||
}
|
||||
const columns = useDealServicesTableColumns({
|
||||
onChange: onQuantityChange,
|
||||
@@ -40,11 +42,24 @@ const DealServicesTable: FC<Props> = (
|
||||
title: "Добавление услуги",
|
||||
modal: "addDealService",
|
||||
innerProps: {
|
||||
onCreate: (event) => onCreate(event as DealServiceSchema)
|
||||
onCreate: (event) => onCreate(event as DealServiceSchema),
|
||||
serviceIds
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
const onEditClick = (service: DealServiceSchema) => {
|
||||
if (!onChange) return;
|
||||
openContextModal({
|
||||
title: "Добавление услуги",
|
||||
modal: "addDealService",
|
||||
innerProps: {
|
||||
element: service,
|
||||
onChange,
|
||||
serviceIds
|
||||
}
|
||||
})
|
||||
}
|
||||
return (
|
||||
<BaseTable
|
||||
ref={tableRef}
|
||||
@@ -80,7 +95,6 @@ const DealServicesTable: FC<Props> = (
|
||||
),
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDelete(row.original);
|
||||
@@ -88,6 +102,13 @@ const DealServicesTable: FC<Props> = (
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon onClick={() => {
|
||||
onEditClick(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)
|
||||
} as MRT_TableOptions<DealServiceSchema>}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {useMemo} from "react";
|
||||
import {DealServiceSchema} from "../../../../client";
|
||||
import PlusMinusInput from "../../../../components/PlusMinusInput/PlusMinusInput.tsx";
|
||||
|
||||
type Props = {
|
||||
onChange: (service: DealServiceSchema, quantity: number) => void;
|
||||
@@ -11,7 +10,7 @@ type Props = {
|
||||
export const useDealServicesTableColumns = (props: Props) => {
|
||||
const {onChange, data} = props;
|
||||
const totalPrice = useMemo(() =>
|
||||
data.reduce((acc, row) => acc + row.quantity * row.service.price, 0)
|
||||
data.reduce((acc, row) => acc + row.quantity * row.price, 0)
|
||||
,
|
||||
[data]);
|
||||
|
||||
@@ -28,34 +27,34 @@ export const useDealServicesTableColumns = (props: Props) => {
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
accessorKey: "service.price",
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
accessorKey: "quantity",
|
||||
header: "Количество",
|
||||
Cell: ({row}) => {
|
||||
return (
|
||||
<PlusMinusInput
|
||||
value={row.original.quantity}
|
||||
onChange={(value) => onChange(row.original, value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// Cell: ({row}) => {
|
||||
// return (
|
||||
// <PlusMinusInput
|
||||
// value={row.original.quantity}
|
||||
// onChange={(value) => onChange(row.original, value)}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
header: "Сумма",
|
||||
Cell: ({row}) => {
|
||||
return row.original.quantity * row.original.service.price;
|
||||
return row.original.quantity * row.original.price;
|
||||
},
|
||||
aggregationFn: "sum",
|
||||
AggregatedCell: ({cell}) => {
|
||||
return <>Итоговая сумма по категории: {" "}
|
||||
{
|
||||
cell.row.subRows?.reduce((acc, row) =>
|
||||
acc + row.original.quantity * row.original.service.price, 0)
|
||||
acc + row.original.quantity * row.original.price, 0)
|
||||
}
|
||||
</>;
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ export const useDealStatusChangeTableColumns = () => {
|
||||
accessorKey: "comment",
|
||||
header: "Комментарий",
|
||||
Cell: ({row}) =>
|
||||
<Spoiler onDoubleClick={()=>{console.log("double click")}} maxHeight={80} showLabel={"Показать весь"} hideLabel={"Скрыть"}>
|
||||
<Spoiler maxHeight={80} showLabel={"Показать весь"} hideLabel={"Скрыть"}>
|
||||
<Text style={{wordWrap: "break-word", wordBreak: "break-all", whiteSpace: "normal"}} span>
|
||||
{row.original.comment}<br/>
|
||||
</Text>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.bottom-panel {
|
||||
padding: rem(10);
|
||||
border-radius: rem(5);
|
||||
@mixin light {
|
||||
background-color: var(--mantine-color-gray-1);
|
||||
}
|
||||
@mixin dark {
|
||||
background-color: var(--mantine-color-dark-5);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {IconBarcode, IconBox, IconCalendarUser, IconSettings} from "@tabler/icon
|
||||
import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealStatusChangeTable.tsx";
|
||||
import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx";
|
||||
import {useQueryClient} from "@tanstack/react-query";
|
||||
// import styles from './DealEditDrawer.module.css';
|
||||
|
||||
const useDealServicesTableState = () => {
|
||||
const {selectedDeal, setSelectedDeal} = useDealPageContext();
|
||||
@@ -18,11 +19,10 @@ const useDealServicesTableState = () => {
|
||||
|
||||
const onServiceUpdate = (service: DealServiceSchema) => {
|
||||
if (!selectedDeal) return;
|
||||
DealService.updateDealServiceQuantity({
|
||||
DealService.updateDealService({
|
||||
requestBody: {
|
||||
dealId: selectedDeal.id,
|
||||
serviceId: service.service.id,
|
||||
quantity: service.quantity
|
||||
service
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
|
||||
@@ -72,14 +72,13 @@ const useDealServicesTableState = () => {
|
||||
})
|
||||
}
|
||||
const onServiceCreate = (service: DealServiceSchema) => {
|
||||
console.log('-------Drawer')
|
||||
console.log(service);
|
||||
if (!selectedDeal) return;
|
||||
DealService.addDealService({
|
||||
requestBody: {
|
||||
dealId: selectedDeal.id,
|
||||
serviceId: service.service.id,
|
||||
quantity: service.quantity
|
||||
quantity: service.quantity,
|
||||
price: service.price
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
if (!ok) {
|
||||
@@ -157,17 +156,14 @@ const useDealProductTableState = () => {
|
||||
|
||||
const onProductUpdate = (product: DealProductSchema) => {
|
||||
if (!selectedDeal) return;
|
||||
DealService.updateDealProductQuantity({
|
||||
DealService.updateDealProduct({
|
||||
requestBody: {
|
||||
dealId: selectedDeal.id,
|
||||
productId: product.product.id,
|
||||
quantity: product.quantity
|
||||
product: product
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
if (!ok) {
|
||||
notifications.guess(ok, {message});
|
||||
return;
|
||||
}
|
||||
notifications.guess(ok, {message});
|
||||
if (!ok) return;
|
||||
await DealService.getDealById({dealId: selectedDeal.id})
|
||||
.then(setSelectedDeal)
|
||||
})
|
||||
@@ -361,7 +357,6 @@ const DealEditDrawer: FC = () => {
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"services"}>
|
||||
<Box p={rem(10)}>
|
||||
|
||||
<DealEditDrawerServicesTable/>
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
@@ -370,9 +365,15 @@ const DealEditDrawer: FC = () => {
|
||||
|
||||
<DealEditDrawerProductsTable/>
|
||||
</Box>
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
{/*<Flex*/}
|
||||
{/* h={"10%"}*/}
|
||||
{/* align={'flex-end'}*/}
|
||||
{/* justify={"flex-end"}*/}
|
||||
{/*>*/}
|
||||
|
||||
{/*</Flex>*/}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ const AddDealProductModal = ({
|
||||
|
||||
const validateServices = (services?: DealProductServiceSchema[]) => {
|
||||
if (!services || services.length == 0) return null;
|
||||
console.log("validating...");
|
||||
console.log( services.filter(service => service.service === undefined))
|
||||
return services.find(service => service.service === undefined) ? "Удалите пустые услуги" : null;
|
||||
}
|
||||
const form = useForm<Partial<DealProductSchema>>({
|
||||
@@ -39,11 +37,11 @@ const AddDealProductModal = ({
|
||||
services: validateServices
|
||||
}
|
||||
});
|
||||
console.log(form.values);
|
||||
|
||||
const onClose = () => {
|
||||
context.closeContextModal(id);
|
||||
}
|
||||
console.log(form.values)
|
||||
return (
|
||||
|
||||
<BaseFormModal
|
||||
|
||||
@@ -2,19 +2,24 @@ import {ContextModalProps} from "@mantine/modals";
|
||||
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import {DealServiceSchema} from "../../../client";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {NumberInput} from "@mantine/core";
|
||||
import ServiceSelect from "../../../components/ServiceSelect/ServiceSelect.tsx";
|
||||
import {ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter} from "@mantine/core";
|
||||
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
||||
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
||||
|
||||
type Props = CreateEditFormProps<Partial<DealServiceSchema>>;
|
||||
type RestProps = {
|
||||
serviceIds?: number[];
|
||||
}
|
||||
type Props = CreateEditFormProps<Partial<DealServiceSchema>> & RestProps;
|
||||
const AddDealServiceModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = 'element' in innerProps;
|
||||
const form = useForm<Partial<DealServiceSchema>>({
|
||||
initialValues: {
|
||||
initialValues: isEditing ? innerProps.element : {
|
||||
service: undefined,
|
||||
quantity: 0,
|
||||
quantity: 1,
|
||||
},
|
||||
validate: {
|
||||
service: (service?: DealServiceSchema['service']) => service !== undefined ? null : "Необходимо выбрать услугу",
|
||||
@@ -25,6 +30,16 @@ const AddDealServiceModal = ({
|
||||
context.closeContextModal(id);
|
||||
}
|
||||
|
||||
const serviceOptionsFilter = ({options}: { options: ComboboxItemGroup[] }) => {
|
||||
if (!innerProps.serviceIds) return options;
|
||||
const productServiceIds = innerProps.serviceIds;
|
||||
return (options as ComboboxItemGroup[]).map(({items, group}) => {
|
||||
return {
|
||||
group,
|
||||
items: items.filter(item => !productServiceIds.includes(parseInt((item as ComboboxItem).value)))
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
@@ -34,10 +49,27 @@ const AddDealServiceModal = ({
|
||||
onClose={onClose}>
|
||||
<BaseFormModal.Body>
|
||||
<>
|
||||
<ServiceSelect
|
||||
placeholder={"Выберите услугу"}
|
||||
label={"Услуга"}
|
||||
{...form.getInputProps('service')}
|
||||
<ServiceWithPriceInput
|
||||
serviceProps={{
|
||||
...form.getInputProps('service'),
|
||||
label: "Услуга",
|
||||
placeholder: "Выберите услугу",
|
||||
style: {width: '100%'},
|
||||
disabled: isEditing,
|
||||
filter: serviceOptionsFilter as OptionsFilter
|
||||
}}
|
||||
priceProps={{
|
||||
...form.getInputProps('price'),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
style: {width: '100%'}
|
||||
}}
|
||||
quantity={form.values.quantity || 1}
|
||||
containerProps={{
|
||||
direction: "column",
|
||||
style: {width: "100%"}
|
||||
}}
|
||||
filterType={ServiceType.DEAL_SERVICE}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите количество"}
|
||||
|
||||
Reference in New Issue
Block a user