feat: cards, attributes and modules

This commit is contained in:
2025-02-19 14:46:13 +04:00
parent cc3e72bf94
commit dc9455966e
286 changed files with 2355 additions and 2168 deletions

View File

@@ -0,0 +1 @@
export { CardPage } from "./ui/CardPage.tsx";

View File

@@ -0,0 +1,38 @@
import { useParams } from "@tanstack/react-router";
import { CardPageContextProvider, useCardPageContext } from "../../CardsPage/contexts/CardPageContext.tsx";
import ProductAndServiceTab from "../../CardsPage/tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
import React, { FC, useEffect } from "react";
import { CardService } from "../../../client";
export type Props = {
cardId: number;
};
const CardPageContent: FC<Props> = ({ cardId }) => {
const { setSelectedCard } = useCardPageContext();
useEffect(() => {
CardService.getCardById({ cardId }).then(card => {
setSelectedCard(card);
});
}, []);
return <ProductAndServiceTab />;
};
const CardPageWrapper: FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<CardPageContextProvider
refetchCards={async () => {
}}
>
{children}
</CardPageContextProvider>
);
};
export const CardPage = () => {
const { dealId } = useParams({ strict: false });
return (
<CardPageWrapper>
<CardPageContent cardId={parseInt(dealId || "-1")} />
</CardPageWrapper>
);
};

View File

