feat: projects and boards
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { DealPageContextProvider, useDealPageContext } from "../../LeadsPage/contexts/DealPageContext.tsx";
|
||||
import ProductAndServiceTab from "../../LeadsPage/tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||
import { DealPageContextProvider, useDealPageContext } from "../../DealsPage/contexts/DealPageContext.tsx";
|
||||
import ProductAndServiceTab from "../../DealsPage/tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||
import { FC, useEffect } from "react";
|
||||
import { DealService } from "../../../client";
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { DealStatusHistorySchema } from "../../../../client";
|
||||
import {
|
||||
DealStatus,
|
||||
DealStatusDictionary,
|
||||
} from "../../../../shared/enums/DealStatus.ts";
|
||||
import { Spoiler, Text } from "@mantine/core";
|
||||
|
||||
export const useDealStatusChangeTableColumns = () => {
|
||||
@@ -22,16 +18,12 @@ export const useDealStatusChangeTableColumns = () => {
|
||||
`${row.user.firstName} ${row.user.secondName}`,
|
||||
},
|
||||
{
|
||||
accessorKey: "fromStatus",
|
||||
accessorKey: "fromStatus.name",
|
||||
header: "Из статуса",
|
||||
accessorFn: row =>
|
||||
DealStatusDictionary[row.fromStatus as DealStatus],
|
||||
},
|
||||
{
|
||||
accessorKey: "toStatus",
|
||||
accessorKey: "toStatus.name",
|
||||
header: "В статус",
|
||||
accessorFn: row =>
|
||||
DealStatusDictionary[row.toStatus as DealStatus],
|
||||
},
|
||||
{
|
||||
accessorKey: "comment",
|
||||
@@ -1,22 +0,0 @@
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import { FC } from "react";
|
||||
import { DealStatuses } from "../../../../shared/enums/DealStatus.ts";
|
||||
|
||||
type DealStatus = {
|
||||
name: string;
|
||||
id: number;
|
||||
};
|
||||
type Props = Omit<ObjectSelectProps<DealStatus>, "data">;
|
||||
|
||||
const DealStatusSelect: FC<Props> = props => {
|
||||
const data: DealStatus[] = DealStatuses;
|
||||
return (
|
||||
<ObjectSelect
|
||||
data={data}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default DealStatusSelect;
|
||||
@@ -6,7 +6,7 @@ import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { IconEdit } from "@tabler/icons-react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { useDealPageContext } from "../../../LeadsPage/contexts/DealPageContext.tsx";
|
||||
import { useDealPageContext } from "../../contexts/DealPageContext.tsx";
|
||||
|
||||
type RestProps = {
|
||||
viewOnly?: boolean;
|
||||
|
||||
@@ -42,15 +42,6 @@ const useDealsTableColumns = () => {
|
||||
header: "Клиент",
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
Cell: ({ row }) =>
|
||||
new Date(row.original.deadline || 0).toLocaleString("ru-RU"),
|
||||
accessorKey: "deadline",
|
||||
header: "Дедлайн",
|
||||
sortingFn: (rowA, rowB) =>
|
||||
new Date(rowB.original.deadline || 0).getTime() -
|
||||
new Date(rowA.original.deadline || 0).getTime(),
|
||||
},
|
||||
{
|
||||
header: "Общая стоимость",
|
||||
Cell: ({ row }) =>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { ActionIcon, Flex, rem, Text } from "@mantine/core";
|
||||
import { IconEdit, IconMenu2, IconMenuDeep } from "@tabler/icons-react";
|
||||
import { motion } from "framer-motion";
|
||||
import styles from "../../ui/DealsPage.module.css";
|
||||
import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
|
||||
import DisplayMode from "../../enums/DisplayMode.ts";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { DealsPageState } from "../../hooks/useDealsPageState.tsx";
|
||||
import React from "react";
|
||||
import { ProjectSchema } from "../../../../client";
|
||||
import { modals } from "@mantine/modals";
|
||||
import ObjectSelect from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import DealsTableFiltersModal from "../../modals/DealsTableFiltersModal.tsx";
|
||||
|
||||
type Props = {
|
||||
displayMode: DisplayMode;
|
||||
setDisplayMode: React.Dispatch<React.SetStateAction<DisplayMode>>;
|
||||
form: UseFormReturnType<DealsPageState>;
|
||||
projects: ProjectSchema[];
|
||||
refetchProjects: () => void;
|
||||
}
|
||||
|
||||
const LeadsPageHeader = ({
|
||||
displayMode,
|
||||
setDisplayMode,
|
||||
form,
|
||||
projects,
|
||||
refetchProjects,
|
||||
}: Props) => {
|
||||
const openModal = () => {
|
||||
modals.openContextModal({
|
||||
modal: "projectsModal",
|
||||
title: "Проекты",
|
||||
innerProps: {
|
||||
onUpdate: refetchProjects,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getHeaderInputsBoard = () => {
|
||||
return (
|
||||
<div className={styles["top-panel"]}
|
||||
style={{
|
||||
display: displayMode === DisplayMode.BOARD ? "flex" : "none",
|
||||
}}>
|
||||
<ActionIcon
|
||||
size={"lg"}
|
||||
onClick={openModal}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
<ObjectSelect
|
||||
placeholder={"Выберите проект"}
|
||||
data={projects}
|
||||
{...form.getInputProps("project")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getHeaderInputsTable = () => {
|
||||
return (
|
||||
<div
|
||||
className={styles["top-panel"]}
|
||||
style={{
|
||||
display: displayMode === DisplayMode.TABLE ? "flex" : "none",
|
||||
}}
|
||||
>
|
||||
<DealsTableFiltersModal
|
||||
form={form}
|
||||
projects={projects}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<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 }}>
|
||||
{getHeaderInputsTable()}
|
||||
{getHeaderInputsBoard()}
|
||||
</motion.div>
|
||||
</Flex>
|
||||
</PageBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeadsPageHeader;
|
||||
@@ -3,7 +3,7 @@ import { useDisclosure } from "@mantine/hooks";
|
||||
import { DealsWithExcelForm, ProductExcelData } from "../drawers/PrefillDealWithExcelDrawer/types.tsx";
|
||||
import { FileWithPath } from "@mantine/dropzone";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
import { DealService, type ProductFromExcelSchema, ProductSchema } from "../../../client";
|
||||
import { DealService, type ProductFromExcelSchema, ProductSchema, StatusSchema } from "../../../client";
|
||||
import UseExcelDropzone from "../../../types/UseExcelDropzone.tsx";
|
||||
import { useForm, UseFormReturnType } from "@mantine/form";
|
||||
import { useDealPageContext } from "./DealPageContext.tsx";
|
||||
@@ -16,7 +16,7 @@ type PrefillDealsWithExcelContextState = {
|
||||
onProductSelectChange: (barcode: string, selectedProduct: ProductSchema) => void,
|
||||
onDrop: (files: FileWithPath[]) => void;
|
||||
excelDropzone: UseExcelDropzone;
|
||||
createDeals: (values: DealsWithExcelForm) => void;
|
||||
createDeals: (values: DealsWithExcelForm, status: StatusSchema) => void;
|
||||
form: UseFormReturnType<DealsWithExcelForm>;
|
||||
errors: string[];
|
||||
};
|
||||
@@ -24,6 +24,7 @@ type PrefillDealsWithExcelContextState = {
|
||||
const PrefillDealsWithExcelContext = createContext<PrefillDealsWithExcelContextState | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const usePrefillDealsWithExcelContextState = () => {
|
||||
const [prefillWithExcelOpened, { open, close }] = useDisclosure(false);
|
||||
const { refetchDeals } = useDealPageContext();
|
||||
@@ -90,7 +91,7 @@ const usePrefillDealsWithExcelContextState = () => {
|
||||
form.reset();
|
||||
};
|
||||
|
||||
const createDeals = (values: DealsWithExcelForm) => {
|
||||
const createDeals = (values: DealsWithExcelForm, status: StatusSchema) => {
|
||||
const products: ProductFromExcelSchema[] = barcodeProductsMap.entries().map(([, productData]) => {
|
||||
return {
|
||||
productId: productData.selectedProduct!.id,
|
||||
@@ -102,6 +103,7 @@ const usePrefillDealsWithExcelContextState = () => {
|
||||
requestBody: {
|
||||
products,
|
||||
clientId: values.client?.id ?? -1,
|
||||
statusId: status.id,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC } from "react";
|
||||
import { FC, useState } from "react";
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
import {
|
||||
ActionIcon,
|
||||
@@ -14,8 +14,14 @@ import {
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ClientService, DealSchema, DealService, ShippingWarehouseSchema } from "../../../../../client";
|
||||
import { DealStatus, DealStatusDictionary } from "../../../../../shared/enums/DealStatus.ts";
|
||||
import {
|
||||
ClientService,
|
||||
DealSchema,
|
||||
DealService,
|
||||
ProjectSchema,
|
||||
ShippingWarehouseSchema,
|
||||
StatusSchema,
|
||||
} from "../../../../../client";
|
||||
import { isEqual } from "lodash";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
@@ -27,10 +33,13 @@ import ButtonCopy from "../../../../../components/ButtonCopy/ButtonCopy.tsx";
|
||||
import FileSaver from "file-saver";
|
||||
import { dateWithoutTimezone, getCurrentDateTimeForFilename } from "../../../../../shared/lib/date.ts";
|
||||
import { IconBarcode, IconPrinter } from "@tabler/icons-react";
|
||||
import styles from "../../../ui/LeadsPage.module.css";
|
||||
import styles from "../../../ui/DealsPage.module.css";
|
||||
import { base64ToBlob } from "../../../../../shared/lib/utils.ts";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
import ManagerSelect from "../../../../../components/ManagerSelect/ManagerSelect.tsx";
|
||||
import ProjectSelect from "../../../../../components/ProjectSelect/ProjectSelect.tsx";
|
||||
import BoardSelect from "../../../../../components/BoardSelect/BoardSelect.tsx";
|
||||
import DealStatusSelect from "../../../../../components/DealStatusSelect/DealStatusSelect.tsx";
|
||||
|
||||
type Props = {
|
||||
deal: DealSchema;
|
||||
@@ -42,6 +51,7 @@ const Content: FC<Props> = ({ deal }) => {
|
||||
const { setSelectedDeal } = useDealPageContext();
|
||||
const clipboard = useClipboard();
|
||||
const queryClient = useQueryClient();
|
||||
const [project, setProject] = useState<ProjectSchema | null>(deal.board.project);
|
||||
|
||||
// ignore typescript
|
||||
|
||||
@@ -61,6 +71,8 @@ const Content: FC<Props> = ({ deal }) => {
|
||||
value.length > 0
|
||||
? null
|
||||
: "Название сделки не может быть пустым",
|
||||
status: (value: StatusSchema) =>
|
||||
!value && "Статус для сделки не выбран",
|
||||
},
|
||||
});
|
||||
const updateDealInfo = async (values: DealGeneralFormType) => {
|
||||
@@ -147,15 +159,20 @@ const Content: FC<Props> = ({ deal }) => {
|
||||
"ru-RU",
|
||||
)}
|
||||
/>
|
||||
<TextInput
|
||||
disabled
|
||||
placeholder={"Текущий статус"}
|
||||
label={"Текущий статус"}
|
||||
value={
|
||||
DealStatusDictionary[
|
||||
deal.currentStatus as DealStatus
|
||||
]
|
||||
}
|
||||
<ProjectSelect
|
||||
value={project}
|
||||
onChange={setProject}
|
||||
label={"Проект"}
|
||||
/>
|
||||
<BoardSelect
|
||||
project={project}
|
||||
{...form.getInputProps("board")}
|
||||
label={"Доска"}
|
||||
/>
|
||||
<DealStatusSelect
|
||||
board={form.values.board}
|
||||
{...form.getInputProps("status")}
|
||||
label={"Статус"}
|
||||
/>
|
||||
{deal.category && (
|
||||
<TextInput
|
||||
@@ -3,8 +3,13 @@ import ExcelDropzone from "../../../../components/ExcelDropzone/ExcelDropzone.ts
|
||||
import styles from "../PrefillDealWithExcelDrawer/PrefillDealsWithExcelDrawer.module.css";
|
||||
import { usePrefillDealsWithExcelContext } from "../../contexts/PrefillDealsWithExcelContext.tsx";
|
||||
import ProductsPreview from "./components/ProductsPreview.tsx";
|
||||
import { BoardSchema } from "../../../../client";
|
||||
|
||||
const PrefillDealsWithExcelDrawer = () => {
|
||||
type Props = {
|
||||
board: BoardSchema | null;
|
||||
}
|
||||
|
||||
const PrefillDealsWithExcelDrawer = ({ board }: Props) => {
|
||||
const {
|
||||
prefillWithExcelOpened,
|
||||
prefillWithExcelOnClose,
|
||||
@@ -14,10 +19,12 @@ const PrefillDealsWithExcelDrawer = () => {
|
||||
} = usePrefillDealsWithExcelContext();
|
||||
|
||||
const getBody = () => {
|
||||
if (!board || board.dealStatuses.length === 0) return;
|
||||
|
||||
if (barcodeProductsMap?.size === 0) {
|
||||
return <ExcelDropzone dropzone={excelDropzone} onDrop={onDrop} />;
|
||||
}
|
||||
return <ProductsPreview />;
|
||||
return <ProductsPreview status={board.dealStatuses[0]}/>;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -6,8 +6,13 @@ import { ProductExcelData } from "../types.tsx";
|
||||
import BreakdownByCityTable from "./BreakdownByCityTable.tsx";
|
||||
import ClientSelect from "../../../../../components/Selects/ClientSelect/ClientSelect.tsx";
|
||||
import ParsingResultsTooltip from "./ParsingResultsTooltip.tsx";
|
||||
import { StatusSchema } from "../../../../../client";
|
||||
|
||||
const ProductsPreview = () => {
|
||||
type Props = {
|
||||
status: StatusSchema;
|
||||
}
|
||||
|
||||
const ProductsPreview = ({ status }: Props) => {
|
||||
const { barcodeProductsMap, createDeals, form } = usePrefillDealsWithExcelContext();
|
||||
|
||||
const getTitle = (barcode: string, productsData: ProductExcelData) => {
|
||||
@@ -41,7 +46,7 @@ const ProductsPreview = () => {
|
||||
return (
|
||||
<Stack gap={"md"}>
|
||||
<Title order={3}>Предпросмотр</Title>
|
||||
<form onSubmit={form.onSubmit((values) => createDeals(values))}>
|
||||
<form onSubmit={form.onSubmit((values) => createDeals(values, status))}>
|
||||
<ClientSelect
|
||||
{...form.getInputProps("client")}
|
||||
inputContainer={(children) => (
|
||||
6
src/pages/DealsPage/enums/DisplayMode.ts
Normal file
6
src/pages/DealsPage/enums/DisplayMode.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
enum DisplayMode {
|
||||
BOARD,
|
||||
TABLE,
|
||||
}
|
||||
|
||||
export default DisplayMode;
|
||||
7
src/pages/DealsPage/enums/DragState.ts
Normal file
7
src/pages/DealsPage/enums/DragState.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
enum DragState {
|
||||
DRAG_ENDED,
|
||||
DRAG_DEAL,
|
||||
DRAG_STATUS,
|
||||
}
|
||||
|
||||
export default DragState;
|
||||
33
src/pages/DealsPage/hooks/useBoards.tsx
Normal file
33
src/pages/DealsPage/hooks/useBoards.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { BoardSchema, BoardService } from "../../../client";
|
||||
|
||||
type Props = {
|
||||
projectId?: number;
|
||||
}
|
||||
|
||||
const useBoards = ({ projectId }: Props) => {
|
||||
const [boards, setBoards] = useState<BoardSchema[]>([]);
|
||||
|
||||
const refetchBoards = () => {
|
||||
if (!projectId) return;
|
||||
|
||||
BoardService.getBoards({
|
||||
projectId,
|
||||
})
|
||||
.then(data => {
|
||||
setBoards(data.boards);
|
||||
})
|
||||
.catch(e => console.log(e));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refetchBoards();
|
||||
}, [projectId]);
|
||||
|
||||
return {
|
||||
boards,
|
||||
refetchBoards,
|
||||
};
|
||||
};
|
||||
|
||||
export default useBoards;
|
||||
@@ -1,53 +1,90 @@
|
||||
import { useDealSummariesFull } from "../../LeadsPage/hooks/useDealSummaries.tsx";
|
||||
import { useDealSummariesFull } from "./useDealSummaries.tsx";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BaseMarketplaceSchema, ClientSchema } from "../../../client";
|
||||
import { BaseMarketplaceSchema, BoardSchema, ClientSchema, ProjectSchema } from "../../../client";
|
||||
import { DealStatusType } from "../../../shared/enums/DealStatus.ts";
|
||||
|
||||
type State = {
|
||||
|
||||
type Props = {
|
||||
projects: ProjectSchema[];
|
||||
}
|
||||
|
||||
export type DealsPageState = {
|
||||
id: number | null;
|
||||
marketplace: BaseMarketplaceSchema | null;
|
||||
dealStatus: DealStatusType | null;
|
||||
client: ClientSchema | null;
|
||||
project: ProjectSchema | null;
|
||||
|
||||
projectForTable: ProjectSchema | null;
|
||||
board: BoardSchema | null;
|
||||
dealStatus: DealStatusType | null;
|
||||
};
|
||||
const useDealsPageState = () => {
|
||||
|
||||
const useDealsPageState = ({ projects }: Props) => {
|
||||
const { objects } = useDealSummariesFull();
|
||||
const form = useForm<State>({
|
||||
|
||||
const form = useForm<DealsPageState>({
|
||||
initialValues: {
|
||||
project: null,
|
||||
id: null,
|
||||
marketplace: null,
|
||||
dealStatus: null,
|
||||
client: null,
|
||||
|
||||
projectForTable: null,
|
||||
board: null,
|
||||
dealStatus: null,
|
||||
},
|
||||
});
|
||||
|
||||
const [data, setData] = useState(objects);
|
||||
|
||||
const applyFilters = () => {
|
||||
let result = objects;
|
||||
if (form.values.id) {
|
||||
result = result.filter(
|
||||
obj => obj.id === form.values.id
|
||||
)
|
||||
obj => obj.id === form.values.id,
|
||||
);
|
||||
}
|
||||
if (form.values.marketplace) {
|
||||
result = result.filter(
|
||||
obj => obj.baseMarketplace?.key === form.values.marketplace?.key
|
||||
obj => obj.baseMarketplace?.key === form.values.marketplace?.key,
|
||||
);
|
||||
}
|
||||
if (form.values.dealStatus) {
|
||||
if (form.values.projectForTable) {
|
||||
result = result.filter(
|
||||
obj => obj.status === form.values.dealStatus?.id
|
||||
obj => obj.board.projectId === form.values.project?.id,
|
||||
);
|
||||
|
||||
if (form.values.board) {
|
||||
result = result.filter(
|
||||
obj => obj.board.id === form.values.board?.id,
|
||||
);
|
||||
|
||||
if (form.values.dealStatus) {
|
||||
result = result.filter(
|
||||
obj => obj.status.id === form.values.dealStatus?.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (form.values.client) {
|
||||
result = result.filter(
|
||||
obj => obj.clientName === form.values.client?.name
|
||||
obj => obj.clientName === form.values.client?.name,
|
||||
);
|
||||
}
|
||||
setData(result);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
applyFilters();
|
||||
}, [form.values, objects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (projects.length > 0 && form.values.project === null) {
|
||||
form.setFieldValue("project", projects[0]);
|
||||
}
|
||||
}, [projects]);
|
||||
|
||||
return { data, form };
|
||||
};
|
||||
|
||||
|
||||
63
src/pages/DealsPage/hooks/useDnd.tsx
Normal file
63
src/pages/DealsPage/hooks/useDnd.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { BoardSchema, DealSummary } from "../../../client";
|
||||
import { DragStart, DropResult } from "@hello-pangea/dnd";
|
||||
import { useState } from "react";
|
||||
import DragState from "../enums/DragState.ts";
|
||||
import useDealsDnd from "../../../components/Dnd/Deals/DealsDndColumn/hooks/useDealsDnd.tsx";
|
||||
import useStatusesDnd from "../../../components/Dnd/Statuses/Statuses/hooks/useStatusesDnd.tsx";
|
||||
|
||||
|
||||
type Props = {
|
||||
selectedBoard: BoardSchema | null;
|
||||
summariesRaw: DealSummary[];
|
||||
refetchSummaries: () => void;
|
||||
refetchBoards: () => void;
|
||||
}
|
||||
|
||||
const useDnd = ({
|
||||
selectedBoard,
|
||||
summariesRaw,
|
||||
refetchSummaries,
|
||||
refetchBoards,
|
||||
}: Props) => {
|
||||
const [dragState, setDragState] = useState<DragState>(DragState.DRAG_ENDED);
|
||||
|
||||
const {
|
||||
summaries,
|
||||
onDealDragEnd,
|
||||
} = useDealsDnd({
|
||||
summariesRaw,
|
||||
refetchSummaries,
|
||||
})
|
||||
|
||||
const {
|
||||
onStatusDragEnd,
|
||||
} = useStatusesDnd({
|
||||
board: selectedBoard,
|
||||
refetch: refetchBoards,
|
||||
});
|
||||
|
||||
const onDragEnd = async (result: DropResult) => {
|
||||
setDragState(DragState.DRAG_ENDED);
|
||||
if (result.draggableId.includes("status")) {
|
||||
return onStatusDragEnd(result);
|
||||
}
|
||||
return onDealDragEnd(result);
|
||||
}
|
||||
|
||||
const onDragStart = (start: DragStart) => {
|
||||
if (start.source.droppableId.includes("status")) {
|
||||
setDragState(DragState.DRAG_STATUS);
|
||||
} else {
|
||||
setDragState(DragState.DRAG_DEAL);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
summaries,
|
||||
dragState,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDnd;
|
||||
26
src/pages/DealsPage/hooks/useProjects.tsx
Normal file
26
src/pages/DealsPage/hooks/useProjects.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ProjectSchema, ProjectService } from "../../../client";
|
||||
|
||||
|
||||
const useProjects = () => {
|
||||
const [projects, setProjects] = useState<ProjectSchema[]>([]);
|
||||
|
||||
const refetchProjects = () => {
|
||||
ProjectService.getProjects()
|
||||
.then(data => {
|
||||
setProjects(data.projects);
|
||||
})
|
||||
.catch(e => console.log(e));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refetchProjects();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
projects,
|
||||
refetchProjects,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProjects;
|
||||
46
src/pages/DealsPage/modals/BoardModal/BoardModal.tsx
Normal file
46
src/pages/DealsPage/modals/BoardModal/BoardModal.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { Button, Stack, TextInput } from "@mantine/core";
|
||||
import { BoardSchema } from "../../../../client";
|
||||
import useBoardModal from "./hooks/useBoardModal.tsx";
|
||||
|
||||
type Props = {
|
||||
projectId: number;
|
||||
board?: BoardSchema;
|
||||
refetchBoards: () => void;
|
||||
};
|
||||
|
||||
const BoardModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const closeModal = () => context.closeContextModal(id);
|
||||
|
||||
const {
|
||||
form,
|
||||
onSubmit,
|
||||
} = useBoardModal({
|
||||
...innerProps,
|
||||
closeModal,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
{...form.getInputProps("name")}
|
||||
label={"Название"}
|
||||
placeholder={"Введите название"}
|
||||
/>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardModal;
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useForm } from "@mantine/form";
|
||||
import { BoardSchema, BoardService } from "../../../../../client";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
type BoardForm = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
projectId: number;
|
||||
board?: BoardSchema;
|
||||
refetchBoards: () => void;
|
||||
closeModal: () => void;
|
||||
};
|
||||
|
||||
const useBoardModal = ({
|
||||
projectId,
|
||||
board,
|
||||
refetchBoards,
|
||||
closeModal,
|
||||
}: Props) => {
|
||||
const form = useForm<BoardForm>({
|
||||
initialValues: {
|
||||
name: board ? board.name : "",
|
||||
},
|
||||
validate: {
|
||||
name: name => !name && "Необходимо ввести название доски",
|
||||
},
|
||||
});
|
||||
|
||||
const createBoard = (values: BoardForm) => {
|
||||
BoardService.createBoard({
|
||||
requestBody: {
|
||||
board: {
|
||||
projectId: projectId,
|
||||
name: values.name,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
refetchBoards();
|
||||
closeModal();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const updateBoard = (values: BoardForm) => {
|
||||
if (!board) return;
|
||||
|
||||
BoardService.updateBoard({
|
||||
requestBody: {
|
||||
board: {
|
||||
...board,
|
||||
name: values.name,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
refetchBoards();
|
||||
closeModal();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onSubmit = (values: BoardForm) => {
|
||||
if (board) {
|
||||
updateBoard(values);
|
||||
return;
|
||||
}
|
||||
createBoard(values);
|
||||
};
|
||||
|
||||
return {
|
||||
form,
|
||||
onSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export default useBoardModal;
|
||||
78
src/pages/DealsPage/modals/DealsTableFiltersModal.tsx
Normal file
78
src/pages/DealsPage/modals/DealsTableFiltersModal.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { ProjectSchema } from "../../../client";
|
||||
import { Flex, Modal, NumberInput, rem } from "@mantine/core";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { DealsPageState } from "../hooks/useDealsPageState.tsx";
|
||||
import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import DealStatusSelect from "../../../components/DealStatusSelect/DealStatusSelect.tsx";
|
||||
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import InlineButton from "../../../components/InlineButton/InlineButton.tsx";
|
||||
import { IconFilter } from "@tabler/icons-react";
|
||||
import BoardSelect from "../../../components/BoardSelect/BoardSelect.tsx";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturnType<DealsPageState>;
|
||||
projects: ProjectSchema[];
|
||||
};
|
||||
|
||||
const DealsTableFiltersModal = ({ form, projects }: Props) => {
|
||||
const [opened, { open, close }] = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<InlineButton onClick={open}>
|
||||
<IconFilter />
|
||||
Фильтры
|
||||
</InlineButton>
|
||||
<Modal title={"Фильтры для сделок"} opened={opened} onClose={close}>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
<NumberInput
|
||||
min={1}
|
||||
placeholder={"Введите номер"}
|
||||
{...form.getInputProps("id")}
|
||||
hideControls
|
||||
/>
|
||||
<ObjectSelect
|
||||
placeholder={"Выберите проект"}
|
||||
data={projects}
|
||||
clearable
|
||||
searchable
|
||||
{...form.getInputProps("projectForTable")}
|
||||
onClear={() => form.setFieldValue("projectForTable", null)}
|
||||
/>
|
||||
<BoardSelect
|
||||
project={form.values.projectForTable}
|
||||
{...form.getInputProps("board")}
|
||||
clearable
|
||||
/>
|
||||
<DealStatusSelect
|
||||
board={form.values.board}
|
||||
{...form.getInputProps("dealStatus")}
|
||||
clearable
|
||||
/>
|
||||
<BaseMarketplaceSelect
|
||||
onClear={() => form.setFieldValue("marketplace", null)}
|
||||
clearable
|
||||
placeholder={"Выберите маркетплейс"}
|
||||
{...form.getInputProps("marketplace")}
|
||||
/>
|
||||
<ClientSelectNew
|
||||
onClear={() =>
|
||||
form.setFieldValue("client", null)
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
placeholder={"Выберите клиента"}
|
||||
{...form.getInputProps("client")}
|
||||
/>
|
||||
</Flex>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealsTableFiltersModal;
|
||||
85
src/pages/DealsPage/modals/ProjectsModal/ProjectsModal.tsx
Normal file
85
src/pages/DealsPage/modals/ProjectsModal/ProjectsModal.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { ProjectSchema } from "../../../../client";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { ActionIcon, Flex, rem, Stack, TextInput, Tooltip } from "@mantine/core";
|
||||
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import useProjectsTableColumns from "./hooks/projectsTableColumns.tsx";
|
||||
import { IconCheck, IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
|
||||
import useProjectModal from "./hooks/useProjectModal.tsx";
|
||||
|
||||
type Props = {
|
||||
onUpdate: () => void;
|
||||
};
|
||||
|
||||
const ProjectsModal = ({ innerProps }: ContextModalProps<Props>) => {
|
||||
const {
|
||||
projects,
|
||||
name,
|
||||
setName,
|
||||
editingProjects,
|
||||
handleEditClick,
|
||||
handleDeleteClick,
|
||||
handleCreateClick,
|
||||
} = useProjectModal(innerProps);
|
||||
|
||||
const columns = useProjectsTableColumns({ editingProjects });
|
||||
|
||||
return (
|
||||
<Stack gap={rem(10)}>
|
||||
<TextInput
|
||||
label={"Добавить проект"}
|
||||
variant={"default"}
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
<InlineButton
|
||||
variant={"default"}
|
||||
onClick={handleCreateClick}>
|
||||
<IconPlus />
|
||||
Добавить
|
||||
</InlineButton>
|
||||
|
||||
<BaseTable
|
||||
data={projects}
|
||||
columns={columns}
|
||||
|
||||
restProps={
|
||||
{
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableRowActions: true,
|
||||
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => handleEditClick(row.original)}
|
||||
variant={"default"}>
|
||||
{
|
||||
editingProjects.has(row.original.id) ? (
|
||||
<IconCheck />
|
||||
) : (
|
||||
<IconEdit />
|
||||
)
|
||||
}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={"Удалить"}>
|
||||
<ActionIcon
|
||||
onClick={() => handleDeleteClick(row.original)}
|
||||
disabled={row.original.boardsCount > 0}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<ProjectSchema>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectsModal;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { BaseProjectSchema, ProjectSchema } from "../../../../../client";
|
||||
import { TextInput } from "@mantine/core";
|
||||
|
||||
|
||||
type Props = {
|
||||
editingProjects: Map<number, ProjectSchema | BaseProjectSchema>;
|
||||
}
|
||||
|
||||
const useProjectsTableColumns = ({ editingProjects }: Props) => {
|
||||
return useMemo<MRT_ColumnDef<ProjectSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Название",
|
||||
accessorKey: "name",
|
||||
Cell: ({ row }) => {
|
||||
if (editingProjects.has(row.original.id)) {
|
||||
return (
|
||||
<TextInput
|
||||
variant={"default"}
|
||||
value={editingProjects.get(row.original.id)?.name}
|
||||
onChange={e => {
|
||||
const project = editingProjects.get(row.original.id);
|
||||
if (!project) return;
|
||||
project.name = e.target.value;
|
||||
editingProjects.set(row.original.id, project);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return row.original.name;
|
||||
},
|
||||
size: 25,
|
||||
},
|
||||
{
|
||||
header: "Кол-во досок",
|
||||
accessorKey: "boardsCount",
|
||||
size: 10,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default useProjectsTableColumns;
|
||||
@@ -0,0 +1,100 @@
|
||||
import { BaseProjectSchema, ProjectSchema, ProjectService } from "../../../../../client";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { useMap } from "@mantine/hooks";
|
||||
import { useState } from "react";
|
||||
import useProjects from "../../../hooks/useProjects.tsx";
|
||||
|
||||
type Props = {
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
const useProjectModal = ({ onUpdate }: Props) => {
|
||||
const editingProjects = useMap<number, ProjectSchema>();
|
||||
const { projects, refetchProjects } = useProjects();
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const updateProject = (project: ProjectSchema) => {
|
||||
ProjectService.updateProject({
|
||||
requestBody: {
|
||||
project,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
editingProjects.delete(project.id);
|
||||
refetchProjects();
|
||||
onUpdate();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const handleEditClick = (project: ProjectSchema) => {
|
||||
const editedProject = editingProjects.get(project.id);
|
||||
|
||||
if (!editedProject) {
|
||||
editingProjects.set(project.id, project);
|
||||
return;
|
||||
}
|
||||
|
||||
if (editedProject.name.length === 0) {
|
||||
notifications.error({ message: "Имя проекта не может быть пустым" });
|
||||
return;
|
||||
}
|
||||
|
||||
updateProject(project);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (project: ProjectSchema) => {
|
||||
ProjectService.deleteProject({
|
||||
projectId: project.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
refetchProjects();
|
||||
onUpdate();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const createProject = (project: BaseProjectSchema) => {
|
||||
ProjectService.createProject({
|
||||
requestBody: { project },
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
setName("");
|
||||
refetchProjects();
|
||||
onUpdate();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const handleCreateClick = () => {
|
||||
if (name.length === 0) {
|
||||
notifications.error({ message: "Имя проекта не может быть пустым" });
|
||||
return;
|
||||
}
|
||||
createProject({ name });
|
||||
};
|
||||
|
||||
return {
|
||||
projects,
|
||||
name,
|
||||
setName,
|
||||
editingProjects,
|
||||
handleEditClick,
|
||||
handleDeleteClick,
|
||||
handleCreateClick,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProjectModal;
|
||||
46
src/pages/DealsPage/modals/StatusModal/StatusModal.tsx
Normal file
46
src/pages/DealsPage/modals/StatusModal/StatusModal.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { Button, Stack, TextInput } from "@mantine/core";
|
||||
import { StatusSchema } from "../../../../client";
|
||||
import useStatusModal from "./hooks/useStatusModal.tsx";
|
||||
|
||||
type Props = {
|
||||
boardId?: number;
|
||||
status?: StatusSchema;
|
||||
refetch: () => void;
|
||||
};
|
||||
|
||||
const StatusModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const closeModal = () => context.closeContextModal(id);
|
||||
|
||||
const {
|
||||
form,
|
||||
onSubmit,
|
||||
} = useStatusModal({
|
||||
...innerProps,
|
||||
closeModal,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
{...form.getInputProps("name")}
|
||||
label={"Название"}
|
||||
placeholder={"Введите название"}
|
||||
/>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusModal;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useForm } from "@mantine/form";
|
||||
import { StatusSchema, StatusService } from "../../../../../client";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
type StatusForm = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
boardId?: number;
|
||||
status?: StatusSchema;
|
||||
refetch: () => void;
|
||||
closeModal: () => void;
|
||||
};
|
||||
|
||||
const useStatusModal = ({
|
||||
boardId,
|
||||
status,
|
||||
refetch,
|
||||
closeModal,
|
||||
}: Props) => {
|
||||
const form = useForm<StatusForm>({
|
||||
initialValues: {
|
||||
name: status ? status.name : "",
|
||||
},
|
||||
validate: {
|
||||
name: name => !name && "Необходимо ввести название статуса",
|
||||
},
|
||||
});
|
||||
|
||||
const createBoard = (values: StatusForm) => {
|
||||
if (!boardId) return;
|
||||
StatusService.createStatus({
|
||||
requestBody: {
|
||||
status: {
|
||||
boardId,
|
||||
name: values.name,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
refetch();
|
||||
closeModal();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const updateBoard = (values: StatusForm) => {
|
||||
if (!status) return;
|
||||
|
||||
StatusService.updateStatus({
|
||||
requestBody: {
|
||||
status: {
|
||||
...status,
|
||||
name: values.name,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
refetch();
|
||||
closeModal();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onSubmit = (values: StatusForm) => {
|
||||
if (status) {
|
||||
updateBoard(values);
|
||||
return;
|
||||
}
|
||||
createBoard(values);
|
||||
};
|
||||
|
||||
return {
|
||||
form,
|
||||
onSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export default useStatusModal;
|
||||
@@ -1,25 +1,52 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
gap: rem(10);
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.body-container {
|
||||
.statuses {
|
||||
margin-top: 1rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: left;
|
||||
/*background-color: rebeccapurple;*/
|
||||
padding-right: 3%;
|
||||
padding-left: 3%;
|
||||
}
|
||||
|
||||
.header-statuses {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delete {
|
||||
@mixin light {
|
||||
border-color: var(--mantine-color-gray-1);
|
||||
}
|
||||
@mixin dark {
|
||||
border-color: var(--mantine-color-dark-5);
|
||||
}
|
||||
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||
border-radius: var(--item-border-radius);
|
||||
padding: rem(30);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete-hidden {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
padding: rem(5);
|
||||
gap: rem(10);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
gap: rem(10);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-pagination {
|
||||
align-self: flex-end;
|
||||
.print-deals-button {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@@ -1,61 +1,108 @@
|
||||
import { FC } from "react";
|
||||
import { FC, useState } from "react";
|
||||
import { useDealSummaries } from "../hooks/useDealSummaries.tsx";
|
||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||
import styles from "./DealsPage.module.css";
|
||||
import DealStatusSelect from "../components/DealStatusSelect/DealStatusSelect.tsx";
|
||||
import DealsTable from "../components/DealsTable/DealsTable.tsx";
|
||||
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||||
import { DealPageContextProvider } from "../../LeadsPage/contexts/DealPageContext.tsx";
|
||||
import DealEditDrawer from "../../LeadsPage/drawers/DealEditDrawer/DealEditDrawer.tsx";
|
||||
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
|
||||
import { DealPageContextProvider } from "../contexts/DealPageContext.tsx";
|
||||
import { rem } from "@mantine/core";
|
||||
import useDealsPageState from "../hooks/useDealsPageState.tsx";
|
||||
import DealsTable from "../components/DealsTable/DealsTable.tsx";
|
||||
import { motion } from "framer-motion";
|
||||
import DealPrefillDrawer from "../drawers/DealPrefillDrawer/DealPrefillDrawer.tsx";
|
||||
import { PrefillDealContextProvider } from "../contexts/PrefillDealContext.tsx";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { PrefillDealsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
|
||||
import DisplayMode from "../enums/DisplayMode.ts";
|
||||
import LeadsPageHeader from "../components/LeadsPageHeader/LeadsPageHeader.tsx";
|
||||
import useProjects from "../hooks/useProjects.tsx";
|
||||
import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
|
||||
import useBoards from "../hooks/useBoards.tsx";
|
||||
|
||||
export const DealsPage: FC = () => {
|
||||
const { data, form } = useDealsPageState();
|
||||
return (
|
||||
<>
|
||||
<DealPageContextProvider refetchDeals={async () => {
|
||||
const { projects, refetchProjects } = useProjects();
|
||||
const { data, form } = useDealsPageState({ projects });
|
||||
const { boards, refetchBoards } = useBoards({ projectId: form.values.project?.id });
|
||||
const { dealId } = useParams({ strict: false });
|
||||
const { summariesRaw, refetch: refetchSummaries } = useDealSummaries();
|
||||
|
||||
}}>
|
||||
<div className={styles["container"]}>
|
||||
<PageBlock>
|
||||
<div className={styles["top-panel"]}>
|
||||
<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>
|
||||
</PageBlock>
|
||||
<PageBlock>
|
||||
<div className={styles["body-container"]}>
|
||||
<div className={styles["table-container"]}>
|
||||
<DealsTable items={data} />
|
||||
</div>
|
||||
</div>
|
||||
</PageBlock>
|
||||
</div>
|
||||
<DealEditDrawer />
|
||||
const [displayMode, setDisplayMode] = useState<DisplayMode>(
|
||||
DisplayMode.BOARD,
|
||||
);
|
||||
|
||||
const getTableBody = () => {
|
||||
return (
|
||||
<DealsTable items={data} />
|
||||
);
|
||||
};
|
||||
|
||||
const getBoardsBody = () => {
|
||||
return (
|
||||
<Boards
|
||||
summariesRaw={summariesRaw}
|
||||
refetchSummaries={refetchSummaries}
|
||||
boards={boards}
|
||||
refetchBoards={refetchBoards}
|
||||
project={form.values.project}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getBody = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key={displayMode}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
{displayMode === DisplayMode.TABLE
|
||||
? getTableBody()
|
||||
: getBoardsBody()
|
||||
}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageBlock
|
||||
fullHeight
|
||||
style={{
|
||||
gap: rem(10),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "transparent",
|
||||
boxShadow: "none",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<DealPageContextProvider
|
||||
defaultDealId={(dealId && parseInt(dealId)) || undefined}
|
||||
refetchDeals={async () => {
|
||||
await refetchSummaries();
|
||||
}}
|
||||
>
|
||||
<PrefillDealContextProvider>
|
||||
<PrefillDealsWithExcelContextProvider>
|
||||
<LeadsPageHeader
|
||||
form={form}
|
||||
displayMode={displayMode}
|
||||
setDisplayMode={setDisplayMode}
|
||||
projects={projects}
|
||||
refetchProjects={refetchProjects}
|
||||
/>
|
||||
<PageBlock
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{getBody()}
|
||||
</PageBlock>
|
||||
<DealEditDrawer />
|
||||
<DealPrefillDrawer />
|
||||
</PrefillDealsWithExcelContextProvider>
|
||||
</PrefillDealContextProvider>
|
||||
</DealPageContextProvider>
|
||||
</>
|
||||
</PageBlock>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { LeadsPage } from "./ui/LeadsPage.tsx";
|
||||
@@ -1,44 +0,0 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.boards {
|
||||
margin-top: 1rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
/*background-color: rebeccapurple;*/
|
||||
padding-right: 5%;
|
||||
padding-left: 5%;
|
||||
}
|
||||
|
||||
.delete {
|
||||
@mixin light {
|
||||
border-color: var(--mantine-color-gray-1);
|
||||
}
|
||||
@mixin dark {
|
||||
border-color: var(--mantine-color-dark-5);
|
||||
}
|
||||
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||
border-radius: var(--item-border-radius);
|
||||
padding: rem(30);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete-hidden {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
padding: rem(5);
|
||||
gap: rem(10);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.print-deals-button {
|
||||
align-self: center;
|
||||
}
|
||||
@@ -1,508 +0,0 @@
|
||||
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";
|
||||
import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
||||
import DealPrefillDrawer from "../drawers/DealPrefillDrawer/DealPrefillDrawer.tsx";
|
||||
import { PrefillDealContextProvider } from "../contexts/PrefillDealContext.tsx";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { PrefillDealsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
|
||||
import PrefillDealsWithExcelDrawer from "../drawers/PrefillDealWithExcelDrawer/PrefillDealsWithExcelDrawer.tsx";
|
||||
|
||||
enum DisplayMode {
|
||||
BOARD,
|
||||
TABLE,
|
||||
}
|
||||
|
||||
export const LeadsPage: FC = () => {
|
||||
const { data, form } = useDealsPageState();
|
||||
const { dealId } = useParams({ strict: false });
|
||||
const { summariesRaw, refetch } = useDealSummaries();
|
||||
const [summaries, setSummaries] = useState(summariesRaw);
|
||||
const [displayMode, setDisplayMode] = useState<DisplayMode>(
|
||||
DisplayMode.BOARD,
|
||||
);
|
||||
const [isDragEnded, setIsDragEnded] = useState(true);
|
||||
|
||||
// const [selectedDeals, setSelectedDeals] = useState<DealSummary[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
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;
|
||||
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 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
DealService.reorderDealSummaries({
|
||||
requestBody: {
|
||||
dealId: dealId,
|
||||
status: status,
|
||||
index: result.destination.index,
|
||||
comment: "",
|
||||
deadline: dateWithoutTimezone(new Date()),
|
||||
},
|
||||
}).then(async response => {
|
||||
setSummaries(response.summaries);
|
||||
await refetch();
|
||||
});
|
||||
};
|
||||
const getTableBody = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key={displayMode}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<DealsTable items={data}
|
||||
// onSelectionChange={setSelectedDeals}
|
||||
/>
|
||||
</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.READY_FOR_WORK,
|
||||
)}
|
||||
title={"Готов к работе"}
|
||||
droppableId={"READY_FOR_WORK"}
|
||||
color={"#D3D3D3"}
|
||||
/>
|
||||
<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.IN_DELIVERY,
|
||||
)}
|
||||
title={"В доставке"}
|
||||
droppableId={"IN_DELIVERY"}
|
||||
color={"#6A0DAD"}
|
||||
/>
|
||||
<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
|
||||
defaultDealId={(dealId && parseInt(dealId)) || undefined}
|
||||
refetchDeals={async () => {
|
||||
await refetch();
|
||||
}}>
|
||||
<PrefillDealContextProvider>
|
||||
<PrefillDealsWithExcelContextProvider>
|
||||
<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}
|
||||
placeholder={"Введите номер"}
|
||||
{...form.getInputProps("id")}
|
||||
hideControls
|
||||
/>
|
||||
<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 />
|
||||
<DealPrefillDrawer />
|
||||
<PrefillDealsWithExcelDrawer />
|
||||
</PrefillDealsWithExcelContextProvider>
|
||||
</PrefillDealContextProvider>
|
||||
</DealPageContextProvider>
|
||||
</PageBlock>
|
||||
);
|
||||
};
|
||||
@@ -4,8 +4,8 @@ import { ContextModalProps } from "@mantine/modals";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { Input, TextInput } from "@mantine/core";
|
||||
import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx";
|
||||
import { DealPageContextProvider } from "../../LeadsPage/contexts/DealPageContext.tsx";
|
||||
import DealEditDrawer from "../../LeadsPage/drawers/DealEditDrawer/DealEditDrawer.tsx";
|
||||
import { DealPageContextProvider } from "../../DealsPage/contexts/DealPageContext.tsx";
|
||||
import DealEditDrawer from "../../DealsPage/drawers/DealEditDrawer/DealEditDrawer.tsx";
|
||||
|
||||
type RestProps = {
|
||||
summaries: DealSummary[];
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ShippingWarehouseService,
|
||||
} from "../../../client";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
import { useDealSummariesFull } from "../../LeadsPage/hooks/useDealSummaries.tsx";
|
||||
import { useDealSummariesFull } from "../../DealsPage/hooks/useDealSummaries.tsx";
|
||||
|
||||
export const ShippingWarehousesPage = () => {
|
||||
const { shippingWarehouses, refetch } = useShippingWarehousesList();
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { DatePickerInput, DatePickerInputProps } from "@mantine/dates";
|
||||
import { Divider, Stack, Text } from "@mantine/core";
|
||||
import { Checkbox, CheckboxProps, Divider, Stack, Text } from "@mantine/core";
|
||||
import ClientSelectNew from "../../../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||||
import { BaseMarketplaceSchema, ClientSchema, TransactionTagSchema, UserSchema } from "../../../../../../client";
|
||||
import {
|
||||
BaseMarketplaceSchema,
|
||||
BoardSchema,
|
||||
ClientSchema,
|
||||
ProjectSchema,
|
||||
StatusSchema,
|
||||
TransactionTagSchema,
|
||||
UserSchema,
|
||||
} from "../../../../../../client";
|
||||
import { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import BaseMarketplaceSelect
|
||||
from "../../../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
import DealStatusSelect from "../../../../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx";
|
||||
import { DealStatusType } from "../../../../../../shared/enums/DealStatus.ts";
|
||||
import DealStatusSelect from "../../../../../../components/DealStatusSelect/DealStatusSelect.tsx";
|
||||
import { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
|
||||
import ManagerSelect from "../../../../../../components/ManagerSelect/ManagerSelect.tsx";
|
||||
import TransactionTagSelect from "../../../../components/ExpenseTagSelect/TransactionTagSelect.tsx";
|
||||
import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
|
||||
import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
|
||||
|
||||
|
||||
type SelectProps<T> = Omit<
|
||||
@@ -26,12 +35,17 @@ type FiltersProps = {
|
||||
baseMarketplaceSelectProps?: SelectProps<BaseMarketplaceSchema>;
|
||||
onBaseMarketplaceClear?: () => void;
|
||||
|
||||
dealStatusSelectProps?: Omit<ObjectSelectProps<DealStatusType>, "data">;
|
||||
onDealStatusClear?: () => void;
|
||||
projectSelectProps?: Omit<ObjectSelectProps<ProjectSchema | null>, "data">;
|
||||
|
||||
boardSelectProps?: Omit<ObjectSelectProps<BoardSchema | null>, "data">;
|
||||
|
||||
dealStatusSelectProps?: Omit<ObjectSelectProps<StatusSchema | null>, "data">;
|
||||
|
||||
managerSelectProps?: SelectProps<UserSchema | null | undefined>;
|
||||
onManagerClear?: () => void;
|
||||
|
||||
isCompletedOnlyCheckboxProps?: CheckboxProps;
|
||||
|
||||
expenseTagSelectProps?: SelectProps<TransactionTagSchema | null>;
|
||||
onExpenseTagClear?: () => void;
|
||||
|
||||
@@ -61,8 +75,8 @@ export const Filters = (props: FiltersProps) => {
|
||||
isIncome={isIncome}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack mb={"lg"}>
|
||||
@@ -79,12 +93,24 @@ export const Filters = (props: FiltersProps) => {
|
||||
valueFormat={"DD.MM.YYYY"}
|
||||
/>
|
||||
}
|
||||
{props.projectSelectProps &&
|
||||
<ProjectSelect
|
||||
{...props.projectSelectProps}
|
||||
clearable
|
||||
/>
|
||||
}
|
||||
{props.boardSelectProps &&
|
||||
<BoardSelect
|
||||
project={props.projectSelectProps?.value ?? null}
|
||||
{...props.boardSelectProps}
|
||||
clearable
|
||||
/>
|
||||
}
|
||||
{props.dealStatusSelectProps &&
|
||||
<DealStatusSelect
|
||||
board={props.boardSelectProps?.value ?? null}
|
||||
{...props.dealStatusSelectProps}
|
||||
onClear={props.onDealStatusClear}
|
||||
clearable
|
||||
placeholder={"Выберите статус"}
|
||||
/>
|
||||
}
|
||||
{props.clientSelectProps &&
|
||||
@@ -111,6 +137,10 @@ export const Filters = (props: FiltersProps) => {
|
||||
placeholder={"Выберите менеджера"}
|
||||
/>
|
||||
}
|
||||
<Checkbox
|
||||
{...props.isCompletedOnlyCheckboxProps}
|
||||
label={"Только завершенные сделки"}
|
||||
/>
|
||||
{getTransactionTagsSelect(false)}
|
||||
{getTransactionTagsSelect(true)}
|
||||
{props.groupTableByProps &&
|
||||
@@ -118,6 +148,8 @@ export const Filters = (props: FiltersProps) => {
|
||||
<Divider />
|
||||
<Text>Группировка таблицы:</Text>
|
||||
<ProfitTableSegmentedControl
|
||||
selectedProject={props.projectSelectProps?.value ?? null}
|
||||
selectedBoard={props.boardSelectProps?.value ?? null}
|
||||
{...props.groupTableByProps}
|
||||
orientation={"vertical"}
|
||||
size={"md"}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user