397 lines
17 KiB
TypeScript
397 lines
17 KiB
TypeScript
import { FC, useEffect, useState } from "react";
|
||
import styles from "./LeadsPage.module.css";
|
||
import Board from "../../../components/Dnd/Board/Board.tsx";
|
||
import { DragDropContext, Droppable, DropResult } from "@hello-pangea/dnd";
|
||
import { useDealSummaries } from "../hooks/useDealSummaries.tsx";
|
||
import {
|
||
DealStatus,
|
||
getDealStatusByName,
|
||
} 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";
|
||
import { modals } from "@mantine/modals";
|
||
import { DealService, DealSummaryReorderRequest } from "../../../client";
|
||
import { ActionIcon, Flex, NumberInput, rem, Text } from "@mantine/core";
|
||
import classNames from "classnames";
|
||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||
import { IconMenu2, IconMenuDeep } from "@tabler/icons-react";
|
||
import useDealsPageState from "../../DealsPage/hooks/useDealsPageState.tsx";
|
||
import DealStatusSelect from "../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx";
|
||
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||
import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx";
|
||
import { motion } from "framer-motion";
|
||
|
||
enum DisplayMode {
|
||
BOARD,
|
||
TABLE,
|
||
}
|
||
|
||
export const LeadsPage: FC = () => {
|
||
const { data, form } = useDealsPageState();
|
||
|
||
const { summariesRaw, refetch } = useDealSummaries();
|
||
const [summaries, setSummaries] = useState(summariesRaw);
|
||
const [displayMode, setDisplayMode] = useState<DisplayMode>(
|
||
DisplayMode.BOARD
|
||
);
|
||
const [isDragEnded, setIsDragEnded] = useState(true);
|
||
useEffect(() => {
|
||
setSummaries(summariesRaw);
|
||
}, [summariesRaw]);
|
||
|
||
const onDelete = (dealId: number) => {
|
||
const summary = summaries.find(summary => summary.id == dealId);
|
||
if (!summary) return;
|
||
modals.openConfirmModal({
|
||
title: "Удаление сделки",
|
||
children: (
|
||
<Flex>
|
||
Вы действительно хотите удалить сделку {summary.name}?
|
||
</Flex>
|
||
),
|
||
onConfirm: () => {
|
||
DealService.deleteDeal({
|
||
requestBody: { dealId: dealId },
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
await refetch();
|
||
});
|
||
},
|
||
labels: {
|
||
confirm: "Удалить",
|
||
cancel: "Отмена",
|
||
},
|
||
});
|
||
};
|
||
const onSuccess = (dealId: number) => {
|
||
const summary = summaries.find(summary => summary.id == dealId);
|
||
if (!summary) return;
|
||
modals.openConfirmModal({
|
||
title: "Завершение сделки",
|
||
children: (
|
||
<Flex>
|
||
Вы действительно хотите завершить сделку {summary.name}?
|
||
</Flex>
|
||
),
|
||
onConfirm: () => {
|
||
DealService.completeDeal({
|
||
requestBody: { dealId: dealId },
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
await refetch();
|
||
});
|
||
},
|
||
labels: {
|
||
confirm: "Завершить",
|
||
cancel: "Отмена",
|
||
},
|
||
});
|
||
};
|
||
const onDragEnd = async (result: DropResult) => {
|
||
setIsDragEnded(true);
|
||
// If there is no changes
|
||
if (!result.destination || result.destination == result.source) return;
|
||
|
||
// Checking for valid dealId
|
||
const dealId = parseInt(result.draggableId);
|
||
if (isNaN(dealId)) return;
|
||
|
||
// Checking for valid deal
|
||
const summary = summaries.find(summary => summary.id == dealId);
|
||
if (!summary) return;
|
||
|
||
// Checking if it is custom actions
|
||
const droppableId = result.destination.droppableId;
|
||
if (droppableId === "DELETE") {
|
||
onDelete(dealId);
|
||
return;
|
||
}
|
||
if (droppableId === "SUCCESS") {
|
||
onSuccess(dealId);
|
||
return;
|
||
}
|
||
const status = getDealStatusByName(droppableId);
|
||
const request: Partial<DealSummaryReorderRequest> = {
|
||
dealId: dealId,
|
||
index: result.destination.index,
|
||
status: status,
|
||
};
|
||
if (status == summary.status) {
|
||
DealService.reorderDealSummaries({
|
||
requestBody: request as DealSummaryReorderRequest,
|
||
}).then(async response => {
|
||
setSummaries(response.summaries);
|
||
await refetch();
|
||
});
|
||
return;
|
||
}
|
||
modals.openContextModal({
|
||
modal: "enterDeadline",
|
||
title: "Необходимо указать дедлайн",
|
||
innerProps: {
|
||
onSubmit: event =>
|
||
DealService.reorderDealSummaries({
|
||
requestBody: event,
|
||
}).then(async response => {
|
||
setSummaries(response.summaries);
|
||
await refetch();
|
||
}),
|
||
request: request,
|
||
},
|
||
});
|
||
};
|
||
const getTableBody = () => {
|
||
return (
|
||
<motion.div
|
||
key={displayMode}
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 0.2 }}>
|
||
<DealsTable items={data} />
|
||
</motion.div>
|
||
);
|
||
};
|
||
const getBoardBody = () => {
|
||
return (
|
||
<motion.div
|
||
style={{
|
||
display: "flex",
|
||
height: "100%",
|
||
flex: 1,
|
||
}}
|
||
key={displayMode}
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 0.2 }}>
|
||
<DragDropContext
|
||
onDragStart={() => {
|
||
setIsDragEnded(false);
|
||
}}
|
||
onDragEnd={onDragEnd}>
|
||
<Flex
|
||
justify={"space-between"}
|
||
direction={"column"}
|
||
style={{ flex: 1 }}>
|
||
<div className={styles["boards"]}>
|
||
<Board
|
||
withCreateButton
|
||
summaries={summaries.filter(
|
||
summary =>
|
||
summary.status ==
|
||
DealStatus.AWAITING_ACCEPTANCE
|
||
)}
|
||
title={"Ожидает приемки"}
|
||
droppableId={"AWAITING_ACCEPTANCE"}
|
||
color={"#4A90E2"}
|
||
/>
|
||
<Board
|
||
summaries={summaries.filter(
|
||
summary =>
|
||
summary.status == DealStatus.PACKAGING
|
||
)}
|
||
title={"Упаковка"}
|
||
droppableId={"PACKAGING"}
|
||
color={"#F5A623"}
|
||
/>
|
||
<Board
|
||
summaries={summaries.filter(
|
||
summary =>
|
||
summary.status ==
|
||
DealStatus.AWAITING_SHIPMENT
|
||
)}
|
||
title={"Ожидает отгрузки"}
|
||
droppableId={"AWAITING_SHIPMENT"}
|
||
color={"#7ED321"}
|
||
/>
|
||
<Board
|
||
summaries={summaries.filter(
|
||
summary =>
|
||
summary.status ==
|
||
DealStatus.AWAITING_PAYMENT
|
||
)}
|
||
title={"Ожидает оплаты"}
|
||
droppableId={"AWAITING_PAYMENT"}
|
||
color={"#D0021B"}
|
||
/>
|
||
<Board
|
||
summaries={summaries.filter(
|
||
summary =>
|
||
summary.status == DealStatus.COMPLETED
|
||
)}
|
||
title={"Завершена"}
|
||
droppableId={"COMPLETED"}
|
||
color={"#417505"}
|
||
/>
|
||
</div>
|
||
<Flex
|
||
justify={"space-between"}
|
||
gap={rem(10)}>
|
||
<div
|
||
className={classNames(
|
||
styles["delete"],
|
||
isDragEnded && styles["delete-hidden"]
|
||
)}>
|
||
<Droppable droppableId={"DELETE"}>
|
||
{(provided, snapshot) => (
|
||
<>
|
||
<div
|
||
{...provided.droppableProps}
|
||
ref={provided.innerRef}>
|
||
{!isDragEnded &&
|
||
!snapshot.isDraggingOver && (
|
||
<span>Удалить</span>
|
||
)}
|
||
</div>
|
||
{provided.placeholder}
|
||
</>
|
||
)}
|
||
</Droppable>
|
||
</div>
|
||
<div
|
||
className={classNames(
|
||
styles["delete"],
|
||
isDragEnded && styles["delete-hidden"]
|
||
)}>
|
||
<Droppable droppableId={"SUCCESS"}>
|
||
{(provided, snapshot) => (
|
||
<>
|
||
<div
|
||
{...provided.droppableProps}
|
||
ref={provided.innerRef}>
|
||
{!isDragEnded &&
|
||
!snapshot.isDraggingOver && (
|
||
<span>
|
||
Успешно завершена
|
||
</span>
|
||
)}
|
||
</div>
|
||
{provided.placeholder}
|
||
</>
|
||
)}
|
||
</Droppable>
|
||
</div>
|
||
</Flex>
|
||
</Flex>
|
||
</DragDropContext>
|
||
</motion.div>
|
||
);
|
||
};
|
||
const getBody = () => {
|
||
return displayMode === DisplayMode.TABLE
|
||
? getTableBody()
|
||
: getBoardBody();
|
||
};
|
||
return (
|
||
<PageBlock
|
||
fullHeight
|
||
style={{
|
||
gap: rem(10),
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
backgroundColor: "transparent",
|
||
boxShadow: "none",
|
||
}}>
|
||
<DealPageContextProvider>
|
||
<PageBlock style={{ flex: 0 }}>
|
||
<Flex
|
||
align={"center"}
|
||
justify={"space-between"}>
|
||
<Flex
|
||
gap={rem(10)}
|
||
direction={"column"}
|
||
align={"center"}>
|
||
<Text size={"xs"}>Вид</Text>
|
||
<Flex gap={rem(10)}>
|
||
<ActionIcon
|
||
onClick={() =>
|
||
setDisplayMode(DisplayMode.BOARD)
|
||
}
|
||
variant={
|
||
displayMode === DisplayMode.BOARD
|
||
? "filled"
|
||
: "default"
|
||
}>
|
||
<IconMenuDeep
|
||
style={{ rotate: "-90deg" }}
|
||
/>
|
||
</ActionIcon>
|
||
<ActionIcon
|
||
onClick={() =>
|
||
setDisplayMode(DisplayMode.TABLE)
|
||
}
|
||
variant={
|
||
displayMode === DisplayMode.TABLE
|
||
? "filled"
|
||
: "default"
|
||
}>
|
||
<IconMenu2 />
|
||
</ActionIcon>
|
||
</Flex>
|
||
</Flex>
|
||
<motion.div
|
||
key={displayMode}
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 0.2 }}>
|
||
<div
|
||
className={styles["top-panel"]}
|
||
style={{
|
||
display:
|
||
displayMode === DisplayMode.TABLE
|
||
? "flex"
|
||
: "none",
|
||
}}>
|
||
<NumberInput
|
||
min={1}
|
||
step={1}
|
||
placeholder={"Введите номер"}
|
||
{...form.getInputProps("id")}
|
||
/>
|
||
<DealStatusSelect
|
||
onClear={() =>
|
||
form.setFieldValue("dealStatus", null)
|
||
}
|
||
clearable
|
||
placeholder={"Выберите статус "}
|
||
{...form.getInputProps("dealStatus")}
|
||
/>
|
||
<BaseMarketplaceSelect
|
||
onClear={() =>
|
||
form.setFieldValue("marketplace", null)
|
||
}
|
||
clearable
|
||
placeholder={"Выберите маркетплейс"}
|
||
{...form.getInputProps("marketplace")}
|
||
/>
|
||
<ClientSelectNew
|
||
onClear={() =>
|
||
form.setFieldValue("client", null)
|
||
}
|
||
clearable
|
||
searchable
|
||
placeholder={"Выберите клиента"}
|
||
{...form.getInputProps("client")}
|
||
/>
|
||
</div>
|
||
</motion.div>
|
||
</Flex>
|
||
</PageBlock>
|
||
<PageBlock
|
||
style={{
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
flex: 1,
|
||
height: "100%",
|
||
}}>
|
||
{getBody()}
|
||
</PageBlock>
|
||
<DealEditDrawer />
|
||
</DealPageContextProvider>
|
||
</PageBlock>
|
||
);
|
||
};
|