@@ -9,7 +9,7 @@ import {
rem,
} from "@mantine/core";
import { BaseFormInputProps } from "../../../../types/utils.ts";
import { DealProductServiceSchema, ServiceSchema } from "../../../../client";
import { CardProductServiceSchema, ServiceSchema } from "../../../../client";
import { FC, useEffect, useState } from "react";
import ServiceWithPriceInput from "../../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
import { isNumber } from "lodash";
@@ -21,13 +21,13 @@ import { RootState } from "../../../../redux/store.ts";
type RestProps = {
quantity: number;
};
type Props = BaseFormInputProps<DealProductServiceSchema[]> & RestProps;
type Props = BaseFormInputProps<CardProductServiceSchema[]> & RestProps;
const DealProductServiceTable: FC<Props> = (props: Props) => {
const { value, onChange, quantity, error } = props;
const authState = useSelector((state: RootState) => state.auth);
const [innerValue, setInnerValue] = useState<
Partial<DealProductServiceSchema>[]
Partial<CardProductServiceSchema>[]
>(value || []);
const onServiceChange = (idx: number, value: ServiceSchema) => {
setInnerValue(oldValue =>
@@ -79,7 +79,7 @@ const DealProductServiceTable: FC<Props> = (props: Props) => {
});
};
useEffect(() => {
onChange(innerValue as DealProductServiceSchema[]);
onChange(innerValue as CardProductServiceSchema[]);
}, [innerValue]);
return (
<Input.Wrapper error={error}>

View File

@@ -2,7 +2,7 @@ import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import useDealProductsTableColumns from "./columns.tsx";
import { FC } from "react";
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
import { DealProductSchema, ProductService } from "../../../../client";
import { CardProductSchema, ProductService } from "../../../../client";
import { ActionIcon, Button, Flex, rem, Tooltip } from "@mantine/core";
import { MRT_TableOptions } from "mantine-react-table";
import { modals } from "@mantine/modals";
@@ -12,9 +12,9 @@ import { CreateProductRequest } from "../../../ProductsPage/types.ts";
type RestProps = {
clientId: number;
onMultipleDelete?: (items: DealProductSchema[]) => void;
onMultipleDelete?: (items: CardProductSchema[]) => void;
};
type Props = CRUDTableProps<DealProductSchema> & RestProps;
type Props = CRUDTableProps<CardProductSchema> & RestProps;
const DealProductsTable: FC<Props> = (props: Props) => {
const { items, clientId, onChange, onCreate, onDelete, onMultipleDelete } =
props;
@@ -34,16 +34,16 @@ const DealProductsTable: FC<Props> = (props: Props) => {
const onCreateClick = () => {
if (!onCreate) return;
modals.openContextModal({
modal: "addDealProduct",
modal: "addCardProduct",
title: "Добавление товара",
innerProps: {
onCreate: product => onCreate(product as DealProductSchema),
onCreate: product => onCreate(product as CardProductSchema),
clientId,
},
size: "lg",
});
};
const onPrintBarcodeClick = (product: DealProductSchema) => {
const onPrintBarcodeClick = (product: CardProductSchema) => {
modals.openContextModal({
modal: "printBarcode",
title: "Печать штрихкода",
@@ -72,10 +72,10 @@ const DealProductsTable: FC<Props> = (props: Props) => {
},
});
};
const onEditClick = (product: DealProductSchema) => {
const onEditClick = (product: CardProductSchema) => {
if (!onChange) return;
modals.openContextModal({
modal: "addDealProduct",
modal: "addCardProduct",
title: "Создание товара",
withCloseButton: false,
innerProps: {
@@ -159,7 +159,7 @@ const DealProductsTable: FC<Props> = (props: Props) => {
</Button>
</Flex>
),
} as MRT_TableOptions<DealProductSchema>
} as MRT_TableOptions<CardProductSchema>
}
/>
);

View File

@@ -1,11 +1,11 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealProductSchema } from "../../../../client";
import { CardProductSchema } from "../../../../client";
import { List } from "@mantine/core";
type Props = {
onChange: (product: DealProductSchema, quantity: number) => void;
data: DealProductSchema[];
onChange: (product: CardProductSchema, quantity: number) => void;
data: CardProductSchema[];
};
const useDealProductsTableColumns = (props: Props) => {
const { onChange, data } = props;
@@ -27,7 +27,7 @@ const useDealProductsTableColumns = (props: Props) => {
),
[data]
);
return useMemo<MRT_ColumnDef<DealProductSchema>[]>(
return useMemo<MRT_ColumnDef<CardProductSchema>[]>(
() => [
{
accessorKey: "product.article",

View File

@@ -1,7 +1,7 @@
import { FC } from "react";
import { useDealServicesTableColumns } from "./columns.tsx";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import { DealServiceSchema } from "../../../../client";
import { CardServiceSchema } from "../../../../client";
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
import { MRT_TableOptions } from "mantine-react-table";
import { ActionIcon, Button, Flex, rem, Tooltip } from "@mantine/core";
@@ -9,9 +9,9 @@ import { openContextModal } from "@mantine/modals";
import { IconEdit, IconTrash } from "@tabler/icons-react";
type RestProps = {
onMultipleDelete?: (items: DealServiceSchema[]) => void;
onMultipleDelete?: (items: CardServiceSchema[]) => void;
};
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
type Props = CRUDTableProps<CardServiceSchema> & RestProps;
const DealServicesTable: FC<Props> = ({
items,
onChange,
@@ -30,18 +30,18 @@ const DealServicesTable: FC<Props> = ({
if (!onCreate) return;
openContextModal({
title: "Добавление услуги",
modal: "addDealService",
modal: "addCardService",
innerProps: {
onCreate: event => onCreate(event as DealServiceSchema),
onCreate: event => onCreate(event as CardServiceSchema),
serviceIds,
},
});
};
const onEditClick = (service: DealServiceSchema) => {
const onEditClick = (service: CardServiceSchema) => {
if (!onChange) return;
openContextModal({
title: "Добавление услуги",
modal: "addDealService",
modal: "addCardService",
innerProps: {
element: service,
onChange,
@@ -119,7 +119,7 @@ const DealServicesTable: FC<Props> = ({
</Tooltip>
</Flex>
),
} as MRT_TableOptions<DealServiceSchema>
} as MRT_TableOptions<CardServiceSchema>
}
/>
</>

View File

@@ -1,9 +1,9 @@
import { MRT_ColumnDef } from "mantine-react-table";
import { useMemo } from "react";
import { DealServiceSchema } from "../../../../client";
import { CardServiceSchema } from "../../../../client";
type Props = {
data: DealServiceSchema[];
data: CardServiceSchema[];
};
export const useDealServicesTableColumns = (props: Props) => {
@@ -13,7 +13,7 @@ export const useDealServicesTableColumns = (props: Props) => {
[data]
);
return useMemo<MRT_ColumnDef<DealServiceSchema>[]>(
return useMemo<MRT_ColumnDef<CardServiceSchema>[]>(
() => [
{
accessorKey: "service.category",

View File

@@ -1,13 +1,13 @@
import { DealStatusHistorySchema } from "../../../../client";
import { CardStatusHistorySchema } from "../../../../client";
import { useDealStatusChangeTableColumns } from "./columns.tsx";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import { FC } from "react";
type Props = {
items: DealStatusHistorySchema[];
items: CardStatusHistorySchema[];
};
const DealStatusChangeTable: FC<Props> = (props: Props) => {
const CardStatusChangeTable: FC<Props> = (props: Props) => {
const { items } = props;
return (
@@ -26,4 +26,4 @@ const DealStatusChangeTable: FC<Props> = (props: Props) => {
/>
);
};
export default DealStatusChangeTable;
export default CardStatusChangeTable;

View File

@@ -1,10 +1,10 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealStatusHistorySchema } from "../../../../client";
import { CardStatusHistorySchema } from "../../../../client";
import { Spoiler, Text } from "@mantine/core";
export const useDealStatusChangeTableColumns = () => {
return useMemo<MRT_ColumnDef<DealStatusHistorySchema>[]>(
return useMemo<MRT_ColumnDef<CardStatusHistorySchema>[]>(
() => [
{
accessorKey: "changedAt",

View File

@@ -1,24 +1,24 @@
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
import { DealService, DealSummary } from "../../../../client";
import { CardService, CardSummary } from "../../../../client";
import { FC } from "react";
import useDealsTableColumns from "./columns.tsx";
import useCardsTableColumns from "./columns.tsx";
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 "../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
type RestProps = {
viewOnly?: boolean;
};
type Props = CRUDTableProps<DealSummary> & RestProps;
type Props = CRUDTableProps<CardSummary> & RestProps;
const DealsTable: FC<Props> = ({ items, onSelectionChange, viewOnly = false }) => {
const columns = useDealsTableColumns();
const { setSelectedDeal } = useDealPageContext();
const onEditClick = (dealSummary: DealSummary) => {
DealService.getDealById({ dealId: dealSummary.id }).then(deal => {
setSelectedDeal(deal);
const CardsTable: FC<Props> = ({ items, onSelectionChange, viewOnly = false }) => {
const columns = useCardsTableColumns();
const { setSelectedCard } = useCardPageContext();
const onEditClick = (cardSummary: CardSummary) => {
CardService.getCardById({ cardId: cardSummary.id }).then(card => {
setSelectedCard(card);
});
};
@@ -48,10 +48,10 @@ const DealsTable: FC<Props> = ({ items, onSelectionChange, viewOnly = false }) =
</Tooltip>
</Flex>
),
} as MRT_TableOptions<DealSummary>
} as MRT_TableOptions<CardSummary>
}
/>
);
};
export default DealsTable;
export default CardsTable;

View File

@@ -1,10 +1,10 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealSummary } from "../../../../client";
import { CardSummary } from "../../../../client";
import { ActionIcon, Image } from "@mantine/core";
const useDealsTableColumns = () => {
return useMemo<MRT_ColumnDef<DealSummary>[]>(
const useCardsTableColumns = () => {
return useMemo<MRT_ColumnDef<CardSummary>[]>(
() => [
{
accessorKey: "id",
@@ -53,4 +53,4 @@ const useDealsTableColumns = () => {
);
};
export default useDealsTableColumns;
export default useCardsTableColumns;

View File

@@ -1,21 +1,21 @@
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 styles from "../../ui/CardsPage.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 { CardsPageState } from "../../hooks/useCardsPageState.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";
import CardsTableFiltersModal from "../../modals/CardsTableFiltersModal.tsx";
type Props = {
displayMode: DisplayMode;
setDisplayMode: React.Dispatch<React.SetStateAction<DisplayMode>>;
form: UseFormReturnType<DealsPageState>;
form: UseFormReturnType<CardsPageState>;
projects: ProjectSchema[];
refetchProjects: () => void;
}
@@ -66,7 +66,7 @@ const LeadsPageHeader = ({
display: displayMode === DisplayMode.TABLE ? "flex" : "none",
}}
>
<DealsTableFiltersModal
<CardsTableFiltersModal
form={form}
projects={projects}
/>

View File

@@ -0,0 +1,75 @@
import React, { createContext, FC, useContext, useEffect, useState } from "react";
import { CardSchema, CardService, ProjectSchema } from "../../../client";
type CardPageContextState = {
selectedCard?: CardSchema;
setSelectedCard: (card: CardSchema | undefined) => void;
refetchCards: () => Promise<void>;
refetchCard: () => void;
selectedProject?: ProjectSchema | null;
};
const CardPageContext = createContext<CardPageContextState | undefined>(
undefined,
);
type CardPageContextStateProps = {
refetchCards: () => Promise<void>;
defaultCardId?: number;
selectedProject?: ProjectSchema | null;
}
const useCardPageContextState = (props: CardPageContextStateProps) => {
const { refetchCards, defaultCardId } = props;
const [selectedCard, setSelectedCard] = useState<CardSchema | undefined>(
undefined,
);
const refetchCard = () => {
const cardId = selectedCard?.id ?? defaultCardId;
if (!cardId) return;
CardService.getCardById({ cardId }).then(card => {
setSelectedCard(card);
});
};
useEffect(() => {
refetchCard();
}, []);
return {
selectedCard,
setSelectedCard,
selectedProject: props.selectedProject,
refetchCards,
refetchCard,
};
};
type CardPageContextProviderProps = {
children: React.ReactNode;
} & CardPageContextStateProps;
export const CardPageContextProvider: FC<CardPageContextProviderProps> = ({
children,
...props
}) => {
const state = useCardPageContextState(props);
return (
<CardPageContext.Provider value={state}>
{children}
</CardPageContext.Provider>
);
};
export const useCardPageContext = () => {
const context = useContext(CardPageContext);
if (!context) {
throw new Error(
"useCardPageContext must be used within a CardPageContextProvider",
);
}
return context;
};

View File

@@ -0,0 +1,71 @@
import { createContext, Dispatch, FC, SetStateAction, useContext, useState } from "react";
import { CardSchema, CardService } from "../../../client";
import { useDisclosure } from "@mantine/hooks";
type PrefillCardContextState = {
prefillOpened: boolean;
prefillOnClose: () => void;
prefillOnOpen: () => void;
selectedPrefillCard?: CardSchema;
selectPrefillCard: (cardId: number) => void;
prefillCard?: CardSchema;
setPrefillCard: Dispatch<SetStateAction<CardSchema | undefined>>;
};
const PrefillCardContext = createContext<PrefillCardContextState | undefined>(
undefined
);
const usePrefillCardContextState = () => {
const [selectedPrefillCard, setSelectedPrefillCard] = useState<CardSchema | undefined>(
undefined,
);
const [prefillCard, setPrefillCard] = useState<CardSchema | undefined>(
undefined,
);
const [prefillOpened, { open, close }] = useDisclosure(false);
const prefillOnClose = close;
const prefillOnOpen = open;
const selectPrefillCard = (cardId: number) => {
CardService.getCardById({ cardId }).then(card => {
setSelectedPrefillCard(card);
});
}
return {
prefillOpened,
prefillOnClose,
prefillOnOpen,
selectedPrefillCard,
selectPrefillCard,
prefillCard,
setPrefillCard,
};
};
type PrefillCardContextProviderProps = {
children: React.ReactNode;
};
export const PrefillCardContextProvider: FC<PrefillCardContextProviderProps> = ({
children,
}) => {
const state = usePrefillCardContextState();
return (
<PrefillCardContext.Provider value={state}>
{children}
</PrefillCardContext.Provider>
);
};
export const usePrefillCardContext = () => {
const context = useContext(PrefillCardContext);
if (!context) {
throw new Error(
"usePrefillCardContext must be used within a PrefillCardContextProvider"
);
}
return context;
};

View File

@@ -1,14 +1,14 @@
import { createContext, FC, useContext, useState } from "react";
import React, { createContext, FC, useContext, useState } from "react";
import { useDisclosure } from "@mantine/hooks";
import { DealsWithExcelForm, ProductExcelData } from "../drawers/PrefillDealWithExcelDrawer/types.tsx";
import { CardsWithExcelForm, ProductExcelData } from "../drawers/PrefillCardWithExcelDrawer/types.tsx";
import { FileWithPath } from "@mantine/dropzone";
import { notifications } from "../../../shared/lib/notifications.ts";
import { DealService, type ProductFromExcelSchema, ProductSchema, StatusSchema } from "../../../client";
import { CardService, type ProductFromExcelSchema, ProductSchema, StatusSchema } from "../../../client";
import UseExcelDropzone from "../../../types/UseExcelDropzone.tsx";
import { useForm, UseFormReturnType } from "@mantine/form";
import { useDealPageContext } from "./DealPageContext.tsx";
import { useCardPageContext } from "./CardPageContext.tsx";
type PrefillDealsWithExcelContextState = {
type PrefillCardsWithExcelContextState = {
prefillWithExcelOpened: boolean;
prefillWithExcelOnClose: () => void;
prefillWithExcelOnOpen: () => void;
@@ -16,18 +16,18 @@ type PrefillDealsWithExcelContextState = {
onProductSelectChange: (barcode: string, selectedProduct: ProductSchema) => void,
onDrop: (files: FileWithPath[]) => void;
excelDropzone: UseExcelDropzone;
createDeals: (values: DealsWithExcelForm, status: StatusSchema) => void;
form: UseFormReturnType<DealsWithExcelForm>;
createCards: (values: CardsWithExcelForm, status: StatusSchema) => void;
form: UseFormReturnType<CardsWithExcelForm>;
errors: string[];
};
const PrefillDealsWithExcelContext = createContext<PrefillDealsWithExcelContextState | undefined>(
const PrefillCardsWithExcelContext = createContext<PrefillCardsWithExcelContextState | undefined>(
undefined,
);
const usePrefillDealsWithExcelContextState = () => {
const usePrefillCardsWithExcelContextState = () => {
const [prefillWithExcelOpened, { open, close }] = useDisclosure(false);
const { refetchDeals } = useDealPageContext();
const { refetchCards } = useCardPageContext();
const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState<string[]>([]);
const excelDropzone: UseExcelDropzone = {
@@ -35,7 +35,7 @@ const usePrefillDealsWithExcelContextState = () => {
setIsLoading,
};
const form = useForm<DealsWithExcelForm>({
const form = useForm<CardsWithExcelForm>({
validate: {
client: client => !client && "Выберите клиента",
},
@@ -51,7 +51,7 @@ const usePrefillDealsWithExcelContextState = () => {
const file = files[0];
setIsLoading(true);
DealService.parseDealsExcel({
CardService.parseDealsExcel({
formData: {
upload_file: file,
},
@@ -91,7 +91,7 @@ const usePrefillDealsWithExcelContextState = () => {
form.reset();
};
const createDeals = (values: DealsWithExcelForm, status: StatusSchema) => {
const createCards = (values: CardsWithExcelForm, status: StatusSchema) => {
const products: ProductFromExcelSchema[] = barcodeProductsMap.entries().map(([, productData]) => {
return {
productId: productData.selectedProduct!.id,
@@ -99,7 +99,7 @@ const usePrefillDealsWithExcelContextState = () => {
};
}).toArray();
DealService.createDealsExcel({
CardService.createDealsExcel({
requestBody: {
products,
clientId: values.client?.id ?? -1,
@@ -109,7 +109,7 @@ const usePrefillDealsWithExcelContextState = () => {
.then(({ ok, message }) => {
notifications.guess(ok, { message });
if (ok) prefillWithExcelOnClose();
refetchDeals();
refetchCards();
})
.catch(err => console.log(err));
};
@@ -122,30 +122,30 @@ const usePrefillDealsWithExcelContextState = () => {
onProductSelectChange,
onDrop,
excelDropzone,
createDeals,
createCards,
form,
errors,
};
};
type PrefillDealsWithExcelContextProviderProps = {
type PrefillCardsWithExcelContextProviderProps = {
children: React.ReactNode;
};
export const PrefillDealsWithExcelContextProvider: FC<PrefillDealsWithExcelContextProviderProps> = ({ children }) => {
const state = usePrefillDealsWithExcelContextState();
export const PrefillCardsWithExcelContextProvider: FC<PrefillCardsWithExcelContextProviderProps> = ({ children }) => {
const state = usePrefillCardsWithExcelContextState();
return (
<PrefillDealsWithExcelContext.Provider value={state}>
<PrefillCardsWithExcelContext.Provider value={state}>
{children}
</PrefillDealsWithExcelContext.Provider>
</PrefillCardsWithExcelContext.Provider>
);
};
export const usePrefillDealsWithExcelContext = () => {
const context = useContext(PrefillDealsWithExcelContext);
export const usePrefillCardsWithExcelContext = () => {
const context = useContext(PrefillCardsWithExcelContext);
if (!context) {
throw new Error(
"usePrefillDealsWithExcelContext must be used within a PrefillDealsWithExcelContextProvider",
"usePrefillCardsWithExcelContext must be used within a PrefillCardsWithExcelContextProvider",
);
}
return context;

View File

@@ -1,43 +1,47 @@
import { Box, Drawer, rem, Tabs } from "@mantine/core";
import { FC, ReactNode, useEffect } from "react";
import { useDealPageContext } from "../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
import { IconBox, IconCalendarUser, IconCubeSend, IconSettings, IconUser, IconUsersGroup } from "@tabler/icons-react";
import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealStatusChangeTable.tsx";
import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx";
import CardStatusChangeTable from "../../components/DealStatusChangeTable/CardStatusChangeTable.tsx";
import GeneralTab from "../../tabs/GeneralTab/GeneralTab.tsx";
import { useQueryClient } from "@tanstack/react-query";
import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
import { motion } from "framer-motion";
import ShippingTab from "../../tabs/ShippingTab/ShippingTab.tsx";
import EmployeesTab from "../../tabs/EmployeesTab/EmployeesTab.tsx";
import ClientTab from "./tabs/ClientTab.tsx";
import ClientTab from "../../tabs/ClientTab/ClientTab.tsx";
const useDealStatusChangeState = () => {
const { selectedDeal } = useDealPageContext();
const useCardStatusChangeState = () => {
const { selectedCard } = useCardPageContext();
return {
statusHistory: selectedDeal?.statusHistory || [],
statusHistory: selectedCard?.statusHistory || [],
};
};
const DealEditDrawerStatusChangeTable = () => {
const { statusHistory } = useDealStatusChangeState();
const CardEditDrawerStatusChangeTable = () => {
const { statusHistory } = useCardStatusChangeState();
return <DealStatusChangeTable items={statusHistory} />;
return <CardStatusChangeTable items={statusHistory} />;
};
const useDealEditDrawerState = () => {
const { selectedDeal, setSelectedDeal } = useDealPageContext();
const { selectedCard, setSelectedCard } = useCardPageContext();
return {
isVisible: selectedDeal !== undefined,
onClose: () => setSelectedDeal(undefined),
isVisible: selectedCard !== undefined,
onClose: () => setSelectedCard(undefined),
};
};
const DealEditDrawer: FC = () => {
const CardEditDrawer: FC = () => {
const { isVisible, onClose } = useDealEditDrawerState();
const queryClient = useQueryClient();
const { selectedCard } = useCardPageContext();
const modules = new Set<string>(selectedCard?.board.project.modules.map(module => module.key));
useEffect(() => {
if (isVisible) return;
queryClient.invalidateQueries({ queryKey: ["getDealSummaries"] });
queryClient.invalidateQueries({ queryKey: ["getCardSummaries"] });
}, [isVisible]);
const getTabPanel = (value: string, component: ReactNode) => {
@@ -58,6 +62,18 @@ const DealEditDrawer: FC = () => {
);
};
const getTab = (key: string, icon: ReactNode, label: string) => {
if (!modules.has(key)) return;
return (
<Tabs.Tab
value={key}
leftSection={icon}>
{label}
</Tabs.Tab>
);
};
return (
<Drawer
size={"calc(100vw - 150px)"}
@@ -96,25 +112,14 @@ const DealEditDrawer: FC = () => {
leftSection={<IconCalendarUser />}>
История
</Tabs.Tab>
<Tabs.Tab
value={"servicesAndProducts"}
leftSection={<IconBox />}>
Товары и услуги
</Tabs.Tab>
<Tabs.Tab
value={"shipment"}
leftSection={<IconCubeSend />}>
Отгрузка
</Tabs.Tab>
<Tabs.Tab
value={"employees"}
leftSection={<IconUsersGroup />}>
Исполнители
</Tabs.Tab>
{getTab("servicesAndProducts", <IconBox />, "Товары и услуги")}
{getTab("shipment", <IconCubeSend />, "Отгрузка")}
{getTab("employees", <IconUsersGroup />, "Исполнители")}
</Tabs.List>
{getTabPanel("general", <DealEditDrawerGeneralTab />)}
{getTabPanel("general", <GeneralTab />)}
{getTabPanel("client", <ClientTab />)}
{getTabPanel("history", <DealEditDrawerStatusChangeTable />)}
{getTabPanel("history", <CardEditDrawerStatusChangeTable />)}
{getTabPanel("servicesAndProducts", <ProductAndServiceTab />)}
{getTabPanel("shipment", <ShippingTab />)}
{getTabPanel("employees", <EmployeesTab />)}
@@ -123,4 +128,4 @@ const DealEditDrawer: FC = () => {
);
};
export default DealEditDrawer;
export default CardEditDrawer;

View File

@@ -2,14 +2,14 @@ import { FC, useEffect } from "react";
import { Button, Drawer, Flex, rem, TextInput } from "@mantine/core";
import DealsTable from "./components/tables/DealsTable/DealsTable.tsx";
import Preview from "./components/Preview/Preview.tsx";
import styles from "./DealPrefillDrawer.module.css";
import styles from "./CardPrefillDrawer.module.css";
import BaseMarketplaceSelect from "../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import usePrefillDeal from "./hooks/usePrefillDeal.tsx";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { usePrefillDealContext } from "../../contexts/PrefillDealContext.tsx";
import { usePrefillCardContext } from "../../contexts/PrefillCardContext.tsx";
const DealPrefillDrawer: FC = () => {
const { prefillOpened, prefillOnClose, selectedPrefillDeal, setPrefillDeal, prefillDeal } = usePrefillDealContext();
const CardPrefillDrawer: FC = () => {
const { prefillOpened, prefillOnClose, selectedPrefillCard, setPrefillCard, prefillCard } = usePrefillCardContext();
const { data, form } = usePrefillDeal();
useEffect(() => {
@@ -52,19 +52,19 @@ const DealPrefillDrawer: FC = () => {
<DealsTable items={data} />
<Flex direction={"row"} gap="sm">
<Button mt={10} w={"100%"} onClick={() => {
if (!selectedPrefillDeal) {
notifications.error({ message: "Сделка не выбрана." });
if (!selectedPrefillCard) {
notifications.error({ message: "Карточка не выбрана." });
return;
}
setPrefillDeal(selectedPrefillDeal);
setPrefillCard(selectedPrefillCard);
prefillOnClose();
}}>
Предзаполнить
</Button>
{
prefillDeal &&
prefillCard &&
<Button mt={10} w={"100%"} variant={"outline"} onClick={() => {
setPrefillDeal(undefined);
setPrefillCard(undefined);
notifications.success({ message: "Предзаполнение отменено." });
prefillOnClose();
}}>
@@ -79,4 +79,4 @@ const DealPrefillDrawer: FC = () => {
);
};
export default DealPrefillDrawer;
export default CardPrefillDrawer;

View File

@@ -3,14 +3,14 @@ import styles from "./Preview.module.css";
import { ScrollArea, Skeleton, Title } from "@mantine/core";
import DealServicesTable from "../tables/DealServicesTable/DealServicesTable.tsx";
import ProductPreview from "../ProductPreview/ProductPreview.tsx";
import { usePrefillDealContext } from "../../../../contexts/PrefillDealContext.tsx";
import { usePrefillCardContext } from "../../../../contexts/PrefillCardContext.tsx";
const Preview: FC = () => {
const { selectedPrefillDeal } = usePrefillDealContext();
const { selectedPrefillCard } = usePrefillCardContext();
const getTotalPrice = () => {
if (!selectedPrefillDeal) return 0;
const productServicesPrice = selectedPrefillDeal.products.reduce(
if (!selectedPrefillCard) return 0;
const productServicesPrice = selectedPrefillCard.products.reduce(
(acc, row) =>
acc +
row.services.reduce(
@@ -19,11 +19,11 @@ const Preview: FC = () => {
),
0,
);
const dealServicesPrice = selectedPrefillDeal.services.reduce(
const cardServicesPrice = selectedPrefillCard.services.reduce(
(acc, row) => acc + row.price * row.quantity,
0,
);
return dealServicesPrice + productServicesPrice;
return cardServicesPrice + productServicesPrice;
};
return (
@@ -31,16 +31,16 @@ const Preview: FC = () => {
<div className={styles["deal-container-wrapper"]}>
<ScrollArea offsetScrollbars={"y"} w={"100%"}>
<div style={{ height: "93vh" }}>
<Skeleton visible={!selectedPrefillDeal}>
<Skeleton visible={!selectedPrefillCard}>
<Title order={4} mb={18}>
Общая стоимость всех услуг:{" "}
{getTotalPrice().toLocaleString("ru")}
</Title>
<DealServicesTable items={selectedPrefillDeal?.services} />
<DealServicesTable items={selectedPrefillCard?.services} />
<div className={styles["products-list"]}>
{selectedPrefillDeal?.products.map(product => (
{selectedPrefillCard?.products.map(product => (
<ProductPreview
key={product.product.id}
product={product}

View File

@@ -1,5 +1,5 @@
import { FC } from "react";
import { DealProductSchema, ProductSchema } from "../../../../../../client";
import { CardProductSchema, ProductSchema } from "../../../../../../client";
import { Image, rem, Text, Title } from "@mantine/core";
import { isNil } from "lodash";
import { ProductFieldNames } from "../../../../tabs/ProductAndServiceTab/components/ProductView/ProductView.tsx";
@@ -7,7 +7,7 @@ import ProductServicesTable from "../tables/ProductServicesTable/ProductServices
import styles from "./ProductPreview.module.css";
type Props = {
product: DealProductSchema;
product: CardProductSchema;
};
const ProductPreview: FC<Props> = ({ product }) => {

View File

@@ -1,12 +1,12 @@
import { FC } from "react";
import { Flex, rem, Title } from "@mantine/core";
import { DealServiceSchema, DealSummary } from "../../../../../../../client";
import { CardServiceSchema, CardSummary } from "../../../../../../../client";
import useDealServicesTableColumns from "./columns.tsx";
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
import { MRT_TableOptions } from "mantine-react-table";
type Props = {
items?: DealServiceSchema[];
items?: CardServiceSchema[];
};
const DealServicesTable: FC<Props> = ({ items }) => {
@@ -33,7 +33,7 @@ const DealServicesTable: FC<Props> = ({ items }) => {
enableColumnActions: false,
enablePagination: false,
enableBottomToolbar: false,
} as MRT_TableOptions<DealSummary>
} as MRT_TableOptions<CardSummary>
}
/>

View File

@@ -1,9 +1,9 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealServiceSchema } from "../../../../../../../client";
import { CardServiceSchema } from "../../../../../../../client";
const useDealServicesTableColumns = () => {
return useMemo<MRT_ColumnDef<DealServiceSchema>[]>(
return useMemo<MRT_ColumnDef<CardServiceSchema>[]>(
() => [
{
header: "Название",

View File

@@ -1,21 +1,21 @@
import { FC, useEffect } from "react";
import useDealsTableColumns from "./columns.tsx";
import { DealSummary } from "../../../../../../../client";
import { CardSummary } from "../../../../../../../client";
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
import { usePrefillDealContext } from "../../../../../contexts/PrefillDealContext.tsx";
import { usePrefillCardContext } from "../../../../../contexts/PrefillCardContext.tsx";
type Props = {
items: DealSummary[];
items: CardSummary[];
};
const DealsTable: FC<Props> = ({ items }) => {
const { selectPrefillDeal } = usePrefillDealContext();
const { selectPrefillCard } = usePrefillCardContext();
const columns = useDealsTableColumns();
const defaultSorting = [{ id: "createdAt", desc: false }];
useEffect(() => {
if (items.length < 1) return;
selectPrefillDeal(items[0].id);
selectPrefillCard(items[0].id);
}, []);
return (

View File

@@ -1,11 +1,11 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { ActionIcon, Image, Radio } from "@mantine/core";
import { DealSummary } from "../../../../../../../client";
import { usePrefillDealContext } from "../../../../../contexts/PrefillDealContext.tsx";
import { CardSummary } from "../../../../../../../client";
import { usePrefillCardContext } from "../../../../../contexts/PrefillCardContext.tsx";
const useDealsTableColumns = () => {
return useMemo<MRT_ColumnDef<DealSummary>[]>(
return useMemo<MRT_ColumnDef<CardSummary>[]>(
() => [
{
accessorKey: "select",
@@ -13,13 +13,13 @@ const useDealsTableColumns = () => {
size: 5,
enableSorting: false,
Cell: ({ row }) => {
const { selectPrefillDeal, selectedPrefillDeal } = usePrefillDealContext();
const checked = row.original.id === selectedPrefillDeal?.id;
const { selectPrefillCard, selectedPrefillCard } = usePrefillCardContext();
const checked = row.original.id === selectedPrefillCard?.id;
return (
<Radio
checked={checked}
onChange={() => {
selectPrefillDeal(row.original.id);
selectPrefillCard(row.original.id);
}}
/>
);

View File

@@ -1,12 +1,12 @@
import { FC } from "react";
import { MRT_TableOptions } from "mantine-react-table";
import { DealProductServiceSchema } from "../../../../../../../client";
import { CardProductServiceSchema } from "../../../../../../../client";
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
import useProductServicesTableColumns from "./columns.tsx";
type Props = {
items: DealProductServiceSchema[];
items: CardProductServiceSchema[];
quantity: number;
};
@@ -23,7 +23,7 @@ const ProductServicesTable: FC<Props> = ({ items, quantity }) => {
enableSorting: false,
enableRowActions: false,
enableBottomToolbar: false,
} as MRT_TableOptions<DealProductServiceSchema>
} as MRT_TableOptions<CardProductServiceSchema>
}
/>
);

View File

@@ -1,11 +1,11 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealProductServiceSchema } from "../../../../../../../client";
import { CardProductServiceSchema } from "../../../../../../../client";
import { useSelector } from "react-redux";
import { RootState } from "../../../../../../../redux/store.ts";
type Props = {
data: DealProductServiceSchema[];
data: CardProductServiceSchema[];
quantity: number;
};
@@ -17,7 +17,7 @@ const useProductServicesTableColumns = (props: Props) => {
[data, quantity]
);
const hideGuestColumns = ["service.cost"];
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(
return useMemo<MRT_ColumnDef<CardProductServiceSchema>[]>(
() => [
{
accessorKey: "service.name",

View File

@@ -1,7 +1,7 @@
import { useForm } from "@mantine/form";
import { useEffect, useState } from "react";
import { BaseMarketplaceSchema } from "../../../../../client";
import { useDealSummariesFull } from "../../../hooks/useDealSummaries.tsx";
import { useCardSummariesFull } from "../../../hooks/useCardSummaries.tsx";
type State = {
idOrName: string | null;
@@ -9,7 +9,7 @@ type State = {
};
const usePrefillDeal = () => {
const { objects } = useDealSummariesFull();
const { objects } = useCardSummariesFull();
const form = useForm<State>({
initialValues: {
idOrName: null,

View File

@@ -1,7 +1,7 @@
import { Drawer, rem } from "@mantine/core";
import ExcelDropzone from "../../../../components/ExcelDropzone/ExcelDropzone.tsx";
import styles from "../PrefillDealWithExcelDrawer/PrefillDealsWithExcelDrawer.module.css";
import { usePrefillDealsWithExcelContext } from "../../contexts/PrefillDealsWithExcelContext.tsx";
import styles from "./PrefillCardsWithExcelDrawer.module.css";
import { usePrefillCardsWithExcelContext } from "../../contexts/PrefillDealsWithExcelContext.tsx";
import ProductsPreview from "./components/ProductsPreview.tsx";
import { BoardSchema } from "../../../../client";
@@ -9,22 +9,22 @@ type Props = {
board: BoardSchema | null;
}
const PrefillDealsWithExcelDrawer = ({ board }: Props) => {
const PrefillCardsWithExcelDrawer = ({ board }: Props) => {
const {
prefillWithExcelOpened,
prefillWithExcelOnClose,
barcodeProductsMap,
onDrop,
excelDropzone,
} = usePrefillDealsWithExcelContext();
} = usePrefillCardsWithExcelContext();
const getBody = () => {
if (!board || board.dealStatuses.length === 0) return;
if (!board || board.statuses.length === 0) return;
if (barcodeProductsMap?.size === 0) {
return <ExcelDropzone dropzone={excelDropzone} onDrop={onDrop} />;
}
return <ProductsPreview status={board.dealStatuses[0]}/>;
return <ProductsPreview status={board.statuses[0]}/>;
};
return (
@@ -51,4 +51,4 @@ const PrefillDealsWithExcelDrawer = ({ board }: Props) => {
);
};
export default PrefillDealsWithExcelDrawer;
export default PrefillCardsWithExcelDrawer;

View File

@@ -1,9 +1,9 @@
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { usePrefillCardsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { Text, Tooltip } from "@mantine/core";
import { IconAlertCircle, IconCircleCheck } from "@tabler/icons-react";
const ParsingResultsTooltip = () => {
const { errors } = usePrefillDealsWithExcelContext();
const { errors } = usePrefillCardsWithExcelContext();
const isError = errors.length !== 0;
const errorLines = errors.map((error, i) => <Text key={i}>{error}</Text>);

View File

@@ -1,6 +1,6 @@
import styles from "../PrefillDealsWithExcelDrawer.module.css";
import styles from "../PrefillCardsWithExcelDrawer.module.css";
import ProductsTable from "./ProductsTable.tsx";
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { usePrefillCardsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { Box, Button, Flex, Group, Stack, Title } from "@mantine/core";
import { ProductExcelData } from "../types.tsx";
import BreakdownByCityTable from "./BreakdownByCityTable.tsx";
@@ -13,7 +13,7 @@ type Props = {
}
const ProductsPreview = ({ status }: Props) => {
const { barcodeProductsMap, createDeals, form } = usePrefillDealsWithExcelContext();
const { barcodeProductsMap, createCards, form } = usePrefillCardsWithExcelContext();
const getTitle = (barcode: string, productsData: ProductExcelData) => {
if (productsData.products.length === 1) {
@@ -46,7 +46,7 @@ const ProductsPreview = ({ status }: Props) => {
return (
<Stack gap={"md"}>
<Title order={3}>Предпросмотр</Title>
<form onSubmit={form.onSubmit((values) => createDeals(values, status))}>
<form onSubmit={form.onSubmit((values) => createCards(values, status))}>
<ClientSelect
{...form.getInputProps("client")}
inputContainer={(children) => (

View File

@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { ProductSchema } from "../../../../../client";
import { Radio } from "@mantine/core";
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { usePrefillCardsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
import { ProductExcelData } from "../types.tsx";
type Props = {
@@ -10,7 +10,7 @@ type Props = {
}
export const useProductsTableColumns = ({ barcode }: Props) => {
const { onProductSelectChange, barcodeProductsMap } = usePrefillDealsWithExcelContext();
const { onProductSelectChange, barcodeProductsMap } = usePrefillCardsWithExcelContext();
const [productData, setProductData] = useState<ProductExcelData>();
useEffect(() => {

View File

@@ -6,6 +6,6 @@ export type ProductExcelData = {
selectedProduct?: ProductSchema;
}
export type DealsWithExcelForm = {
export type CardsWithExcelForm = {
client?: ClientSchema;
}

View File

@@ -1,11 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { DealService } from "../../../client";
import { CardService } from "../../../client";
import ObjectList from "../../../hooks/objectList.tsx";
export const useDealSummaries = () => {
export const useCardSummaries = () => {
const { data: summariesRaw = [], refetch } = useQuery({
queryKey: ["getDealSummaries"],
queryFn: () => DealService.getDealSummaries({ full: false }),
queryKey: ["getCardSummaries"],
queryFn: () => CardService.getCardSummaries({ full: false }),
select: data => data.summaries || [], // Трансформируем полученные данные
});
@@ -14,9 +14,9 @@ export const useDealSummaries = () => {
return { summariesRaw, refetch };
};
export const useDealSummariesFull = () =>
export const useCardSummariesFull = () =>
ObjectList({
queryFn: () => DealService.getDealSummaries({ full: true }),
queryKey: "getDealSummariesFull",
queryFn: () => CardService.getCardSummaries({ full: true }),
queryKey: "getCardSummariesFull",
getObjectsFn: response => response.summaries,
});

View File

@@ -1,4 +1,4 @@
import { useDealSummariesFull } from "./useDealSummaries.tsx";
import { useCardSummariesFull } from "./useCardSummaries.tsx";
import { useForm } from "@mantine/form";
import { useEffect, useState } from "react";
import { BaseMarketplaceSchema, BoardSchema, ClientSchema, ProjectSchema, StatusSchema } from "../../../client";
@@ -8,7 +8,7 @@ type Props = {
projects: ProjectSchema[];
}
export type DealsPageState = {
export type CardsPageState = {
id: number | null;
marketplace: BaseMarketplaceSchema | null;
client: ClientSchema | null;
@@ -16,13 +16,13 @@ export type DealsPageState = {
projectForTable: ProjectSchema | null;
board: BoardSchema | null;
dealStatus: StatusSchema | null;
status: StatusSchema | null;
};
const useDealsPageState = ({ projects }: Props) => {
const { objects } = useDealSummariesFull();
const useCardsPageState = ({ projects }: Props) => {
const { objects } = useCardSummariesFull();
const form = useForm<DealsPageState>({
const form = useForm<CardsPageState>({
initialValues: {
project: null,
id: null,
@@ -31,7 +31,7 @@ const useDealsPageState = ({ projects }: Props) => {
projectForTable: null,
board: null,
dealStatus: null,
status: null,
},
});
@@ -59,9 +59,9 @@ const useDealsPageState = ({ projects }: Props) => {
obj => obj.board.id === form.values.board?.id,
);
if (form.values.dealStatus) {
if (form.values.status) {
result = result.filter(
obj => obj.status.id === form.values.dealStatus?.id,
obj => obj.status.id === form.values.status?.id,
);
}
}
@@ -87,4 +87,4 @@ const useDealsPageState = ({ projects }: Props) => {
return { data, form };
};
export default useDealsPageState;
export default useCardsPageState;

View File

@@ -1,14 +1,14 @@
import { BoardSchema, DealSummary } from "../../../client";
import { BoardSchema, CardSummary } 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 useCardsDnd from "../../../components/Dnd/Cards/CardsDndColumn/hooks/useCardsDnd.tsx";
import useStatusesDnd from "../../../components/Dnd/Statuses/Statuses/hooks/useStatusesDnd.tsx";
type Props = {
selectedBoard: BoardSchema | null;
summariesRaw: DealSummary[];
summariesRaw: CardSummary[];
refetchSummaries: () => void;
refetchBoards: () => void;
}
@@ -24,7 +24,7 @@ const useDnd = ({
const {
summaries,
onDealDragEnd,
} = useDealsDnd({
} = useCardsDnd({
summariesRaw,
refetchSummaries,
})

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { type ProjectSchemaWithCount, ProjectService } from "../../../client";
import { type FullProjectSchema, ProjectService } from "../../../client";
const useProjects = () => {
const [projects, setProjects] = useState<ProjectSchemaWithCount[]>([]);
const [projects, setProjects] = useState<FullProjectSchema[]>([]);
const refetchProjects = () => {
ProjectService.getProjects()

View File

@@ -0,0 +1 @@
export { CardsPage } from "./ui/CardsPage.tsx";

View File

@@ -2,7 +2,7 @@ import { ContextModalProps } from "@mantine/modals";
import BaseFormModal, {
CreateEditFormProps,
} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import { DealProductSchema, DealProductServiceSchema } from "../../../client";
import { CardProductSchema, CardProductServiceSchema } from "../../../client";
import { useForm } from "@mantine/form";
import { NumberInput } from "@mantine/core";
import ProductSelect from "../../../components/ProductSelect/ProductSelect.tsx";
@@ -13,8 +13,8 @@ type RestProps = {
productIds?: number[];
};
type Props = CreateEditFormProps<DealProductSchema> & RestProps;
const AddDealProductModal = ({
type Props = CreateEditFormProps<CardProductSchema> & RestProps;
const AddCardProductModal = ({
context,
id,
innerProps,
@@ -22,13 +22,13 @@ const AddDealProductModal = ({
const isEditing = "element" in innerProps;
const restProps = omit(innerProps, ["clientId"]);
const validateServices = (services?: DealProductServiceSchema[]) => {
const validateServices = (services?: CardProductServiceSchema[]) => {
if (!services || services.length == 0) return null;
return services.find(service => service.service === undefined)
? "Удалите пустые услуги"
: null;
};
const form = useForm<Partial<DealProductSchema>>({
const form = useForm<Partial<CardProductSchema>>({
initialValues: isEditing
? innerProps.element
: {
@@ -37,7 +37,7 @@ const AddDealProductModal = ({
quantity: 1,
},
validate: {
product: (product?: DealProductSchema["product"]) =>
product: (product?: CardProductSchema["product"]) =>
product !== undefined ? null : "Необходимо выбрать товар",
quantity: (quantity?: number) =>
quantity && quantity > 0
@@ -52,7 +52,7 @@ const AddDealProductModal = ({
};
return (
<BaseFormModal
{...(restProps as CreateEditFormProps<DealProductSchema>)}
{...(restProps as CreateEditFormProps<CardProductSchema>)}
form={form}
closeOnSubmit
onClose={onClose}>
@@ -78,7 +78,7 @@ const AddDealProductModal = ({
{/* <DealProductServiceTable*/}
{/* quantity={form.values.quantity || 1}*/}
{/* {...form.getInputProps('services') as*/}
{/* BaseFormInputProps<DealProductServiceSchema[]>}*/}
{/* BaseFormInputProps<CardProductServiceSchema[]>}*/}
{/* />*/}
{/*</Fieldset>*/}
</>
@@ -87,4 +87,4 @@ const AddDealProductModal = ({
);
};
export default AddDealProductModal;
export default AddCardProductModal;

View File

@@ -1,6 +1,6 @@
import { ContextModalProps } from "@mantine/modals";
import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import { DealServiceSchema } from "../../../client";
import { CardServiceSchema } from "../../../client";
import { useForm } from "@mantine/form";
import { ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter } from "@mantine/core";
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
@@ -11,15 +11,15 @@ import { RootState } from "../../../redux/store.ts";
type RestProps = {
serviceIds?: number[];
};
type Props = CreateEditFormProps<Partial<DealServiceSchema>> & RestProps;
const AddDealServiceModal = ({
type Props = CreateEditFormProps<Partial<CardServiceSchema>> & RestProps;
const AddCardServiceModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const authState = useSelector((state: RootState) => state.auth);
const isEditing = "element" in innerProps;
const form = useForm<Partial<DealServiceSchema>>({
const form = useForm<Partial<CardServiceSchema>>({
initialValues: isEditing
? innerProps.element
: {
@@ -28,7 +28,7 @@ const AddDealServiceModal = ({
employees: [],
},
validate: {
service: (service?: DealServiceSchema["service"]) =>
service: (service?: CardServiceSchema["service"]) =>
service !== undefined ? null : "Необходимо выбрать услугу",
quantity: (quantity?: number) =>
quantity && quantity > 0
@@ -103,4 +103,4 @@ const AddDealServiceModal = ({
);
};
export default AddDealServiceModal;
export default AddCardServiceModal;

View File

@@ -1,7 +1,7 @@
import { ProjectSchema } from "../../../client";
import { Flex, Modal, NumberInput, rem } from "@mantine/core";
import { UseFormReturnType } from "@mantine/form";
import { DealsPageState } from "../hooks/useDealsPageState.tsx";
import { CardsPageState } from "../hooks/useCardsPageState.tsx";
import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx";
import DealStatusSelect from "../../../components/DealStatusSelect/DealStatusSelect.tsx";
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
@@ -12,11 +12,11 @@ import { IconFilter } from "@tabler/icons-react";
import BoardSelect from "../../../components/BoardSelect/BoardSelect.tsx";
type Props = {
form: UseFormReturnType<DealsPageState>;
form: UseFormReturnType<CardsPageState>;
projects: ProjectSchema[];
};
const DealsTableFiltersModal = ({ form, projects }: Props) => {
const CardsTableFiltersModal = ({ form, projects }: Props) => {
const [opened, { open, close }] = useDisclosure();
return (
@@ -51,7 +51,7 @@ const DealsTableFiltersModal = ({ form, projects }: Props) => {
/>
<DealStatusSelect
board={form.values.board}
{...form.getInputProps("dealStatus")}
{...form.getInputProps("status")}
clearable
/>
<BaseMarketplaceSelect
@@ -75,4 +75,4 @@ const DealsTableFiltersModal = ({ form, projects }: Props) => {
);
};
export default DealsTableFiltersModal;
export default CardsTableFiltersModal;

View File

@@ -2,7 +2,7 @@ import BaseFormModal, {
CreateEditFormProps,
} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import {
DealProductServiceSchema,
CardProductServiceSchema,
ServiceSchema,
} from "../../../client";
import { ContextModalProps } from "@mantine/modals";
@@ -18,7 +18,7 @@ type RestProps = {
quantity: number;
serviceIds: number[];
};
type Props = CreateEditFormProps<DealProductServiceSchema> & RestProps;
type Props = CreateEditFormProps<CardProductServiceSchema> & RestProps;
const ProductServiceFormModal = ({
context,
@@ -28,7 +28,7 @@ const ProductServiceFormModal = ({
const authState = useSelector((state: RootState) => state.auth);
const isEditing = "onChange" in innerProps;
const initialValues: Partial<DealProductServiceSchema> = isEditing
const initialValues: Partial<CardProductServiceSchema> = isEditing
? innerProps.element
: {
service: undefined,
@@ -36,7 +36,7 @@ const ProductServiceFormModal = ({
employees: [],
isFixedPrice: false,
};
const form = useForm<Partial<DealProductServiceSchema>>({
const form = useForm<Partial<CardProductServiceSchema>>({
initialValues,
validate: {
service: (service?: ServiceSchema) =>
@@ -51,7 +51,7 @@ const ProductServiceFormModal = ({
return (
<BaseFormModal
{...innerProps}
form={form as UseFormReturnType<DealProductServiceSchema>}
form={form as UseFormReturnType<CardProductServiceSchema>}
onClose={onClose}
closeOnSubmit>
<BaseFormModal.Body>

View File

@@ -1,4 +1,3 @@
import { type ProjectSchemaWithCount } from "../../../../client";
import { ContextModalProps } from "@mantine/modals";
import { ActionIcon, Flex, rem, Stack, TextInput, Tooltip } from "@mantine/core";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
@@ -7,6 +6,7 @@ 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";
import { FullProjectSchema } from "../../../../client";
type Props = {
onUpdate: () => void;
@@ -75,7 +75,7 @@ const ProjectsModal = ({ innerProps }: ContextModalProps<Props>) => {
</Tooltip>
</Flex>
),
} as MRT_TableOptions<ProjectSchemaWithCount>
} as MRT_TableOptions<FullProjectSchema>
}
/>
</Stack>

View File

@@ -1,4 +1,4 @@
import { DealProductSchema } from "../../../client";
import { CardProductSchema } from "../../../client";
import { ContextModalProps } from "@mantine/modals";
import { Button, Flex, rem } from "@mantine/core";
import { useState } from "react";
@@ -6,36 +6,36 @@ import ObjectMultiSelect from "../../../components/ObjectMultiSelect/ObjectMulti
import { notifications } from "../../../shared/lib/notifications.ts";
type Props = {
dealProducts: DealProductSchema[];
dealProduct: DealProductSchema;
cardProducts: CardProductSchema[];
cardProduct: CardProductSchema;
onSelect: (
sourceProduct: DealProductSchema,
destinationProducts: DealProductSchema[]
sourceProduct: CardProductSchema,
destinationProducts: CardProductSchema[]
) => void;
};
const SelectDealProductsModal = ({
const SelectCardProductsModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const [dealProducts, setDealProducts] = useState<DealProductSchema[]>([]);
const [cardProducts, setCardProducts] = useState<CardProductSchema[]>([]);
const onSelectClick = () => {
if (!dealProducts) {
if (!cardProducts) {
notifications.error({
message:
"Выберите товары на которые необходимо продублировать услуги",
});
return;
}
innerProps.onSelect(innerProps.dealProduct, dealProducts);
innerProps.onSelect(innerProps.cardProduct, cardProducts);
context.closeContextModal(id);
};
const onDuplicateAllClick = () => {
innerProps.onSelect(
innerProps.dealProduct,
innerProps.dealProducts.filter(
item => item !== innerProps.dealProduct
innerProps.cardProduct,
innerProps.cardProducts.filter(
item => item !== innerProps.cardProduct
)
);
context.closeContextModal(id);
@@ -45,18 +45,18 @@ const SelectDealProductsModal = ({
direction={"column"}
gap={rem(10)}>
<Flex>
<ObjectMultiSelect<DealProductSchema>
<ObjectMultiSelect<CardProductSchema>
w={"100%"}
label={"Товары"}
placeholder={
"Выберите товары на которые нужно продублировать услуги"
}
onChange={setDealProducts}
value={dealProducts}
data={innerProps.dealProducts}
onChange={setCardProducts}
value={cardProducts}
data={innerProps.cardProducts}
getLabelFn={item => item.product.name}
getValueFn={item => item.product.id.toString()}
filterBy={item => item !== innerProps.dealProduct}
filterBy={item => item !== innerProps.cardProduct}
/>
</Flex>
<Flex
@@ -80,4 +80,4 @@ const SelectDealProductsModal = ({
);
};
export default SelectDealProductsModal;
export default SelectCardProductsModal;

View File

@@ -1,18 +1,18 @@
import { Button, Fieldset, Flex, rem, Textarea, TextInput } from "@mantine/core";
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
import { useForm } from "@mantine/form";
import { DealGeneralFormType } from "./DealEditDrawerGeneralTab.tsx";
import { ClientService, DealSchema, DealService } from "../../../../../client";
import { CardGeneralFormType } from "../GeneralTab/GeneralTab.tsx";
import { ClientService, CardSchema, CardService } from "../../../../client";
import { isEqual } from "lodash";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { useQueryClient } from "@tanstack/react-query";
const ClientTab = () => {
const { selectedDeal: deal, setSelectedDeal } = useDealPageContext();
const initialValues: DealGeneralFormType = deal as DealSchema;
const { selectedCard: card, setSelectedCard } = useCardPageContext();
const initialValues: CardGeneralFormType = card as CardSchema;
const queryClient = useQueryClient();
const form = useForm<DealGeneralFormType>(
const form = useForm<CardGeneralFormType>(
{
initialValues: initialValues,
validate: {
@@ -21,7 +21,7 @@ const ClientTab = () => {
},
);
const hasChanges = !isEqual(form.values, initialValues);
const updateClientInfo = async (values: DealGeneralFormType) => {
const updateClientInfo = async (values: CardGeneralFormType) => {
return ClientService.updateClient({
requestBody: {
data: values.client,
@@ -29,11 +29,11 @@ const ClientTab = () => {
}).then(({ ok, message }) => notifications.guess(ok, { message }));
};
const update = async () => {
return DealService.getDealById({ dealId: form.values.id }).then(data => {
setSelectedDeal(data);
return CardService.getCardById({ cardId: form.values.id }).then(data => {
setSelectedCard(data);
form.setInitialValues(data);
queryClient.invalidateQueries({
queryKey: ["getDealSummaries"],
queryKey: ["getCardSummaries"],
});
});
};
@@ -53,7 +53,7 @@ const ClientTab = () => {
disabled
placeholder={"Название"}
label={"Название"}
value={deal?.client.name}
value={card?.client.name}
/>
<TextInput
placeholder={"Введите телефон"}

View File

@@ -4,7 +4,7 @@ import { UserSchema } from "../../../../../client";
import useAvailableEmployeesList from "../hooks/useAvailableEmployeesList.tsx";
type DealData = {
dealId: number;
cardId: number;
}
type Props = DealData & Omit<
@@ -12,8 +12,8 @@ type Props = DealData & Omit<
"data" | "getValueFn" | "getLabelFn"
>;
const UserForDepartmentSelect: FC<Props> = ({ dealId, ...selectProps }) => {
const { objects: employees } = useAvailableEmployeesList({ dealId });
const UserForDepartmentSelect: FC<Props> = ({ cardId, ...selectProps }) => {
const { objects: employees } = useAvailableEmployeesList({ cardId });
return (
<ObjectSelect

View File

@@ -1,21 +1,21 @@
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
import useEmployeeTableColumns from "../hooks/useEmployeesTableColumns.tsx";
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconTrash } from "@tabler/icons-react";
import { MRT_TableOptions } from "mantine-react-table";
import { DealEmployeesSchema } from "../../../../../client";
import { CardEmployeesSchema } from "../../../../../client";
import useEmployeesTab from "../hooks/useEmployeesTab.tsx";
const EmployeesTable = () => {
const { selectedDeal: deal } = useDealPageContext();
const { selectedCard: card } = useCardPageContext();
const columns = useEmployeeTableColumns();
const { onUnassignEmployeeClick } = useEmployeesTab();
return (
<BaseTable
data={deal?.employees}
data={card?.employees}
columns={columns}
restProps={
{
@@ -33,7 +33,7 @@ const EmployeesTable = () => {
</Tooltip>
</Flex>
),
} as MRT_TableOptions<DealEmployeesSchema>
} as MRT_TableOptions<CardEmployeesSchema>
}
/>
);

View File

@@ -1,14 +1,14 @@
import { DealService } from "../../../../../client";
import { CardService } from "../../../../../client";
import ObjectList from "../../../../../hooks/objectList.tsx";
type Props = {
dealId: number;
cardId: number;
}
const useAvailableEmployeesList = ({ dealId }: Props) =>
const useAvailableEmployeesList = ({ cardId }: Props) =>
ObjectList({
queryFn: () => DealService.getAvailableEmployeesToAssign({ dealId }),
queryFn: () => CardService.getAvailableEmployeesToAssign({ cardId }),
getObjectsFn: response => response.employees,
queryKey: "getAvailableEmployeesToAssign",
});

View File

@@ -1,22 +1,22 @@
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { modals } from "@mantine/modals";
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { DealEmployeesSchema, DealService } from "../../../../../client";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import { CardEmployeesSchema, CardService } from "../../../../../client";
const useEmployeesTab = () => {
const { selectedDeal: deal, refetchDeal } = useDealPageContext();
const { selectedCard: card, refetchCard } = useCardPageContext();
const manageEmployee = (dealId: number, userId: number, isAssign: boolean) => {
DealService.manageEmployee({
const manageEmployee = (cardId: number, userId: number, isAssign: boolean) => {
CardService.manageEmployee({
requestBody: {
dealId,
cardId,
userId,
isAssign,
},
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
refetchDeal();
refetchCard();
})
.catch((err) => console.log(err));
};
@@ -28,8 +28,8 @@ const useEmployeesTab = () => {
return;
}
if (!deal) return;
manageEmployee(deal.id, userId, true);
if (!card) return;
manageEmployee(card.id, userId, true);
};
const onAssignEmployeeByQrClick = () => {
@@ -44,20 +44,20 @@ const useEmployeesTab = () => {
});
};
const onUnassignEmployeeClick = (assignment: DealEmployeesSchema) => {
if (!deal) return;
manageEmployee(deal.id, assignment.user.id, false);
const onUnassignEmployeeClick = (assignment: CardEmployeesSchema) => {
if (!card) return;
manageEmployee(card.id, assignment.user.id, false);
};
const onAssignEmployeeManuallyClick = () => {
if (!deal) return;
if (!card) return;
modals.openContextModal({
modal: "assignUserModal",
title: `Назначение исполнителя`,
withCloseButton: false,
innerProps: {
deal,
card,
manageEmployee,
},
});

View File

@@ -1,10 +1,10 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealEmployeesSchema } from "../../../../../client";
import { CardEmployeesSchema } from "../../../../../client";
const useEmployeeTableColumns = () => {
return useMemo<MRT_ColumnDef<DealEmployeesSchema>[]>(
return useMemo<MRT_ColumnDef<CardEmployeesSchema>[]>(
() => [
{
accessorKey: "createdAt",

View File

@@ -1,13 +1,13 @@
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import { Button, Flex, rem } from "@mantine/core";
import { DealSchema } from "../../../../../client";
import { CardSchema } from "../../../../../client";
import AssignUserModalForm from "../types/AssignUserModalForm.tsx";
import AvailableEmployeesSelect from "../components/AvailableEmployeesSelect.tsx";
type Props = {
deal: DealSchema;
manageEmployee: (dealId: number, userId: number, isAssign: boolean) => void;
card: CardSchema;
manageEmployee: (cardId: number, userId: number, isAssign: boolean) => void;
}
const AssignEmployeeModal = ({
@@ -16,7 +16,7 @@ const AssignEmployeeModal = ({
innerProps,
}: ContextModalProps<Props>) => {
const {
deal,
card,
manageEmployee,
} = innerProps;
@@ -28,7 +28,7 @@ const AssignEmployeeModal = ({
const onSubmit = () => {
if (!form.values.employee) return;
manageEmployee(deal.id, form.values.employee.id, true);
manageEmployee(card.id, form.values.employee.id, true);
context.closeContextModal(id);
};
@@ -42,7 +42,7 @@ const AssignEmployeeModal = ({
label={"Работник"}
placeholder={"Выберите работника"}
{...form.getInputProps("employee")}
dealId={deal.id}
cardId={card.id}
/>
<Button
variant={"default"}

View File

@@ -0,0 +1,309 @@
import { FC, useState } from "react";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
import { Button, Checkbox, Divider, Fieldset, Flex, Group, rem, Textarea, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import {
CardSchema,
CardService,
ClientService,
ProjectSchema,
ShippingWarehouseSchema,
StatusSchema,
} from "../../../../client";
import { isEqual } from "lodash";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { useQueryClient } from "@tanstack/react-query";
import ShippingWarehouseAutocomplete
from "../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
import { ButtonCopyControlled } from "../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
import { useClipboard } from "@mantine/hooks";
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";
import CardAttributeFields from "../../../../components/CardAttributeFields/CardAttributeFields.tsx";
import getAttributesFromCard from "../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
import isModuleInProject, { Modules } from "../../utils/isModuleInProject.ts";
import PaymentLinkButton from "./components/PaymentLinkButton.tsx";
import PrintDealBarcodesButton from "./components/PrintDealBarcodesButton.tsx";
type Props = {
card: CardSchema;
};
type Attributes = {
[key: string]: number | boolean | string;
};
export type CardGeneralFormType = Omit<CardSchema, "statusHistory" | "services" | "products">;
const Content: FC<Props> = ({ card }) => {
const { setSelectedCard } = useCardPageContext();
const clipboard = useClipboard();
const queryClient = useQueryClient();
const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, card.board.project);
const getInitialValues = (card: CardSchema): CardGeneralFormType => {
return {
...card,
...getAttributesFromCard(card),
};
};
let initialValues = getInitialValues(card);
const form = useForm<CardGeneralFormType>({
initialValues,
validate: {
name: (value: string) =>
value.length > 0
? null
: "Название не может быть пустым",
status: (value: StatusSchema) =>
!value && "Статус не выбран",
},
});
const updateCardInfo = async (values: CardGeneralFormType) => {
console.log("Updated attributes:");
console.log(values);
const formCardAttrs = values as unknown as Attributes;
const attributes = project?.attributes.reduce((attrs, projectAttr) => {
return {
...attrs,
[projectAttr.name]: formCardAttrs[projectAttr.name],
};
}, {});
return CardService.updateCardGeneralInfo({
requestBody: {
cardId: card.id,
data: {
...values,
statusId: values.status.id,
boardId: values.board.id,
shippingWarehouse: values.shippingWarehouse?.toString(),
attributes,
},
},
}).then(({ ok, message }) => {
notifications.guess(ok, { message });
if (!ok) return;
CardService.getCardById({ cardId: card.id }).then(data => {
console.log(data);
setSelectedCard(data);
initialValues = getInitialValues(data);
form.setValues(initialValues);
queryClient.invalidateQueries({
queryKey: ["getCardSummaries"],
});
});
});
};
const updateClientInfo = async (values: CardGeneralFormType) => {
return ClientService.updateClient({
requestBody: {
data: values.client,
},
}).then(({ ok, message }) => notifications.guess(ok, { message }));
};
const handleSubmit = async (values: CardGeneralFormType) => {
// Updating client info if there changes
if (!isEqual(values.client, card.client)) {
await updateClientInfo(values);
}
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse) ? values.shippingWarehouse.name : values.shippingWarehouse;
await updateCardInfo(
{
...values,
shippingWarehouse,
},
);
};
const isShippingWarehouse = (
value: ShippingWarehouseSchema | string | null | undefined,
): value is ShippingWarehouseSchema => {
return !["string", "null", "undefined"].includes(typeof value);
};
const onCopyGuestUrlClick = () => {
CardService.createDealGuestUrl({
requestBody: {
cardId: card.id,
},
}).then(({ ok, message, url }) => {
if (!ok) notifications.guess(ok, { message });
clipboard.copy(`${window.location.origin}/${url}`);
});
};
return (
<form onSubmit={form.onSubmit(values => handleSubmit(values))}>
<Flex
direction={"column"}
justify={"space-between"}
h={"100%"}>
<Fieldset legend={`Общие параметры [ID: ${card.id}]`}>
<Flex
direction={"column"}
gap={rem(10)}
>
<TextInput
placeholder={"Название сделки"}
label={"Название сделки"}
{...form.getInputProps("name")}
/>
<TextInput
disabled
placeholder={"Дата создания"}
label={"Дата создания"}
value={new Date(card.createdAt).toLocaleString(
"ru-RU",
)}
/>
<ProjectSelect
value={project}
onChange={setProject}
label={"Проект"}
disabled
/>
<BoardSelect
project={project}
{...form.getInputProps("board")}
label={"Доска"}
/>
<DealStatusSelect
board={form.values.board}
{...form.getInputProps("status")}
label={"Статус"}
/>
<Textarea
h={rem(150)}
styles={{
wrapper: { height: "90%" },
input: { height: "90%" },
}}
label={"Коментарий"}
placeholder={"Введите коментарий"}
{...form.getInputProps("comment")}
/>
<ShippingWarehouseAutocomplete
placeholder={"Введите склад отгрузки"}
label={"Склад отгрузки"}
value={
isShippingWarehouse(
form.values.shippingWarehouse,
)
? form.values.shippingWarehouse
: undefined
}
onChange={event => {
if (isShippingWarehouse(event)) {
form.getInputProps(
"shippingWarehouse",
).onChange(event.name);
return;
}
form.getInputProps(
"shippingWarehouse",
).onChange(event);
}}
/>
<ManagerSelect
placeholder={"Укажите менеджера"}
label={"Менеджер"}
{...form.getInputProps("manager")}
/>
{project && (
<CardAttributeFields
project={project}
form={form}
/>
)}
</Flex>
</Fieldset>
<Flex
mt={"md"}
gap={rem(10)}
align={"center"}
justify={"flex-end"}>
<Flex
align={"center"}
gap={rem(10)}
justify={"center"}>
<Flex
gap={rem(10)}
align={"center"}
justify={"space-between"}>
{isServicesAndProductsIncluded && (
<PrintDealBarcodesButton card={card}/>
)}
<Flex gap={rem(10)}>
{isServicesAndProductsIncluded && (
<PaymentLinkButton card={card} />
)}
<ButtonCopyControlled
onCopyClick={onCopyGuestUrlClick}
onCopiedLabel={
"Ссылка скопирована в буфер обмена"
}
copied={clipboard.copied}
>
Ссылка на редактирование
</ButtonCopyControlled>
</Flex>
</Flex>
<Flex gap={rem(10)}>
{isServicesAndProductsIncluded && (
<Checkbox
label={"Оплачен"}
checked={card.billRequest?.paid || card.group?.billRequest?.paid || false}
disabled
/>
)}
<Checkbox
label={"Завершена"}
{...form.getInputProps("isCompleted", { type: "checkbox" })}
/>
<Checkbox
label={"Удалена"}
{...form.getInputProps("isDeleted", { type: "checkbox" })}
/>
</Flex>
</Flex>
<Divider orientation={"vertical"} />
<Group
align={"center"}
justify={"center"}>
<Button
color={"red"}
type={"reset"}
disabled={isEqual(initialValues, form.values)}
onClick={() => form.reset()}>
Отменить изменения
</Button>
<Button
variant={"default"}
type={"submit"}
disabled={isEqual(initialValues, form.values)}>
Сохранить изменения
</Button>
</Group>
</Flex>
</Flex>
</form>
);
};
const GeneralTab: FC = () => {
const { selectedCard } = useCardPageContext();
if (!selectedCard) return <>No card selected</>;
return <Content card={selectedCard} />;
};
export default GeneralTab;

View File

@@ -0,0 +1,43 @@
import { CardSchema } from "../../../../../client";
import ButtonCopy from "../../../../../components/ButtonCopy/ButtonCopy.tsx";
import { ButtonCopyControlled } from "../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
import { getCurrentDateTimeForFilename } from "../../../../../shared/lib/date.ts";
import FileSaver from "file-saver";
type Props = {
card: CardSchema;
}
const PaymentLinkButton = ({ card }: Props) => {
const billRequestPdfUrl = card?.billRequest?.pdfUrl || card?.group?.billRequest?.pdfUrl;
if (billRequestPdfUrl) {
return (
<ButtonCopy
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
value={billRequestPdfUrl}
>
Ссылка на оплату
</ButtonCopy>
);
}
return (
<ButtonCopyControlled
onCopyClick={() => {
const date =
getCurrentDateTimeForFilename();
FileSaver.saveAs(
`${import.meta.env.VITE_API_URL}/card/billing-document/${card.id}`,
`bill_${card.id}_${date}.pdf`,
);
}}
copied={false}
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
>
Ссылка на оплату (PDF)
</ButtonCopyControlled>
);
}
export default PaymentLinkButton;

View File

@@ -0,0 +1,63 @@
import { ActionIcon, Tooltip } from "@mantine/core";
import styles from "../../../ui/CardsPage.module.css";
import { CardSchema, CardService } from "../../../../../client";
import { base64ToBlob } from "../../../../../shared/lib/utils.ts";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { IconBarcode, IconPrinter } from "@tabler/icons-react";
type Props = {
card: CardSchema;
}
const PrintDealBarcodesButton = ({ card }: Props) => {
return (
<>
<Tooltip
className={styles["print-deals-button"]}
label={"Распечатать штрихкоды сделки"}
>
<ActionIcon
onClick={async () => {
const response =
await CardService.getCardProductsBarcodesPdf({
requestBody: {
cardId: card.id,
},
});
const pdfBlob = base64ToBlob(
response.base64String,
response.mimeType,
);
const pdfUrl = URL.createObjectURL(pdfBlob);
const pdfWindow = window.open(pdfUrl);
if (!pdfWindow) {
notifications.error({ message: "Ошибка" });
return;
}
pdfWindow.onload = () => {
pdfWindow.print();
};
}}
variant={"default"}>
<IconBarcode />
</ActionIcon>
</Tooltip>
<Tooltip label={"Распечатать сделку"}>
<ActionIcon
onClick={() => {
const pdfWindow = window.open(
`${import.meta.env.VITE_API_URL}/card/tech-spec/${card.id}`,
);
if (!pdfWindow) return;
pdfWindow.print();
}}
variant={"default"}>
<IconPrinter />
</ActionIcon>
</Tooltip>
</>
);
};
export default PrintDealBarcodesButton;

View File

@@ -10,13 +10,13 @@ import {
Text,
Title,
} from "@mantine/core";
import DealServicesTable from "./components/DealServicesTable/DealServicesTable.tsx";
import useDealProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
import CardServicesTable from "./components/DealServicesTable/CardServicesTable.tsx";
import useCardProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
import { modals } from "@mantine/modals";
import {
BillingService,
DealProductSchema,
DealService,
CardProductSchema,
CardService,
GetServiceKitSchema,
ProductSchema,
ProductService,
@@ -26,27 +26,27 @@ import { CreateProductRequest } from "../../../ProductsPage/types.ts";
import classNames from "classnames";
const ProductAndServiceTab: FC = () => {
const { dealState, dealServicesState, dealProductsState } =
useDealProductAndServiceTabState();
const isLocked = Boolean(dealState.deal?.billRequest || dealState.deal?.group?.billRequest);
const { cardState, cardServicesState, cardProductsState } =
useCardProductAndServiceTabState();
const isLocked = Boolean(cardState.card?.billRequest || cardState.card?.group?.billRequest);
const onAddProductClick = () => {
if (!dealProductsState.onCreate || !dealState.deal) return;
const productIds = dealState.deal.products.map(
if (!cardProductsState.onCreate || !cardState.card) return;
const productIds = cardState.card.products.map(
product => product.product.id
);
modals.openContextModal({
modal: "addDealProduct",
modal: "addCardProduct",
innerProps: {
onCreate: dealProductsState.onCreate,
clientId: dealState.deal.clientId,
onCreate: cardProductsState.onCreate,
clientId: cardState.card.clientId,
productIds: productIds,
},
withCloseButton: false,
});
};
const getTotalPrice = () => {
if (!dealState.deal) return 0;
const productServicesPrice = dealState.deal.products.reduce(
if (!cardState.card) return 0;
const productServicesPrice = cardState.card.products.reduce(
(acc, row) =>
acc +
row.services.reduce(
@@ -55,20 +55,20 @@ const ProductAndServiceTab: FC = () => {
),
0
);
const dealServicesPrice = dealState.deal.services.reduce(
const cardServicesPrice = cardState.card.services.reduce(
(acc, row) => acc + row.price * row.quantity,
0
);
return dealServicesPrice + productServicesPrice;
return cardServicesPrice + productServicesPrice;
};
const onCopyServices = (
sourceProduct: DealProductSchema,
destinationProducts: DealProductSchema[]
sourceProduct: CardProductSchema,
destinationProducts: CardProductSchema[]
) => {
if (!dealState.deal) return;
DealService.copyProductServices({
if (!cardState.card) return;
CardService.copyProductServices({
requestBody: {
dealId: dealState.deal.id,
cardId: cardState.card.id,
destinationProductIds: destinationProducts.map(
product => product.product.id
),
@@ -77,48 +77,48 @@ const ProductAndServiceTab: FC = () => {
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message });
if (!ok) return;
await dealState.refetch();
await cardState.refetch();
});
};
const onCopyServicesClick = (product: DealProductSchema) => {
const onCopyServicesClick = (product: CardProductSchema) => {
modals.openContextModal({
modal: "selectDealProductsModal",
modal: "selectCardProductsModal",
title: "Дублирование услуг",
size: "lg",
innerProps: {
dealProducts: dealState.deal?.products || [],
dealProduct: product,
cardProducts: cardState.card?.products || [],
cardProduct: product,
onSelect: onCopyServices,
},
withCloseButton: false,
});
};
const onKitAdd = (item: DealProductSchema, kit: GetServiceKitSchema) => {
if (!dealState.deal) return;
DealService.addKitToDealProduct({
const onKitAdd = (item: CardProductSchema, kit: GetServiceKitSchema) => {
if (!cardState.card) return;
CardService.addKitToCardProduct({
requestBody: {
dealId: dealState.deal.id,
cardId: cardState.card.id,
kitId: kit.id,
productId: item.product.id,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message });
if (!ok) return;
await dealState.refetch();
await cardState.refetch();
});
};
const onDealKitAdd = (kit: GetServiceKitSchema) => {
if (!dealState.deal) return;
DealService.addKitToDeal({
if (!cardState.card) return;
CardService.addKitToCard({
requestBody: {
dealId: dealState.deal.id,
cardId: cardState.card.id,
kitId: kit.id,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message });
if (!ok) return;
await dealState.refetch();
await cardState.refetch();
});
};
@@ -130,13 +130,13 @@ const ProductAndServiceTab: FC = () => {
});
};
const onCreateProductClick = () => {
if (!dealState.deal) return;
if (!cardState.card) return;
modals.openContextModal({
modal: "createProduct",
title: "Создание товара",
withCloseButton: false,
innerProps: {
clientId: dealState.deal.clientId,
clientId: cardState.card.clientId,
onCreate: onCreateProduct,
},
});
@@ -146,14 +146,14 @@ const ProductAndServiceTab: FC = () => {
async ({ ok, message }) => {
notifications.guess(ok, { message });
if (!ok) return;
await dealState.refetch();
await cardState.refetch();
}
);
};
const onCreateBillClick = () => {
if (!dealState.deal) return;
const dealId = dealState.deal.id;
if (!cardState.card) return;
const cardId = cardState.card.id;
modals.openConfirmModal({
withCloseButton: false,
size: "xl",
@@ -168,7 +168,7 @@ const ProductAndServiceTab: FC = () => {
onConfirm: () => {
BillingService.createDealBill({
requestBody: {
dealId,
cardId,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message });
@@ -177,7 +177,7 @@ const ProductAndServiceTab: FC = () => {
message:
"Ссылка на оплату доступна во вкладе общее",
});
await dealState.refetch();
await cardState.refetch();
});
},
labels: {
@@ -187,8 +187,8 @@ const ProductAndServiceTab: FC = () => {
});
};
const onCancelBillClick = () => {
if (!dealState.deal) return;
const dealId = dealState.deal.id;
if (!cardState.card) return;
const cardId = cardState.card.id;
modals.openConfirmModal({
withCloseButton: false,
children: (
@@ -199,11 +199,11 @@ const ProductAndServiceTab: FC = () => {
onConfirm: () => {
BillingService.cancelDealBill({
requestBody: {
dealId,
cardId,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message });
await dealState.refetch();
await cardState.refetch();
});
},
labels: {
@@ -216,19 +216,19 @@ const ProductAndServiceTab: FC = () => {
<div
className={classNames(
styles["container"],
dealState.deal?.billRequest && styles["container-disabled"]
cardState.card?.billRequest && styles["container-disabled"]
)}>
<div className={styles["products-list"]}>
<ScrollArea offsetScrollbars>
{dealState.deal?.products.map(product => (
{cardState.card?.products.map(product => (
<ProductView
onProductEdit={onProductEdit}
onKitAdd={onKitAdd}
onCopyServices={onCopyServicesClick}
key={product.product.id}
product={product}
onChange={dealProductsState.onChange}
onDelete={dealProductsState.onDelete}
onChange={cardProductsState.onChange}
onDelete={cardProductsState.onDelete}
/>
))}
</ScrollArea>
@@ -239,9 +239,9 @@ const ProductAndServiceTab: FC = () => {
<Flex
direction={"column"}
className={styles["deal-container-wrapper"]}>
<DealServicesTable
<CardServicesTable
onKitAdd={onDealKitAdd}
{...dealServicesState}
{...cardServicesState}
/>
<Divider my={rem(15)} />

View File

@@ -1,5 +1,5 @@
import { CRUDTableProps } from "../../../../../../types/CRUDTable.tsx";
import { DealServiceSchema, GetServiceKitSchema, UserSchema } from "../../../../../../client";
import { CardServiceSchema, GetServiceKitSchema, UserSchema } from "../../../../../../client";
import { FC, useState } from "react";
import { ActionIcon, Button, Flex, Modal, NumberInput, rem, Text, Title, Tooltip } from "@mantine/core";
import { IconTrash, IconUsersGroup } from "@tabler/icons-react";
@@ -9,36 +9,36 @@ import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUser
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 useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
import LockCheckbox from "../../../../../../components/LockCheckbox/LockCheckbox.tsx";
import { useDebouncedCallback } from "@mantine/hooks";
type RestProps = {
onKitAdd?: (kit: GetServiceKitSchema) => void;
};
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
const DealServicesTable: FC<Props> = ({
type Props = CRUDTableProps<CardServiceSchema> & RestProps;
const CardServicesTable: FC<Props> = ({
items,
onDelete,
onCreate,
onChange,
onKitAdd,
}) => {
const debouncedOnChange = useDebouncedCallback(async (item: DealServiceSchema) => {
const debouncedOnChange = useDebouncedCallback(async (item: CardServiceSchema) => {
if (!onChange) return;
onChange(item);
}, 200);
const authState = useSelector((state: RootState) => state.auth);
const { dealState } = useDealProductAndServiceTabState();
const isLocked = Boolean(dealState.deal?.billRequest);
const { cardState } = useCardProductAndServiceTabState();
const isLocked = Boolean(cardState.card?.billRequest);
const [currentService, setCurrentService] = useState<
DealServiceSchema | undefined
CardServiceSchema | undefined
>();
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
const onDeleteClick = (item: DealServiceSchema) => {
const onDeleteClick = (item: CardServiceSchema) => {
if (!onDelete) return;
onDelete(item);
};
@@ -46,7 +46,7 @@ const DealServicesTable: FC<Props> = ({
if (!onCreate) return;
const serviceIds = items.map(service => service.service.id);
modals.openContextModal({
modal: "addDealService",
modal: "addCardService",
innerProps: {
onCreate: onCreate,
serviceIds,
@@ -54,14 +54,14 @@ const DealServicesTable: FC<Props> = ({
withCloseButton: false,
});
};
const onQuantityChange = (item: DealServiceSchema, quantity: number) => {
const onQuantityChange = (item: CardServiceSchema, quantity: number) => {
if (!onChange) return;
debouncedOnChange({
...item,
quantity,
});
};
const onPriceChange = (item: DealServiceSchema, price: number) => {
const onPriceChange = (item: CardServiceSchema, price: number) => {
if (!onChange) return;
debouncedOnChange({
...item,
@@ -69,14 +69,14 @@ const DealServicesTable: FC<Props> = ({
isFixedPrice: true,
});
};
const onLockChange = (item: DealServiceSchema, isLocked: boolean) => {
const onLockChange = (item: CardServiceSchema, isLocked: boolean) => {
if (!onChange) return;
debouncedOnChange({
...item,
isFixedPrice: isLocked,
});
};
const onEmployeeClick = (item: DealServiceSchema) => {
const onEmployeeClick = (item: CardServiceSchema) => {
if (!onChange) return;
setCurrentService(item);
setEmployeesModalVisible(true);
@@ -248,4 +248,4 @@ const DealServicesTable: FC<Props> = ({
</>
);
};
export default DealServicesTable;
export default CardServicesTable;

View File

@@ -1,5 +1,5 @@
import { CRUDTableProps } from "../../../../../../types/CRUDTable.tsx";
import { DealProductServiceSchema, UserSchema } from "../../../../../../client";
import { CardProductServiceSchema, UserSchema } from "../../../../../../client";
import { FC, useState } from "react";
import useProductServicesTableColumns from "./columns.tsx";
import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
@@ -10,7 +10,7 @@ import { modals } from "@mantine/modals";
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
import { useSelector } from "react-redux";
import { RootState } from "../../../../../../redux/store.ts";
import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
type RestProps = {
quantity: number;
@@ -18,7 +18,7 @@ type RestProps = {
onKitAdd?: () => void;
};
type Props = CRUDTableProps<DealProductServiceSchema> & RestProps;
type Props = CRUDTableProps<CardProductServiceSchema> & RestProps;
const ProductServicesTable: FC<Props> = ({
items,
quantity,
@@ -28,15 +28,15 @@ const ProductServicesTable: FC<Props> = ({
onCopyServices,
onKitAdd,
}) => {
const { dealState } = useDealProductAndServiceTabState();
const isLocked = Boolean(dealState.deal?.billRequest);
const { cardState } = useCardProductAndServiceTabState();
const isLocked = Boolean(cardState.card?.billRequest);
const authState = useSelector((state: RootState) => state.auth);
const columns = useProductServicesTableColumns({ data: items, quantity });
const serviceIds = items.map(service => service.service.id);
const [currentService, setCurrentService] = useState<
DealProductServiceSchema | undefined
CardProductServiceSchema | undefined
>();
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
@@ -53,7 +53,7 @@ const ProductServicesTable: FC<Props> = ({
});
};
const onChangeClick = (item: DealProductServiceSchema) => {
const onChangeClick = (item: CardProductServiceSchema) => {
if (!onChange) return;
modals.openContextModal({
modal: "productServiceForm",
@@ -66,7 +66,7 @@ const ProductServicesTable: FC<Props> = ({
withCloseButton: false,
});
};
const onEmployeeClick = (item: DealProductServiceSchema) => {
const onEmployeeClick = (item: CardProductServiceSchema) => {
if (!onChange) return;
setCurrentService(item);
setEmployeesModalVisible(true);
@@ -168,7 +168,7 @@ const ProductServicesTable: FC<Props> = ({
)}
</Flex>
),
} as MRT_TableOptions<DealProductServiceSchema>
} as MRT_TableOptions<CardProductServiceSchema>
}
/>
</Flex>

View File

@@ -1,11 +1,11 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { DealProductServiceSchema } from "../../../../../../client";
import { CardProductServiceSchema } from "../../../../../../client";
import { useSelector } from "react-redux";
import { RootState } from "../../../../../../redux/store.ts";
type Props = {
data: DealProductServiceSchema[];
data: CardProductServiceSchema[];
quantity: number;
};
const useProductServicesTableColumns = (props: Props) => {
@@ -17,7 +17,7 @@ const useProductServicesTableColumns = (props: Props) => {
);
const hideGuestColumns = ["service.cost"];
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(
return useMemo<MRT_ColumnDef<CardProductServiceSchema>[]>(
() => [
{
accessorKey: "service.name",

View File

@@ -1,7 +1,7 @@
import { FC } from "react";
import {
DealProductSchema,
DealProductServiceSchema,
CardProductSchema,
CardProductServiceSchema,
GetServiceKitSchema,
ProductSchema,
} from "../../../../../../client";
@@ -12,15 +12,15 @@ 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 useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
import { useDebouncedCallback } from "@mantine/hooks";
type Props = {
product: DealProductSchema;
onChange?: (item: DealProductSchema) => void;
onDelete?: (item: DealProductSchema) => void;
onCopyServices?: (item: DealProductSchema) => void;
onKitAdd?: (item: DealProductSchema, kit: GetServiceKitSchema) => void;
product: CardProductSchema;
onChange?: (item: CardProductSchema) => void;
onDelete?: (item: CardProductSchema) => void;
onCopyServices?: (item: CardProductSchema) => void;
onKitAdd?: (item: CardProductSchema, kit: GetServiceKitSchema) => void;
onProductEdit: (product: ProductSchema) => void;
};
type ProductFieldNames = {
@@ -42,18 +42,18 @@ const ProductView: FC<Props> = ({
onKitAdd,
onProductEdit,
}) => {
const { dealState } = useDealProductAndServiceTabState();
const debouncedOnChange = useDebouncedCallback(async (item: DealProductSchema) => {
const { cardState } = useCardProductAndServiceTabState();
const debouncedOnChange = useDebouncedCallback(async (item: CardProductSchema) => {
if (!onChange) return;
onChange(item);
}, 200);
const isLocked = Boolean(dealState.deal?.billRequest);
const isLocked = Boolean(cardState.card?.billRequest);
const onDeleteClick = () => {
if (!onDelete) return;
onDelete(product);
};
const onServiceDelete = (item: DealProductServiceSchema) => {
const onServiceDelete = (item: CardProductServiceSchema) => {
if (!onChange) return;
onChange({
...product,
@@ -62,7 +62,7 @@ const ProductView: FC<Props> = ({
),
});
};
const onServiceCreate = (item: DealProductServiceSchema) => {
const onServiceCreate = (item: CardProductServiceSchema) => {
if (!onChange) return;
onChange({
...product,
@@ -70,7 +70,7 @@ const ProductView: FC<Props> = ({
});
};
const onServiceChange = (item: DealProductServiceSchema) => {
const onServiceChange = (item: CardProductServiceSchema) => {
if (!onChange) return;
onChange({
...product,

View File

@@ -1,51 +1,51 @@
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
import { DealProductSchema, DealService, DealServiceSchema } from "../../../../../client";
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { CardService, CardServiceSchema, CardProductSchema } from "../../../../../client";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import { notifications } from "../../../../../shared/lib/notifications.ts";
const useDealState = () => {
const useCardState = () => {
const { selectedDeal, setSelectedDeal } = useDealPageContext();
const { selectedCard, setSelectedCard } = useCardPageContext();
const recalculate = async () => {
return DealService.recalculateDealPrice({
return CardService.recalculateCardPrice({
requestBody: {
dealId: selectedDeal?.id || -1,
cardId: selectedCard?.id || -1,
},
});
};
const refetchDeal = async () => {
if (!selectedDeal) return;
const refetchCard = async () => {
if (!selectedCard) return;
return DealService.getDealById({ dealId: selectedDeal.id }).then(
async deal => {
setSelectedDeal(deal);
return CardService.getCardById({ cardId: selectedCard.id }).then(
async card => {
setSelectedCard(card);
},
);
};
const refetch = async () => {
if (!selectedDeal) return;
await refetchDeal();
if (!selectedCard) return;
await refetchCard();
const { ok, message } = await recalculate();
if (!ok) notifications.guess(ok, { message });
await refetchDeal();
await refetchCard();
};
return {
deal: selectedDeal,
card: selectedCard,
refetch,
};
};
const useDealServicesState = (): CRUDTableProps<DealServiceSchema> => {
const { deal, refetch } = useDealState();
const useCardServicesState = (): CRUDTableProps<CardServiceSchema> => {
const { card, refetch } = useCardState();
const refetchAndRecalculate = async () => {
await refetch();
};
const onCreate = (item: DealServiceSchema) => {
if (!deal) return;
DealService.addDealService({
const onCreate = (item: CardServiceSchema) => {
if (!card) return;
CardService.addCardService({
requestBody: {
dealId: deal.id,
cardId: card.id,
serviceId: item.service.id,
quantity: item.quantity,
price: item.price,
@@ -55,11 +55,11 @@ const useDealServicesState = (): CRUDTableProps<DealServiceSchema> => {
if (ok) await refetchAndRecalculate();
});
};
const onDelete = (item: DealServiceSchema) => {
if (!deal) return;
DealService.deleteDealService({
const onDelete = (item: CardServiceSchema) => {
if (!card) return;
CardService.deleteCardService({
requestBody: {
dealId: deal.id,
cardId: card.id,
serviceId: item.service.id,
},
}).then(async ({ ok, message }) => {
@@ -67,11 +67,11 @@ const useDealServicesState = (): CRUDTableProps<DealServiceSchema> => {
if (ok) await refetchAndRecalculate();
});
};
const onChange = (item: DealServiceSchema) => {
if (!deal) return;
DealService.updateDealService({
const onChange = (item: CardServiceSchema) => {
if (!card) return;
CardService.updateCardService({
requestBody: {
dealId: deal.id,
cardId: card.id,
service: item,
},
}).then(async ({ ok, message }) => {
@@ -80,23 +80,23 @@ const useDealServicesState = (): CRUDTableProps<DealServiceSchema> => {
});
};
return {
items: deal?.services || [],
items: card?.services || [],
onCreate,
onDelete,
onChange,
};
};
const useDealProductsState = (): CRUDTableProps<DealProductSchema> => {
const { deal, refetch } = useDealState();
const useDealProductsState = (): CRUDTableProps<CardProductSchema> => {
const { card, refetch } = useCardState();
const refetchAndRecalculate = async () => {
await refetch();
};
const onCreate = (item: DealProductSchema) => {
if (!deal) return;
DealService.addDealProduct({
const onCreate = (item: CardProductSchema) => {
if (!card) return;
CardService.addCardProduct({
requestBody: {
dealId: deal.id,
cardId: card.id,
product: item,
},
}).then(async ({ ok, message }) => {
@@ -104,11 +104,11 @@ const useDealProductsState = (): CRUDTableProps<DealProductSchema> => {
if (ok) await refetchAndRecalculate();
});
};
const onDelete = (item: DealProductSchema) => {
if (!deal) return;
DealService.deleteDealProduct({
const onDelete = (item: CardProductSchema) => {
if (!card) return;
CardService.deleteCardProduct({
requestBody: {
dealId: deal.id,
cardId: card.id,
productId: item.product.id,
},
}).then(async ({ ok, message }) => {
@@ -116,11 +116,11 @@ const useDealProductsState = (): CRUDTableProps<DealProductSchema> => {
if (ok) await refetchAndRecalculate();
});
};
const onChange = (item: DealProductSchema) => {
if (!deal) return;
DealService.updateDealProduct({
const onChange = (item: CardProductSchema) => {
if (!card) return;
CardService.updateCardProduct({
requestBody: {
dealId: deal.id,
cardId: card.id,
product: item,
},
}).then(async ({ ok, message }) => {
@@ -129,20 +129,20 @@ const useDealProductsState = (): CRUDTableProps<DealProductSchema> => {
});
};
return {
items: deal?.products || [],
items: card?.products || [],
onCreate,
onDelete,
onChange,
};
};
const useDealProductAndServiceTabState = () => {
const dealState = useDealState();
const dealProductsState = useDealProductsState();
const dealServicesState = useDealServicesState();
const useCardProductAndServiceTabState = () => {
const cardState = useCardState();
const cardProductsState = useDealProductsState();
const cardServicesState = useCardServicesState();
return {
dealState,
dealProductsState,
dealServicesState,
cardState,
cardProductsState,
cardServicesState,
};
};
export default useDealProductAndServiceTabState;
export default useCardProductAndServiceTabState;

View File

@@ -8,7 +8,7 @@ import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
const ShippingTab = () => {
const {
onCreateBoxInDealClick,
onCreateBoxInCardClick,
onCreatePalletClick,
} = useShipping();
@@ -24,7 +24,7 @@ const ShippingTab = () => {
<InlineButton onClick={() => onCreatePalletClick()}>
Добавить паллет
</InlineButton>
<InlineButton onClick={() => onCreateBoxInDealClick()}>
<InlineButton onClick={() => onCreateBoxInCardClick()}>
Добавить короб
</InlineButton>
<InlineButton onClick={() => onGetDealQrPdfClick()}>

View File

@@ -5,8 +5,8 @@ import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { MRT_TableOptions } from "mantine-react-table";
import { modals } from "@mantine/modals";
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import useUpdateDeal from "../hooks/useUpdateDeal.tsx";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import useUpdateCard from "../hooks/useUpdateCard.tsx";
import { notifications } from "../../../../../shared/lib/notifications.ts";
@@ -16,8 +16,8 @@ type Props = {
const BoxesTable = ({ items }: Props) => {
const columns = useShippingTableColumns<BoxSchema>({ isBox: true });
const { update } = useUpdateDeal();
const { selectedDeal: deal } = useDealPageContext();
const { update } = useUpdateCard();
const { selectedCard: card } = useCardPageContext();
const onDeleteClick = (box: BoxSchema) => {
ShippingService.deleteBox({
@@ -31,13 +31,13 @@ const BoxesTable = ({ items }: Props) => {
};
const onEditClick = (box: BoxSchema) => {
if (!deal) return;
if (!card) return;
modals.openContextModal({
modal: "shippingProductModal",
title: "Редактирование короба",
withCloseButton: false,
innerProps: {
deal,
card,
updateOnSubmit: update,
isBox: true,
shippingData: {

View File

@@ -5,9 +5,9 @@ import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { MRT_TableOptions } from "mantine-react-table";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import useUpdateDeal from "../hooks/useUpdateDeal.tsx";
import useUpdateCard from "../hooks/useUpdateCard.tsx";
import { modals } from "@mantine/modals";
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
type Props = {
@@ -16,8 +16,8 @@ type Props = {
const ShippingProductsTable = ({ items }: Props) => {
const columns = useShippingTableColumns<ShippingProductSchema>({ isBox: false });
const { update } = useUpdateDeal();
const { selectedDeal: deal } = useDealPageContext();
const { update } = useUpdateCard();
const { selectedCard: card } = useCardPageContext();
const onDeleteClick = (shippingProduct: ShippingProductSchema) => {
ShippingService.deleteShippingProduct({
@@ -31,13 +31,13 @@ const ShippingProductsTable = ({ items }: Props) => {
};
const onEditClick = (shippingProduct: ShippingProductSchema) => {
if (!deal) return;
if (!card) return;
modals.openContextModal({
modal: "shippingProductModal",
title: "Редактирование товара на паллете",
withCloseButton: false,
innerProps: {
deal,
card,
updateOnSubmit: update,
isBox: false,
shippingData: {

View File

@@ -1,4 +1,4 @@
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import { Accordion, ActionIcon, Button, Center, Group, rem, Stack, Title, Tooltip } from "@mantine/core";
import { BoxSchema, PalletSchema, ShippingProductSchema } from "../../../../../client";
import ShippingProductsTable from "./ShippingProductsTable.tsx";
@@ -7,7 +7,7 @@ import { IconBox, IconPlus, IconSpace, IconTrash } from "@tabler/icons-react";
import useShipping from "../hooks/useShipping.tsx";
const ShippingTree = () => {
const { selectedDeal: deal } = useDealPageContext();
const { selectedCard: card } = useCardPageContext();
const {
onCreateBoxInPallet,
@@ -21,7 +21,7 @@ const ShippingTree = () => {
};
const getPallets = () => {
const sortedPallets = sortById(deal?.pallets) as PalletSchema[];
const sortedPallets = sortById(card?.pallets) as PalletSchema[];
const pallets = sortedPallets?.map((pallet => {
palletIds.push(pallet.id.toString());
return (
@@ -39,8 +39,8 @@ const ShippingTree = () => {
);
})) ?? [];
if (deal?.boxes && deal?.boxes.length > 0) {
const boxes = deal?.boxes.sort((b1, b2) => (b1.id - b2.id));
if (card?.boxes && card?.boxes.length > 0) {
const boxes = card?.boxes.sort((b1, b2) => (b1.id - b2.id));
const itemValue = "noPallets";
const boxesWithoutPallet = (
<Accordion.Item key={-1} value={itemValue}>

View File

@@ -1,20 +1,20 @@
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { CreateBoxInDealSchema, CreateBoxInPalletSchema, ShippingService } from "../../../../../client";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import { CreateBoxInCardSchema, CreateBoxInPalletSchema, ShippingService } from "../../../../../client";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { modals } from "@mantine/modals";
import { Text } from "@mantine/core";
import useUpdateDeal from "./useUpdateDeal.tsx";
import useUpdateCard from "./useUpdateCard.tsx";
const useShipping = () => {
const { selectedDeal: deal } = useDealPageContext();
const { update } = useUpdateDeal();
const { selectedCard: card } = useCardPageContext();
const { update } = useUpdateCard();
const palletIds: string[] = [];
const onCreatePalletClick = () => {
if (!deal) return;
if (!card) return;
ShippingService.createPallet({
dealId: deal.id,
cardId: card.id,
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
@@ -35,7 +35,7 @@ const useShipping = () => {
};
const onDeletePalletClick = (palletId: number) => {
if (!deal) return;
if (!card) return;
modals.openConfirmModal({
title: "Удаление паллета",
children: <Text size="sm">Вы уверены что хотите удалить паллет?</Text>,
@@ -45,7 +45,7 @@ const useShipping = () => {
});
};
const onCreateBox = (data: CreateBoxInPalletSchema | CreateBoxInDealSchema) => {
const onCreateBox = (data: CreateBoxInPalletSchema | CreateBoxInCardSchema) => {
ShippingService.updateBox({
requestBody: {
data,
@@ -58,8 +58,8 @@ const useShipping = () => {
.catch(err => console.log(err));
};
const onCreateBoxInDealClick = () => {
onCreateBox({ dealId: deal?.id ?? -1 });
const onCreateBoxInCardClick = () => {
onCreateBox({ cardId: card?.id ?? -1 });
};
const onCreateBoxInPallet = (palletId: number) => {
@@ -67,13 +67,13 @@ const useShipping = () => {
};
const onCreateShippingProduct = (palletId: number) => {
if (!deal) return;
if (!card) return;
modals.openContextModal({
modal: "shippingProductModal",
title: "Добавление товара на паллет",
withCloseButton: false,
innerProps: {
deal,
card,
updateOnSubmit: update,
isBox: false,
shippingData: {
@@ -86,7 +86,7 @@ const useShipping = () => {
};
return {
onCreateBoxInDealClick,
onCreateBoxInCardClick,
onCreateBoxInPallet,
onCreateShippingProduct,
onCreatePalletClick,

View File

@@ -1,28 +1,28 @@
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
const useShippingQrs = () => {
const { selectedDeal: deal } = useDealPageContext();
const { selectedCard: card } = useCardPageContext();
const basePdfUrl = `${import.meta.env.VITE_API_URL}/shipping/pdf`;
const getPdf = (url: string) => {
if (!deal) return;
if (!card) return;
const pdfWindow = window.open(url);
if (!pdfWindow) return;
pdfWindow.print();
};
const onGetDealQrPdfClick = () => {
getPdf(`${basePdfUrl}/deal/${deal?.id}`);
getPdf(`${basePdfUrl}/deal/${card?.id}`);
};
const onGetPalletsPdfClick = () => {
getPdf(`${basePdfUrl}/pallets/${deal?.id}`);
getPdf(`${basePdfUrl}/pallets/${card?.id}`);
};
const onGetBoxesPdfClick = () => {
getPdf(`${basePdfUrl}/boxes/${deal?.id}`);
getPdf(`${basePdfUrl}/boxes/${card?.id}`);
};
return {

View File

@@ -0,0 +1,18 @@
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx";
import { CardService } from "../../../../../client";
const useUpdateCard = () => {
const { selectedCard, setSelectedCard } = useCardPageContext();
const update = () => {
if (!selectedCard) return;
CardService.getCardById({ cardId: selectedCard.id })
.then(data => {
setSelectedCard(data);
});
};
return { update };
};
export default useUpdateCard;

View File

@@ -3,11 +3,11 @@ import { ContextModalProps } from "@mantine/modals";
import { Button, Flex, NumberInput, rem, Text } from "@mantine/core";
import getRestProducts from "../utils/getRestProducts.tsx";
import {
CreateBoxInDealSchema,
CreateBoxInCardSchema,
CreateBoxInPalletSchema,
CreateShippingProductSchema,
DealProductSchema,
DealSchema,
CardProductSchema,
CardSchema,
ProductSchema,
ShippingService,
UpdateBoxSchema,
@@ -21,7 +21,7 @@ import ShippingProductSelect from "../components/ShippingProductSelect.tsx";
type Props = {
updateOnSubmit: () => void;
deal: DealSchema;
card: CardSchema;
isBox: boolean;
shippingData: Partial<ShippingData>;
}
@@ -31,7 +31,7 @@ const ShippingProductModal = ({
id,
innerProps,
}: ContextModalProps<Props>) => {
const [restProducts, setRestProducts] = useState<Map<number, DealProductSchema>>(new Map());
const [restProducts, setRestProducts] = useState<Map<number, CardProductSchema>>(new Map());
const [restProductsSelectData, setRestProductSelectData] = useState<ProductSchema[]>([]);
const getRestProductQuantity = () => {
@@ -45,8 +45,8 @@ const ShippingProductModal = ({
};
const findProductById = (productId?: number | null) => {
const dealProduct = innerProps.deal.products.find(p => p.product.id === productId);
return dealProduct ? dealProduct.product : null;
const cardProduct = innerProps.card.products.find(p => p.product.id === productId);
return cardProduct ? cardProduct.product : null;
};
const initialValues: ShippingModalForm = {
@@ -64,19 +64,19 @@ const ShippingProductModal = ({
useEffect(() => {
const data = getRestProducts({
deal: innerProps.deal,
card: innerProps.card,
unaccountedValues: innerProps.shippingData as UpdateShippingProductSchema | UpdateBoxSchema,
});
setRestProducts(data.restProducts);
setRestProductSelectData(data.restProductsSelectData);
}, [innerProps.deal]);
}, [innerProps.card]);
const updateBox = () => {
const data = {
...innerProps.shippingData,
...form.values,
productId: form.values.product?.id,
} as CreateBoxInPalletSchema | CreateBoxInDealSchema | UpdateBoxSchema;
} as CreateBoxInPalletSchema | CreateBoxInCardSchema | UpdateBoxSchema;
ShippingService.updateBox({
requestBody: { data },

View File

@@ -1,4 +1,4 @@
import { DealProductSchema, DealSchema, ProductSchema } from "../../../../../client";
import { CardProductSchema, CardSchema, ProductSchema } from "../../../../../client";
type UnaccountedValues = {
boxId?: number | null;
@@ -6,16 +6,16 @@ type UnaccountedValues = {
}
type Props = {
deal?: DealSchema;
card?: CardSchema;
unaccountedValues: UnaccountedValues;
}
const getRestProducts = ({
deal,
card,
unaccountedValues,
}: Props) => {
const totalProducts = new Map(
deal?.products.map(product => product && [product.product.id, product]),
card?.products.map(product => product && [product.product.id, product]),
);
const distributedProducts = new Map();
@@ -30,12 +30,12 @@ const getRestProducts = ({
}
};
deal?.boxes?.forEach((box) => {
card?.boxes?.forEach((box) => {
if (!box.product || box.id === unaccountedValues.boxId) return;
accountProduct(box.product, box.quantity);
});
deal?.pallets?.forEach(pallet => {
card?.pallets?.forEach(pallet => {
pallet.shippingProducts.forEach(shippingProduct => {
if (shippingProduct.id === unaccountedValues.shippingProductId) return;
accountProduct(shippingProduct.product, shippingProduct.quantity);
@@ -46,7 +46,7 @@ const getRestProducts = ({
});
});
const restProducts = new Map<number, DealProductSchema>();
const restProducts = new Map<number, CardProductSchema>();
totalProducts.entries().forEach(([key, product]) => {
const distributedProduct = distributedProducts.get(key);

View File

@@ -1,28 +1,28 @@
import { FC, useState } from "react";
import { useDealSummaries } from "../hooks/useDealSummaries.tsx";
import { useCardSummaries } from "../hooks/useCardSummaries.tsx";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
import { DealPageContextProvider } from "../contexts/DealPageContext.tsx";
import CardEditDrawer from "../drawers/CardEditDrawer/CardEditDrawer.tsx";
import { CardPageContextProvider } from "../contexts/CardPageContext.tsx";
import { rem } from "@mantine/core";
import useDealsPageState from "../hooks/useDealsPageState.tsx";
import DealsTable from "../components/DealsTable/DealsTable.tsx";
import useCardsPageState from "../hooks/useCardsPageState.tsx";
import CardsTable from "../components/DealsTable/CardsTable.tsx";
import { motion } from "framer-motion";
import DealPrefillDrawer from "../drawers/DealPrefillDrawer/DealPrefillDrawer.tsx";
import { PrefillDealContextProvider } from "../contexts/PrefillDealContext.tsx";
import CardPrefillDrawer from "../drawers/CardPrefillDrawer/CardPrefillDrawer.tsx";
import { PrefillCardContextProvider } from "../contexts/PrefillCardContext.tsx";
import { useParams } from "@tanstack/react-router";
import { PrefillDealsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
import { PrefillCardsWithExcelContextProvider } 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 = () => {
export const CardsPage: FC = () => {
const { projects, refetchProjects } = useProjects();
const { data, form } = useDealsPageState({ projects });
const { data, form } = useCardsPageState({ projects });
const { boards, refetchBoards } = useBoards({ projectId: form.values.project?.id });
const { dealId } = useParams({ strict: false });
const { summariesRaw, refetch: refetchSummaries } = useDealSummaries();
const { summariesRaw, refetch: refetchSummaries } = useCardSummaries();
const [displayMode, setDisplayMode] = useState<DisplayMode>(
DisplayMode.BOARD,
@@ -30,7 +30,7 @@ export const DealsPage: FC = () => {
const getTableBody = () => {
return (
<DealsTable items={data} />
<CardsTable items={data} />
);
};
@@ -41,7 +41,6 @@ export const DealsPage: FC = () => {
refetchSummaries={refetchSummaries}
boards={boards}
refetchBoards={refetchBoards}
project={form.values.project}
/>
);
};
@@ -73,14 +72,15 @@ export const DealsPage: FC = () => {
padding: 0,
}}
>
<DealPageContextProvider
defaultDealId={(dealId && parseInt(dealId)) || undefined}
refetchDeals={async () => {
<CardPageContextProvider
defaultCardId={(dealId && parseInt(dealId)) || undefined}
refetchCards={async () => {
await refetchSummaries();
}}
selectedProject={form.values.project}
>
<PrefillDealContextProvider>
<PrefillDealsWithExcelContextProvider>
<PrefillCardContextProvider>
<PrefillCardsWithExcelContextProvider>
<LeadsPageHeader
form={form}
displayMode={displayMode}
@@ -98,11 +98,11 @@ export const DealsPage: FC = () => {
>
{getBody()}
</PageBlock>
<DealEditDrawer />
<DealPrefillDrawer />
</PrefillDealsWithExcelContextProvider>
</PrefillDealContextProvider>
</DealPageContextProvider>
<CardEditDrawer />
<CardPrefillDrawer />
</PrefillCardsWithExcelContextProvider>
</PrefillCardContextProvider>
</CardPageContextProvider>
</PageBlock>
);
};

View File

@@ -0,0 +1,12 @@
import { ProjectSchema } from "../../../client";
export enum Modules {
SERVICES_AND_PRODUCTS = "servicesAndProducts",
}
const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => {
console.log(module.toString());
return project?.modules.findIndex(m => m.key === module.toString()) !== -1;
};
export default isModuleInProject;

View File

@@ -1 +0,0 @@
export { DealPage } from "./ui/DealPage";

View File

@@ -1,30 +0,0 @@
import { useParams } from "@tanstack/react-router";
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";
export type Props = {
dealId: number;
};
const DealPageContent: FC<Props> = ({ dealId }) => {
const { setSelectedDeal } = useDealPageContext();
useEffect(() => {
DealService.getDealById({ dealId }).then(deal => {
setSelectedDeal(deal);
});
}, []);
return <ProductAndServiceTab />;
};
const DealPageWrapper: FC<{ children: React.ReactNode }> = ({ children }) => {
return <DealPageContextProvider refetchDeals={async () => {
}}>{children}</DealPageContextProvider>;
};
export const DealPage = () => {
const { dealId } = useParams({ strict: false });
return (
<DealPageWrapper>
<DealPageContent dealId={parseInt(dealId || "-1")} />
</DealPageWrapper>
);
};

Some files were not shown because too many files have changed in this diff Show More