This commit is contained in:
2024-04-11 07:57:01 +03:00
parent 4ce516307d
commit 18157972a1
30 changed files with 911 additions and 50 deletions

View File

@@ -0,0 +1,98 @@
import {FC} from "react";
import {useDealServicesTableColumns} from "./columns.tsx";
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
import {DealServiceSchema} from "../../../../client";
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
import {MRT_TableOptions} from "mantine-react-table";
import {ActionIcon, Box, Button, Flex, rem, Tooltip} from "@mantine/core";
import {openContextModal} from "@mantine/modals";
import {IconTrash} from "@tabler/icons-react";
type RestProps = {
onMultipleDelete?: (items: DealServiceSchema[]) => void;
}
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
const DealServicesTable: FC<Props> = (
{
items,
onChange,
onDelete,
onCreate,
onSelectionChange,
onMultipleDelete,
tableRef
}) => {
const onQuantityChange = (service: DealServiceSchema, quantity: number) => {
if (!onChange) return;
if (quantity <= 0 && onDelete) {
onDelete(service);
return;
}
onChange({...service, quantity});
}
const columns = useDealServicesTableColumns({
onChange: onQuantityChange,
data: items
});
const onCreateClick = () => {
if (!onCreate) return;
openContextModal({
title: "Добавление услуги",
modal: "addDealService",
innerProps: {
onCreate: (event) => onCreate(event as DealServiceSchema)
}
})
}
return (
<BaseTable
ref={tableRef}
data={items}
columns={columns}
onSelectionChange={onSelectionChange}
restProps={{
enableGrouping: true,
initialState: {grouping: ["service.category"]},
enableColumnActions: false,
enableSorting: false,
enableBottomToolbar: true,
enableRowActions: true,
enableRowSelection: true,
renderBottomToolbar: ({table}) => (
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
{(onMultipleDelete && table.getSelectedRowModel().rows.length > 0) && (
<Button
onClick={() => {
onMultipleDelete(table.getSelectedRowModel().rows.map(row => row.original))
}}
variant={"filled"}
color={"red"}
>
Удалить выбранные
</Button>
)}
<Button onClick={onCreateClick} variant={"default"}>
Добавить услугу
</Button>
</Flex>
),
renderRowActions: ({row}) => (
<Flex gap="md">
<Tooltip label="Удалить">
<ActionIcon onClick={() => {
if (onDelete) onDelete(row.original);
}} variant={"default"}>
<IconTrash/>
</ActionIcon>
</Tooltip>
</Flex>
)
} as MRT_TableOptions<DealServiceSchema>}
/>
)
}
export default DealServicesTable;

View File

@@ -0,0 +1,66 @@
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;
data: DealServiceSchema[];
}
export const useDealServicesTableColumns = (props: Props) => {
const {onChange, data} = props;
const totalPrice = useMemo(() =>
data.reduce((acc, row) => acc + row.quantity * row.service.price, 0)
,
[data]);
return useMemo<MRT_ColumnDef<DealServiceSchema>[]>(() => [
{
accessorKey: "service.category",
header: "Категория",
accessorFn: (row) => row.service.category.name,
},
{
enableGrouping: false,
accessorKey: "service.name",
header: "Услуга",
},
{
enableGrouping: false,
accessorKey: "service.price",
header: "Цена",
},
{
enableGrouping: false,
accessorKey: "quantity",
header: "Количество",
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;
},
aggregationFn: "sum",
AggregatedCell: ({cell}) => {
return <>Итоговая сумма по категории: {" "}
{
cell.row.subRows?.reduce((acc, row) =>
acc + row.original.quantity * row.original.service.price, 0)
}
</>;
},
Footer: <>Итоговая сумма по услугам: {totalPrice}</>
}
], [onChange]);
}

View File

@@ -0,0 +1,34 @@
import {createContext, FC, useContext, useState} from "react";
import {DealSchema} from "../../../client";
type DealPageContextState = {
selectedDeal?: DealSchema;
setSelectedDeal: (deal: DealSchema | undefined) => void;
}
const DealPageContext = createContext<DealPageContextState | undefined>(undefined);
const useDealPageContextState = () => {
const [selectedDeal, setSelectedDeal] = useState<DealSchema | undefined>(undefined);
return {selectedDeal, setSelectedDeal};
}
type DealPageContextProviderProps = {
children: React.ReactNode;
}
export const DealPageContextProvider: FC<DealPageContextProviderProps> = ({children}) => {
const state = useDealPageContextState();
return (
<DealPageContext.Provider value={state}>
{children}
</DealPageContext.Provider>
);
}
export const useDealPageContext = () => {
const context = useContext(DealPageContext);
if (!context) {
throw new Error('useDealPageContext must be used within a DealPageContextProvider');
}
return context;
}

View File

