From bb34f12274de55a919b093682108c30a90c8a3f2 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 22 Oct 2024 19:31:51 +0300 Subject: [PATCH] feat: total services amount and recalculating --- src/client/index.ts | 2 + src/client/models/DealProductServiceSchema.ts | 1 + .../models/DealRecalculatePriceRequest.ts | 8 +++ .../models/DealRecalculatePriceResponse.ts | 9 +++ src/client/models/DealServiceSchema.ts | 1 + src/client/services/DealService.ts | 22 +++++++ src/components/LockCheckbox/LockCheckbox.tsx | 34 ++++++++++ .../modals/ProductServiceFormModal.tsx | 26 +++++--- .../DealServicesTable/DealServicesTable.tsx | 62 +++++++++++-------- .../hooks/useProductAndServiceTabState.tsx | 31 +++++++--- 10 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 src/client/models/DealRecalculatePriceRequest.ts create mode 100644 src/client/models/DealRecalculatePriceResponse.ts create mode 100644 src/components/LockCheckbox/LockCheckbox.tsx diff --git a/src/client/index.ts b/src/client/index.ts index 0dde429..b1b14b3 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -98,6 +98,8 @@ export type { DealProductSchema } from './models/DealProductSchema'; export type { DealProductServiceSchema } from './models/DealProductServiceSchema'; export type { DealQuickCreateRequest } from './models/DealQuickCreateRequest'; export type { DealQuickCreateResponse } from './models/DealQuickCreateResponse'; +export type { DealRecalculatePriceRequest } from './models/DealRecalculatePriceRequest'; +export type { DealRecalculatePriceResponse } from './models/DealRecalculatePriceResponse'; export type { DealSchema } from './models/DealSchema'; export type { DealServiceSchema } from './models/DealServiceSchema'; export type { DealServicesCopyRequest } from './models/DealServicesCopyRequest'; diff --git a/src/client/models/DealProductServiceSchema.ts b/src/client/models/DealProductServiceSchema.ts index 770b271..7d9e5b9 100644 --- a/src/client/models/DealProductServiceSchema.ts +++ b/src/client/models/DealProductServiceSchema.ts @@ -8,5 +8,6 @@ export type DealProductServiceSchema = { service: ServiceSchema; price: number; employees: Array; + isFixedPrice: boolean; }; diff --git a/src/client/models/DealRecalculatePriceRequest.ts b/src/client/models/DealRecalculatePriceRequest.ts new file mode 100644 index 0000000..0f0623f --- /dev/null +++ b/src/client/models/DealRecalculatePriceRequest.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DealRecalculatePriceRequest = { + dealId: number; +}; + diff --git a/src/client/models/DealRecalculatePriceResponse.ts b/src/client/models/DealRecalculatePriceResponse.ts new file mode 100644 index 0000000..d1c4a12 --- /dev/null +++ b/src/client/models/DealRecalculatePriceResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DealRecalculatePriceResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/DealServiceSchema.ts b/src/client/models/DealServiceSchema.ts index cf53024..c7cbc1a 100644 --- a/src/client/models/DealServiceSchema.ts +++ b/src/client/models/DealServiceSchema.ts @@ -9,5 +9,6 @@ export type DealServiceSchema = { quantity: number; price: number; employees: Array; + isFixedPrice: boolean; }; diff --git a/src/client/services/DealService.ts b/src/client/services/DealService.ts index 4198344..5502716 100644 --- a/src/client/services/DealService.ts +++ b/src/client/services/DealService.ts @@ -34,6 +34,8 @@ import type { DealProductAddKitRequest } from '../models/DealProductAddKitReques import type { DealProductAddKitResponse } from '../models/DealProductAddKitResponse'; import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest'; import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse'; +import type { DealRecalculatePriceRequest } from '../models/DealRecalculatePriceRequest'; +import type { DealRecalculatePriceResponse } from '../models/DealRecalculatePriceResponse'; import type { DealSchema } from '../models/DealSchema'; import type { DealServicesCopyRequest } from '../models/DealServicesCopyRequest'; import type { DealServicesCopyResponse } from '../models/DealServicesCopyResponse'; @@ -350,6 +352,26 @@ export class DealService { }, }); } + /** + * Recalculate Deal Price + * @returns DealRecalculatePriceResponse Successful Response + * @throws ApiError + */ + public static recalculateDealPrice({ + requestBody, + }: { + requestBody: DealRecalculatePriceRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/deal/recalculate-price', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } /** * Services Add * @returns DealAddServicesResponse Successful Response diff --git a/src/components/LockCheckbox/LockCheckbox.tsx b/src/components/LockCheckbox/LockCheckbox.tsx new file mode 100644 index 0000000..3cf876a --- /dev/null +++ b/src/components/LockCheckbox/LockCheckbox.tsx @@ -0,0 +1,34 @@ +import { ActionIcon, ActionIconVariant, CheckboxProps, Tooltip } from "@mantine/core"; +import { FC } from "react"; +import { IconLock, IconLockOpen } from "@tabler/icons-react"; + +type RestProps = { + value?: boolean; + onChange?: (value: boolean) => void; + variant: ActionIconVariant +} +type Props = Omit & RestProps; + +const LockCheckbox: FC = (props) => { + const { value = false } = props; + const getIcon = () => { + return value ? : ; + }; + + const handleChange = () => { + if (props.onChange) { + props.onChange(!value); + } + }; + return ( + + + {getIcon()} + + + ); +}; + +export default LockCheckbox; \ No newline at end of file diff --git a/src/pages/LeadsPage/modals/ProductServiceFormModal.tsx b/src/pages/LeadsPage/modals/ProductServiceFormModal.tsx index 8f404aa..656898a 100644 --- a/src/pages/LeadsPage/modals/ProductServiceFormModal.tsx +++ b/src/pages/LeadsPage/modals/ProductServiceFormModal.tsx @@ -10,7 +10,7 @@ import { ContextModalProps } from "@mantine/modals"; import { useForm, UseFormReturnType } from "@mantine/form"; import { isNil, isNumber } from "lodash"; import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx"; -import { Flex } from "@mantine/core"; +import { Checkbox, Flex, rem } from "@mantine/core"; import { ServiceType } from "../../../shared/enums/ServiceType.ts"; import { useSelector } from "react-redux"; import { RootState } from "../../../redux/store.ts"; @@ -23,20 +23,21 @@ type RestProps = { type Props = CreateEditFormProps & RestProps; const ProductServiceFormModal = ({ - context, - id, - innerProps, -}: ContextModalProps) => { + context, + id, + innerProps, + }: ContextModalProps) => { const authState = useSelector((state: RootState) => state.auth); const isEditing = "onChange" in innerProps; const initialValues: Partial = isEditing ? innerProps.element : { - service: undefined, - price: undefined, - employees: [], - }; + service: undefined, + price: undefined, + employees: [], + isFixedPrice: false, + }; const form = useForm>({ initialValues, validate: { @@ -57,7 +58,7 @@ const ProductServiceFormModal = ({ closeOnSubmit> <> - + + diff --git a/src/pages/LeadsPage/tabs/ProductAndServiceTab/components/DealServicesTable/DealServicesTable.tsx b/src/pages/LeadsPage/tabs/ProductAndServiceTab/components/DealServicesTable/DealServicesTable.tsx index fba2430..7abab4f 100644 --- a/src/pages/LeadsPage/tabs/ProductAndServiceTab/components/DealServicesTable/DealServicesTable.tsx +++ b/src/pages/LeadsPage/tabs/ProductAndServiceTab/components/DealServicesTable/DealServicesTable.tsx @@ -1,21 +1,7 @@ import { CRUDTableProps } from "../../../../../../types/CRUDTable.tsx"; -import { - DealServiceSchema, - GetServiceKitSchema, - UserSchema, -} from "../../../../../../client"; +import { DealServiceSchema, GetServiceKitSchema, UserSchema } from "../../../../../../client"; import { FC, useState } from "react"; -import { - ActionIcon, - Button, - Flex, - Modal, - NumberInput, - rem, - Text, - Title, - Tooltip, -} from "@mantine/core"; +import { ActionIcon, Button, Flex, Modal, NumberInput, rem, Text, Title, Tooltip } from "@mantine/core"; import { IconTrash, IconUsersGroup } from "@tabler/icons-react"; import { modals } from "@mantine/modals"; import { isNumber } from "lodash"; @@ -24,18 +10,19 @@ import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts"; import { useSelector } from "react-redux"; import { RootState } from "../../../../../../redux/store.ts"; import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx"; +import LockCheckbox from "../../../../../../components/LockCheckbox/LockCheckbox.tsx"; type RestProps = { onKitAdd?: (kit: GetServiceKitSchema) => void; }; type Props = CRUDTableProps & RestProps; const DealServicesTable: FC = ({ - items, - onDelete, - onCreate, - onChange, - onKitAdd, -}) => { + items, + onDelete, + onCreate, + onChange, + onKitAdd, + }) => { const authState = useSelector((state: RootState) => state.auth); const { dealState } = useDealProductAndServiceTabState(); @@ -78,7 +65,13 @@ const DealServicesTable: FC = ({ price, }); }; - + const onLockChange = (item: DealServiceSchema, isLocked: boolean) => { + if (!onChange) return; + onChange({ + ...item, + isFixedPrice: isLocked, + }); + }; const onEmployeeClick = (item: DealServiceSchema) => { if (!onChange) return; setCurrentService(item); @@ -91,7 +84,7 @@ const DealServicesTable: FC = ({ const getCurrentEmployees = (): UserSchema[] => { if (!currentService) return []; const item = items.find( - i => i.service.id === currentService.service.id + i => i.service.id === currentService.service.id, ); if (!item) return []; return item.employees; @@ -169,7 +162,9 @@ const DealServicesTable: FC = ({ isNumber(event) && onQuantityChange(service, event) } + value={service.quantity} + /> = ({ } suffix={"₽"} value={service.price} - disabled={authState.isGuest || isLocked} + disabled={authState.isGuest || isLocked || service.isFixedPrice} + rightSectionProps={{ + style: { + display: "flex", + cursor: "pointer", + pointerEvents: "auto", + }, + }} + rightSection={ + onLockChange(service, value)} + /> + } /> ))} @@ -192,7 +202,7 @@ const DealServicesTable: FC = ({ Итог:{" "} {items.reduce( (acc, item) => acc + item.price * item.quantity, - 0 + 0, )} ₽ diff --git a/src/pages/LeadsPage/tabs/ProductAndServiceTab/hooks/useProductAndServiceTabState.tsx b/src/pages/LeadsPage/tabs/ProductAndServiceTab/hooks/useProductAndServiceTabState.tsx index 9132618..bae0b8c 100644 --- a/src/pages/LeadsPage/tabs/ProductAndServiceTab/hooks/useProductAndServiceTabState.tsx +++ b/src/pages/LeadsPage/tabs/ProductAndServiceTab/hooks/useProductAndServiceTabState.tsx @@ -8,13 +8,24 @@ import { useDealPageContext } from "../../../contexts/DealPageContext.tsx"; import { notifications } from "../../../../../shared/lib/notifications.ts"; const useDealState = () => { + const { selectedDeal, setSelectedDeal } = useDealPageContext(); + const recalculate = async () => { + + return DealService.recalculateDealPrice({ + requestBody: { + dealId: selectedDeal?.id || -1, + }, + }); + }; const refetch = async () => { if (!selectedDeal) return; + const { ok, message } = await recalculate(); + if (!ok) notifications.guess(ok, { message }); return DealService.getDealById({ dealId: selectedDeal.id }).then( deal => { setSelectedDeal(deal); - } + }, ); }; return { @@ -25,6 +36,9 @@ const useDealState = () => { const useDealServicesState = (): CRUDTableProps => { const { deal, refetch } = useDealState(); + const refetchAndRecalculate = async () => { + await refetch(); + }; const onCreate = (item: DealServiceSchema) => { if (!deal) return; DealService.addDealService({ @@ -36,7 +50,7 @@ const useDealServicesState = (): CRUDTableProps => { }, }).then(async ({ ok, message }) => { if (!ok) notifications.guess(ok, { message }); - if (ok) await refetch(); + if (ok) await refetchAndRecalculate(); }); }; const onDelete = (item: DealServiceSchema) => { @@ -48,7 +62,7 @@ const useDealServicesState = (): CRUDTableProps => { }, }).then(async ({ ok, message }) => { if (!ok) notifications.guess(ok, { message }); - if (ok) await refetch(); + if (ok) await refetchAndRecalculate(); }); }; const onChange = (item: DealServiceSchema) => { @@ -60,7 +74,7 @@ const useDealServicesState = (): CRUDTableProps => { }, }).then(async ({ ok, message }) => { if (!ok) notifications.guess(ok, { message }); - if (ok) await refetch(); + if (ok) await refetchAndRecalculate(); }); }; return { @@ -73,6 +87,9 @@ const useDealServicesState = (): CRUDTableProps => { const useDealProductsState = (): CRUDTableProps => { const { deal, refetch } = useDealState(); + const refetchAndRecalculate = async () => { + await refetch(); + }; const onCreate = (item: DealProductSchema) => { if (!deal) return; DealService.addDealProduct({ @@ -82,7 +99,7 @@ const useDealProductsState = (): CRUDTableProps => { }, }).then(async ({ ok, message }) => { if (!ok) notifications.guess(ok, { message }); - if (ok) await refetch(); + if (ok) await refetchAndRecalculate(); }); }; const onDelete = (item: DealProductSchema) => { @@ -94,7 +111,7 @@ const useDealProductsState = (): CRUDTableProps => { }, }).then(async ({ ok, message }) => { if (!ok) notifications.guess(ok, { message }); - if (ok) await refetch(); + if (ok) await refetchAndRecalculate(); }); }; const onChange = (item: DealProductSchema) => { @@ -106,7 +123,7 @@ const useDealProductsState = (): CRUDTableProps => { }, }).then(async ({ ok, message }) => { if (!ok) notifications.guess(ok, { message }); - if (ok) await refetch(); + if (ok) await refetchAndRecalculate(); }); }; return {