feat: deal group and stuff
This commit is contained in:
@@ -3,7 +3,6 @@ import { useMemo } from "react";
|
||||
import { DealServiceSchema } from "../../../../client";
|
||||
|
||||
type Props = {
|
||||
// onChange: (service: DealServiceSchema, quantity: number) => void;
|
||||
data: DealServiceSchema[];
|
||||
};
|
||||
|
||||
@@ -31,11 +30,6 @@ export const useDealServicesTableColumns = (props: Props) => {
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
accessorKey: "service.cost",
|
||||
header: "Себестоимость",
|
||||
},
|
||||
{
|
||||
enableGrouping: false,
|
||||
accessorKey: "quantity",
|
||||
|
||||
@@ -4,30 +4,40 @@ import { DealSchema } from "../../../client";
|
||||
type DealPageContextState = {
|
||||
selectedDeal?: DealSchema;
|
||||
setSelectedDeal: (deal: DealSchema | undefined) => void;
|
||||
refetchDeals: () => Promise<void>;
|
||||
};
|
||||
|
||||
const DealPageContext = createContext<DealPageContextState | undefined>(
|
||||
undefined
|
||||
undefined,
|
||||
);
|
||||
const useDealPageContextState = () => {
|
||||
|
||||
type DealPageContextStateProps = {
|
||||
refetchDeals: () => Promise<void>;
|
||||
}
|
||||
|
||||
const useDealPageContextState = (props: DealPageContextStateProps) => {
|
||||
const { refetchDeals } = props;
|
||||
const [selectedDeal, setSelectedDeal] = useState<DealSchema | undefined>(
|
||||
undefined
|
||||
undefined,
|
||||
);
|
||||
|
||||
return {
|
||||
selectedDeal,
|
||||
setSelectedDeal,
|
||||
refetchDeals,
|
||||
};
|
||||
};
|
||||
|
||||
type DealPageContextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
refetchDeals: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const DealPageContextProvider: FC<DealPageContextProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const state = useDealPageContextState();
|
||||
children,
|
||||
refetchDeals,
|
||||
}) => {
|
||||
const state = useDealPageContextState({ refetchDeals });
|
||||
return (
|
||||
<DealPageContext.Provider value={state}>
|
||||
{children}
|
||||
@@ -39,7 +49,7 @@ export const useDealPageContext = () => {
|
||||
const context = useContext(DealPageContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useDealPageContext must be used within a DealPageContextProvider"
|
||||
"useDealPageContext must be used within a DealPageContextProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../../../../redux/store.ts";
|
||||
import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
||||
import LockCheckbox from "../../../../../../components/LockCheckbox/LockCheckbox.tsx";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
|
||||
type RestProps = {
|
||||
onKitAdd?: (kit: GetServiceKitSchema) => void;
|
||||
@@ -23,6 +24,10 @@ const DealServicesTable: FC<Props> = ({
|
||||
onChange,
|
||||
onKitAdd,
|
||||
}) => {
|
||||
const debouncedOnChange = useDebouncedCallback(async (item: DealServiceSchema) => {
|
||||
if (!onChange) return;
|
||||
onChange(item);
|
||||
}, 200);
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const { dealState } = useDealProductAndServiceTabState();
|
||||
@@ -39,7 +44,6 @@ const DealServicesTable: FC<Props> = ({
|
||||
};
|
||||
const onCreateClick = () => {
|
||||
if (!onCreate) return;
|
||||
console.log("228");
|
||||
const serviceIds = items.map(service => service.service.id);
|
||||
modals.openContextModal({
|
||||
modal: "addDealService",
|
||||
@@ -53,21 +57,21 @@ const DealServicesTable: FC<Props> = ({
|
||||
};
|
||||
const onQuantityChange = (item: DealServiceSchema, quantity: number) => {
|
||||
if (!onChange) return;
|
||||
onChange({
|
||||
debouncedOnChange({
|
||||
...item,
|
||||
quantity,
|
||||
});
|
||||
};
|
||||
const onPriceChange = (item: DealServiceSchema, price: number) => {
|
||||
if (!onChange) return;
|
||||
onChange({
|
||||
debouncedOnChange({
|
||||
...item,
|
||||
price,
|
||||
});
|
||||
};
|
||||
const onLockChange = (item: DealServiceSchema, isLocked: boolean) => {
|
||||
if (!onChange) return;
|
||||
onChange({
|
||||
debouncedOnChange({
|
||||
...item,
|
||||
isFixedPrice: isLocked,
|
||||
});
|
||||
@@ -91,7 +95,7 @@ const DealServicesTable: FC<Props> = ({
|
||||
};
|
||||
const onEmployeesChange = (items: UserSchema[]) => {
|
||||
if (!currentService || !onChange) return;
|
||||
onChange({
|
||||
debouncedOnChange({
|
||||
...currentService,
|
||||
employees: items,
|
||||
});
|
||||
|
||||
@@ -15,14 +15,7 @@ const useProductServicesTableColumns = (props: Props) => {
|
||||
() => data.reduce((acc, row) => acc + row.price * quantity, 0),
|
||||
[data, quantity]
|
||||
);
|
||||
const totalCost = useMemo(
|
||||
() =>
|
||||
data.reduce(
|
||||
(acc, row) => acc + (row.service.cost || 0) * quantity,
|
||||
0
|
||||
),
|
||||
[data, quantity]
|
||||
);
|
||||
|
||||
const hideGuestColumns = ["service.cost"];
|
||||
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(
|
||||
() => [
|
||||
@@ -30,17 +23,6 @@ const useProductServicesTableColumns = (props: Props) => {
|
||||
accessorKey: "service.name",
|
||||
header: "Услуга",
|
||||
},
|
||||
{
|
||||
enableHiding: true,
|
||||
accessorKey: "service.cost",
|
||||
header: "Себестоимость",
|
||||
Footer: () => (
|
||||
<>
|
||||
Итоговая себестоимость: {totalCost.toLocaleString("ru")}
|
||||
₽
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
|
||||
@@ -6,23 +6,14 @@ import {
|
||||
ProductSchema,
|
||||
} from "../../../../../../client";
|
||||
import styles from "./ProductView.module.css";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Flex,
|
||||
Image,
|
||||
NumberInput,
|
||||
rem,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { ActionIcon, Box, Flex, Image, NumberInput, rem, Text, Title, Tooltip } from "@mantine/core";
|
||||
import ProductServicesTable from "../ProductServicesTable/ProductServicesTable.tsx";
|
||||
import { isNil, isNumber } from "lodash";
|
||||
import { IconBarcode, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts";
|
||||
import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
|
||||
type Props = {
|
||||
product: DealProductSchema;
|
||||
@@ -52,6 +43,10 @@ const ProductView: FC<Props> = ({
|
||||
onProductEdit,
|
||||
}) => {
|
||||
const { dealState } = useDealProductAndServiceTabState();
|
||||
const debouncedOnChange = useDebouncedCallback(async (item: DealProductSchema) => {
|
||||
if (!onChange) return;
|
||||
onChange(item);
|
||||
}, 200);
|
||||
const isLocked = Boolean(dealState.deal?.billRequest);
|
||||
const onDeleteClick = () => {
|
||||
if (!onDelete) return;
|
||||
@@ -87,7 +82,7 @@ const ProductView: FC<Props> = ({
|
||||
|
||||
const onQuantityChange = (quantity: number) => {
|
||||
if (!onChange) return;
|
||||
onChange({
|
||||
debouncedOnChange({
|
||||
...product,
|
||||
quantity,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
|
||||
import {
|
||||
DealProductSchema,
|
||||
DealService,
|
||||
DealServiceSchema,
|
||||
} from "../../../../../client";
|
||||
import { DealProductSchema, DealService, DealServiceSchema } from "../../../../../client";
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
@@ -11,23 +7,29 @@ const useDealState = () => {
|
||||
|
||||
const { selectedDeal, setSelectedDeal } = useDealPageContext();
|
||||
const recalculate = async () => {
|
||||
|
||||
return DealService.recalculateDealPrice({
|
||||
requestBody: {
|
||||
dealId: selectedDeal?.id || -1,
|
||||
},
|
||||
});
|
||||
};
|
||||
const refetch = async () => {
|
||||
const refetchDeal = async () => {
|
||||
if (!selectedDeal) return;
|
||||
const { ok, message } = await recalculate();
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
|
||||
return DealService.getDealById({ dealId: selectedDeal.id }).then(
|
||||
deal => {
|
||||
async deal => {
|
||||
setSelectedDeal(deal);
|
||||
},
|
||||
);
|
||||
};
|
||||
const refetch = async () => {
|
||||
if (!selectedDeal) return;
|
||||
await refetchDeal();
|
||||
const { ok, message } = await recalculate();
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
|
||||
await refetchDeal();
|
||||
};
|
||||
return {
|
||||
deal: selectedDeal,
|
||||
refetch,
|
||||
|
||||
@@ -44,6 +44,16 @@ export const LeadsPage: FC = () => {
|
||||
setSummaries(summariesRaw);
|
||||
}, [summariesRaw]);
|
||||
|
||||
const recalculate = async (dealId: number) => {
|
||||
return DealService.recalculateDealPrice({
|
||||
requestBody: {
|
||||
dealId: dealId,
|
||||
},
|
||||
}).then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (dealId: number) => {
|
||||
const summary = summaries.find(summary => summary.id == dealId);
|
||||
if (!summary) return;
|
||||
@@ -94,12 +104,79 @@ export const LeadsPage: FC = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
const onCombine = async (result: DropResult) => {
|
||||
if (!result.combine) return;
|
||||
const destination = result.combine.draggableId;
|
||||
const source = result.draggableId;
|
||||
if (!destination || !source) return;
|
||||
const sourceId = parseInt(source);
|
||||
if (destination.includes("group")) {
|
||||
const groupId = parseInt(destination.split("-")[1]);
|
||||
DealService.addDealToGroup({
|
||||
requestBody: {
|
||||
dealId: sourceId,
|
||||
groupId: groupId,
|
||||
},
|
||||
}).then(async response => {
|
||||
if (!response.ok) {
|
||||
notifications.error({ message: response.message });
|
||||
return;
|
||||
}
|
||||
await refetch();
|
||||
await recalculate(sourceId);
|
||||
await refetch();
|
||||
});
|
||||
} else {
|
||||
const destinationId = parseInt(destination);
|
||||
// creating new group
|
||||
DealService.createDealGroup({
|
||||
requestBody: {
|
||||
draggingDealId: sourceId,
|
||||
hoveredDealId: destinationId,
|
||||
},
|
||||
}).then(async response => {
|
||||
if (!response.ok) {
|
||||
notifications.error({ message: response.message });
|
||||
return;
|
||||
}
|
||||
await refetch();
|
||||
await recalculate(sourceId);
|
||||
await refetch();
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
const moveGroup = async (result: DropResult) => {
|
||||
const groupId = parseInt(result.draggableId.split("-")[1]);
|
||||
const destination = result.destination?.droppableId;
|
||||
if (!destination) return;
|
||||
const status = getDealStatusByName(destination);
|
||||
DealService.changeDealGroupStatus({
|
||||
requestBody: {
|
||||
groupId: groupId,
|
||||
newStatus: status,
|
||||
},
|
||||
}).then(async response => {
|
||||
if (!response.ok) {
|
||||
notifications.error({ message: response.message });
|
||||
return;
|
||||
}
|
||||
await refetch();
|
||||
});
|
||||
};
|
||||
const onDragEnd = async (result: DropResult) => {
|
||||
|
||||
if (result.combine) {
|
||||
return onCombine(result);
|
||||
}
|
||||
setIsDragEnded(true);
|
||||
// If there is no changes
|
||||
if (!result.destination || result.destination == result.source) return;
|
||||
|
||||
// Checking for valid dealId
|
||||
if (result.draggableId.includes("group")) {
|
||||
return moveGroup(result);
|
||||
}
|
||||
const dealId = parseInt(result.draggableId);
|
||||
if (isNaN(dealId)) return;
|
||||
|
||||
@@ -317,7 +394,9 @@ export const LeadsPage: FC = () => {
|
||||
backgroundColor: "transparent",
|
||||
boxShadow: "none",
|
||||
}}>
|
||||
<DealPageContextProvider>
|
||||
<DealPageContextProvider refetchDeals={async () => {
|
||||
await refetch();
|
||||
}}>
|
||||
<PrefillDealContextProvider>
|
||||
<PageBlock style={{ flex: 0 }}>
|
||||
<Flex
|
||||
|
||||
Reference in New Issue
Block a user