@@ -0,0 +1,175 @@
import {Drawer, Text} from "@mantine/core";
import {FC, useRef} from "react";
import DealServicesTable from "../../components/DealServicesTable/DealServicesTable.tsx";
import {useDealPageContext} from "../../contexts/DealPageContext.tsx";
import {DealService, DealServiceSchema} from "../../../../client";
import {notifications} from "../../../../shared/lib/notifications.ts";
import {modals} from "@mantine/modals";
import {BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx";
const useDealServicesTableState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext();
const tableRef = useRef<BaseTableRef<DealServiceSchema>>(null);
const onServiceUpdate = (service: DealServiceSchema) => {
if (!selectedDeal) return;
DealService.updateDealServiceQuantity({
requestBody: {
dealId: selectedDeal.id,
serviceId: service.service.id,
quantity: service.quantity
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
}
const onServiceDelete = (service: DealServiceSchema) => {
if (!selectedDeal) return;
modals.openConfirmModal({
title: "Удаление услуги",
children: (
<>
<Text>
Вы уверены, что хотите удалить услугу:
</Text>
<Text>
{service.service.name}?
</Text>
</>
),
onConfirm: () => {
DealService.deleteDealService({
requestBody: {
dealId: selectedDeal.id,
serviceId: service.service.id
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
},
labels: {
cancel: "Отмена",
confirm: "Удалить"
}
})
}
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
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
}
const onsServiceMultipleDelete = (items: DealServiceSchema[]) => {
if (!selectedDeal) return;
modals.openConfirmModal({
title: "Удаление услуг",
children: (
<>
<Text>
Вы уверены, что хотите удалить выбранные услуги?
</Text>
</>
),
onConfirm: () => {
DealService.deleteMultipleDealServices({
requestBody: {
dealId: selectedDeal.id,
serviceIds: items.map(item => item.service.id)
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
},
labels: {
cancel: "Отмена",
confirm: "Удалить"
}
})
}
return {
onServiceUpdate,
onServiceDelete,
onServiceCreate,
onsServiceMultipleDelete,
tableRef,
services: selectedDeal?.services || []
}
}
const DealEditDrawerServicesTable = () => {
const {
services,
tableRef,
onServiceCreate,
onServiceUpdate,
onServiceDelete,
onsServiceMultipleDelete
} = useDealServicesTableState();
return (<DealServicesTable
tableRef={tableRef}
items={services}
onChange={onServiceUpdate}
onDelete={onServiceDelete}
onCreate={onServiceCreate}
onMultipleDelete={onsServiceMultipleDelete}
/>)
}
const useDealEditDrawerState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext();
return {
isVisible: selectedDeal !== undefined,
onClose: () => setSelectedDeal(undefined)
}
}
const DealEditDrawer: FC = () => {
const {isVisible, onClose} = useDealEditDrawerState();
return (
<Drawer
size={"95%"}
position={"right"}
onClose={onClose}
opened={isVisible}>
<DealEditDrawerServicesTable/>
</Drawer>
);
}
export default DealEditDrawer;

View File

@@ -0,0 +1,55 @@
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";
type Props = CreateEditFormProps<Partial<DealServiceSchema>>;
const AddDealServiceModal = ({
context,
id,
innerProps
}: ContextModalProps<Props>) => {
const form = useForm<Partial<DealServiceSchema>>({
initialValues: {
service: undefined,
quantity: 0,
},
validate: {
service: (service?: DealServiceSchema['service']) => service !== undefined ? null : "Необходимо выбрать услугу",
quantity: (quantity?: number) => (quantity && quantity > 0) ? null : "Количество должно быть больше 0"
}
});
const onClose = () => {
context.closeContextModal(id);
}
return (
<BaseFormModal
{...innerProps}
form={form}
closeOnSubmit
onClose={onClose}>
<BaseFormModal.Body>
<>
<ServiceSelect
placeholder={"Выберите услугу"}
label={"Услуга"}
{...form.getInputProps('service')}
/>
<NumberInput
placeholder={"Введите количество"}
label={"Количество"}
min={1}
{...form.getInputProps('quantity')}
/>
</>
</BaseFormModal.Body>
</BaseFormModal>
)
}
export default AddDealServiceModal;

View File

@@ -5,6 +5,8 @@ import {DragDropContext} from "@hello-pangea/dnd";
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
import {DealStatus} from "../../../shared/enums/DealStatus.ts";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
import {DealPageContextProvider} from "../contexts/DealPageContext.tsx";
export const LeadsPage: FC = () => {
@@ -25,45 +27,50 @@ export const LeadsPage: FC = () => {
}
return (
<>
<PageBlock>
<div className={styles['container']}>
<div className={styles['boards']}>
<DragDropContext onDragEnd={onDragEnd}>
<Board
withCreateButton
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
title={"Ожидает приемки"}
droppableId={"AWAITING_ACCEPTANCE"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.PACKAGING)}
title={"Упаковка"}
droppableId={"PACKAGING"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
title={"Ожидает отгрузки"}
droppableId={"AWAITING_SHIPMENT"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
title={"Ожидает оплаты"}
droppableId={"AWAITING_PAYMENT"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.COMPLETED)}
title={"Завершена"}
droppableId={"COMPLETED"}
/>
</DragDropContext>
<DealPageContextProvider>
<PageBlock>
<div className={styles['container']}>
<div className={styles['boards']}>
<DragDropContext onDragEnd={onDragEnd}>
<Board
withCreateButton
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
title={"Ожидает приемки"}
droppableId={"AWAITING_ACCEPTANCE"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.PACKAGING)}
title={"Упаковка"}
droppableId={"PACKAGING"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
title={"Ожидает отгрузки"}
droppableId={"AWAITING_SHIPMENT"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
title={"Ожидает оплаты"}
droppableId={"AWAITING_PAYMENT"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.COMPLETED)}
title={"Завершена"}
droppableId={"COMPLETED"}
/>
</DragDropContext>
</div>
</div>
</div>
</PageBlock>
</PageBlock>
<DealEditDrawer
/>
</DealPageContextProvider>
</>