feat: processing of modules in card, renaming

This commit is contained in:
2025-02-20 20:21:08 +04:00
parent dc9455966e
commit 8083bdf3d0
29 changed files with 240 additions and 768 deletions

View File

@@ -20,7 +20,7 @@ type Props = {
refetchProjects: () => void;
}
const LeadsPageHeader = ({
const CardsPageHeader = ({
displayMode,
setDisplayMode,
form,
@@ -124,4 +124,4 @@ const LeadsPageHeader = ({
);
};
export default LeadsPageHeader;
export default CardsPageHeader;

View File

@@ -1,132 +0,0 @@
import {
ActionIcon,
Button,
ComboboxItem,
ComboboxItemGroup,
Flex,
Input,
OptionsFilter,
rem,
} from "@mantine/core";
import { BaseFormInputProps } from "../../../../types/utils.ts";
import { CardProductServiceSchema, ServiceSchema } from "../../../../client";
import { FC, useEffect, useState } from "react";
import ServiceWithPriceInput from "../../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
import { isNumber } from "lodash";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { IconTrash } from "@tabler/icons-react";
import { useSelector } from "react-redux";
import { RootState } from "../../../../redux/store.ts";
type RestProps = {
quantity: number;
};
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<CardProductServiceSchema>[]
>(value || []);
const onServiceChange = (idx: number, value: ServiceSchema) => {
setInnerValue(oldValue =>
oldValue.map((item, i) =>
i === idx ? { ...item, service: value } : item
)
);
};
const onQuantityChange = (idx: number, value: string | number) => {
if (!isNumber(value)) return;
setInnerValue(oldValue =>
oldValue.map((item, i) =>
i === idx ? { ...item, price: value } : item
)
);
};
const onCreate = () => {
if (innerValue.length > 0 && !innerValue.at(-1)?.service) {
notifications.error({ message: "Заполните последнюю услугу" });
return;
}
setInnerValue(prevState => [
...prevState,
{ service: undefined, quantity: 1 },
]);
};
const onDelete = (idx: number) => {
setInnerValue(oldValue => oldValue.filter((_, i) => i !== idx));
};
const serviceOptionsFilter = ({
options,
}: {
options: ComboboxItemGroup[];
}) => {
const productServiceIds = innerValue.map(
service => service.service?.id
);
return (options as ComboboxItemGroup[]).map(({ items, group }) => {
return {
group,
items: items.filter(
item =>
!productServiceIds.includes(
parseInt((item as ComboboxItem).value)
)
),
};
});
};
useEffect(() => {
onChange(innerValue as CardProductServiceSchema[]);
}, [innerValue]);
return (
<Input.Wrapper error={error}>
<Flex
direction={"column"}
gap={rem(10)}>
{innerValue.map((service, idx) => (
<Flex
key={service.service?.name || idx}
direction={"row"}
gap={rem(10)}
align={"center"}
justify={"stretch"}>
<ActionIcon
onClick={() => onDelete(idx)}
variant={"default"}>
<IconTrash />
</ActionIcon>
<ServiceWithPriceInput
serviceProps={{
onChange: event => onServiceChange(idx, event),
value: service.service,
placeholder: "Выберите услугу",
style: { width: "100%" },
filter: serviceOptionsFilter as OptionsFilter,
}}
priceProps={{
onChange: event => onQuantityChange(idx, event),
value: service.price,
placeholder: "Введите стоимость",
hideControls: true,
style: { width: "100%" },
suffix: "₽",
disabled: authState.isGuest,
}}
containerProps={{ w: "100%" }}
quantity={quantity}
/>
</Flex>
))}
<Button
onClick={onCreate}
variant={"default"}>
Добавить услугу
</Button>
</Flex>
</Input.Wrapper>
);
};
export default DealProductServiceTable;

View File

@@ -1,168 +0,0 @@
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import useDealProductsTableColumns from "./columns.tsx";
import { FC } from "react";
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
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";
import { IconBarcode, IconEdit, IconTrash } from "@tabler/icons-react";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { CreateProductRequest } from "../../../ProductsPage/types.ts";
type RestProps = {
clientId: number;
onMultipleDelete?: (items: CardProductSchema[]) => void;
};
type Props = CRUDTableProps<CardProductSchema> & RestProps;
const DealProductsTable: FC<Props> = (props: Props) => {
const { items, clientId, onChange, onCreate, onDelete, onMultipleDelete } =
props;
const columns = useDealProductsTableColumns({
onChange: (product, quantity) => {
if (!onChange) return;
if (quantity <= 0 && onDelete) {
onDelete(product);
return;
}
onChange({ ...product, quantity });
},
data: items,
});
const onCreateClick = () => {
if (!onCreate) return;
modals.openContextModal({
modal: "addCardProduct",
title: "Добавление товара",
innerProps: {
onCreate: product => onCreate(product as CardProductSchema),
clientId,
},
size: "lg",
});
};
const onPrintBarcodeClick = (product: CardProductSchema) => {
modals.openContextModal({
modal: "printBarcode",
title: "Печать штрихкода",
withCloseButton: true,
innerProps: {
productId: product.product.id,
defaultQuantity: product.quantity,
},
});
};
const onCreateProduct = (newProduct: CreateProductRequest) => {
ProductService.createProduct({
requestBody: newProduct,
}).then(({ ok, message }) => {
notifications.guess(ok, { message: message });
});
};
const onCreateProductClick = () => {
modals.openContextModal({
modal: "createProduct",
title: "Создание товара",
withCloseButton: false,
innerProps: {
clientId: clientId,
onCreate: onCreateProduct,
},
});
};
const onEditClick = (product: CardProductSchema) => {
if (!onChange) return;
modals.openContextModal({
modal: "addCardProduct",
title: "Создание товара",
withCloseButton: false,
innerProps: {
clientId: clientId,
element: product,
onChange: onChange,
},
size: "lg",
});
};
return (
<BaseTable
data={items}
columns={columns}
restProps={
{
enableBottomToolbar: true,
enableRowActions: true,
enableRowSelection: true,
renderRowActions: ({ row }) => (
<Flex gap="md">
<Tooltip label="Удалить">
<ActionIcon
onClick={() =>
onDelete && onDelete(row.original)
}
variant={"default"}>
<IconTrash />
</ActionIcon>
</Tooltip>
<Tooltip label="Печать штрихкода">
<ActionIcon
onClick={() =>
onPrintBarcodeClick(row.original)
}
variant={"default"}>
<IconBarcode />
</ActionIcon>
</Tooltip>
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => onEditClick(row.original)}
variant={"default"}>
<IconEdit />
</ActionIcon>
</Tooltip>
</Flex>
),
renderBottomToolbar: ({ table }) => (
<Flex
justify={"flex-end"}
gap={rem(10)}
p={rem(10)}>
{onMultipleDelete &&
table.getSelectedRowModel().rows.length > 0 && (
<Button
onClick={() => {
onMultipleDelete(
table
.getSelectedRowModel()
.rows.map(
row => row.original
)
);
}}
variant={"filled"}
color={"red"}>
Удалить выбранные
</Button>
)}
<Button
variant={"default"}
onClick={onCreateProductClick}>
Создать товар
</Button>
<Button
onClick={onCreateClick}
variant={"default"}>
Добавить товар в сделку
</Button>
</Flex>
),
} as MRT_TableOptions<CardProductSchema>
}
/>
);
};
export default DealProductsTable;

View File

@@ -1,107 +0,0 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { CardProductSchema } from "../../../../client";
import { List } from "@mantine/core";
type Props = {
onChange: (product: CardProductSchema, quantity: number) => void;
data: CardProductSchema[];
};
const useDealProductsTableColumns = (props: Props) => {
const { onChange, data } = props;
const totalQuantity = useMemo(
() => data.reduce((acc, row) => acc + row.quantity, 0),
[data]
);
const totalPrice = useMemo(
() =>
data.reduce(
(totalAcc, row) =>
totalAcc +
row.services.reduce(
(singleAcc, service) =>
singleAcc + service.price * row.quantity,
0
),
0
),
[data]
);
return useMemo<MRT_ColumnDef<CardProductSchema>[]>(
() => [
{
accessorKey: "product.article",
header: "Артикул",
enableSorting: false,
enableColumnActions: false,
},
{
accessorKey: "product.name",
header: "Название",
enableSorting: false,
enableColumnActions: false,
},
{
accessorKey: "product.barcodes",
header: "Штрихкоды",
Cell: ({ cell }) => {
return (
<List size={"sm"}>
{cell
.getValue<string[]>()
?.map(barcode => (
<List.Item key={barcode}>
{barcode}
</List.Item>
))}
</List>
);
},
enableSorting: false,
enableColumnActions: false,
},
{
accessorKey: "quantity",
header: "Количество",
enableSorting: false,
enableColumnActions: false,
Footer: <>Всего товаров: {totalQuantity} </>,
},
{
header: "Услуги",
Cell: ({ row }) => (
<List size={"sm"}>
{row.original.services
.map(
service =>
`${service.service.name} (${service.price}₽ за шт)`
)
.map(serviceText => (
<List.Item key={serviceText}>
{serviceText}
</List.Item>
))}
</List>
),
enableColumnActions: false,
},
{
header: "Итоговая стоимость услуг",
Cell: ({ row }) => (
<>
{row.original.services.reduce(
(acc, service) =>
acc + row.original.quantity * service.price,
0
)}
</>
),
enableColumnActions: false,
Footer: <>Всего стоимость услуг: {totalPrice}</>,
},
],
[onChange, data]
);
};
export default useDealProductsTableColumns;

View File

@@ -1,129 +0,0 @@
import { FC } from "react";
import { useDealServicesTableColumns } from "./columns.tsx";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
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";
import { openContextModal } from "@mantine/modals";
import { IconEdit, IconTrash } from "@tabler/icons-react";
type RestProps = {
onMultipleDelete?: (items: CardServiceSchema[]) => void;
};
type Props = CRUDTableProps<CardServiceSchema> & RestProps;
const DealServicesTable: FC<Props> = ({
items,
onChange,
onDelete,
onCreate,
onSelectionChange,
onMultipleDelete,
tableRef,
}) => {
const serviceIds = items.map(item => item.service.id);
const columns = useDealServicesTableColumns({
data: items,
});
const onCreateClick = () => {
if (!onCreate) return;
openContextModal({
title: "Добавление услуги",
modal: "addCardService",
innerProps: {
onCreate: event => onCreate(event as CardServiceSchema),
serviceIds,
},
});
};
const onEditClick = (service: CardServiceSchema) => {
if (!onChange) return;
openContextModal({
title: "Добавление услуги",
modal: "addCardService",
innerProps: {
element: service,
onChange,
serviceIds,
},
});
};
return (
<>
<BaseTable
ref={tableRef}
data={items}
columns={columns}
onSelectionChange={onSelectionChange}
restProps={
{
enableGrouping: true,
initialState: { grouping: ["service.category"] },
enableColumnActions: false,
enableSorting: false,
enableBottomToolbar: true,
enableRowActions: true,
enableRowSelection: true,
renderBottomToolbar: ({ table }) => (
<Flex
justify={"flex-end"}
gap={rem(10)}
p={rem(10)}>
{onMultipleDelete &&
table.getSelectedRowModel().rows.length >
0 && (
<Button
onClick={() => {
onMultipleDelete(
table
.getSelectedRowModel()
.rows.map(
row => row.original
)
);
}}
variant={"filled"}
color={"red"}>
Удалить выбранные
</Button>
)}
<Button
onClick={onCreateClick}
variant={"default"}>
Добавить услугу
</Button>
</Flex>
),
renderRowActions: ({ row }) => (
<Flex gap="md">
<Tooltip label="Удалить">
<ActionIcon
onClick={() => {
if (onDelete)
onDelete(row.original);
}}
variant={"default"}>
<IconTrash />
</ActionIcon>
</Tooltip>
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => {
onEditClick(row.original);
}}
variant={"default"}>
<IconEdit />
</ActionIcon>
</Tooltip>
</Flex>
),
} as MRT_TableOptions<CardServiceSchema>
}
/>
</>
);
};
export default DealServicesTable;

View File

@@ -1,63 +0,0 @@
import { MRT_ColumnDef } from "mantine-react-table";
import { useMemo } from "react";
import { CardServiceSchema } from "../../../../client";
type Props = {
data: CardServiceSchema[];
};
export const useDealServicesTableColumns = (props: Props) => {
const { data } = props;
const totalPrice = useMemo(
() => data.reduce((acc, row) => acc + row.quantity * row.price, 0),
[data]
);
return useMemo<MRT_ColumnDef<CardServiceSchema>[]>(
() => [
{
accessorKey: "service.category",
header: "Категория",
accessorFn: row => row.service.category.name,
},
{
enableGrouping: false,
accessorKey: "service.name",
header: "Услуга",
},
{
enableGrouping: false,
accessorKey: "price",
header: "Цена",
},
{
enableGrouping: false,
accessorKey: "quantity",
header: "Количество",
},
{
enableGrouping: false,
header: "Сумма",
Cell: ({ row }) => {
return row.original.quantity * row.original.price;
},
aggregationFn: "sum",
AggregatedCell: ({ cell }) => {
return (
<>
Итоговая сумма по категории:{" "}
{cell.row.subRows?.reduce(
(acc, row) =>
acc +
row.original.quantity * row.original.price,
0
)}
</>
);
},
Footer: <>Итоговая сумма по услугам: {totalPrice}</>,
},
],
[]
);
};

View File

@@ -2,7 +2,7 @@ import { Box, Drawer, rem, Tabs } from "@mantine/core";
import { FC, ReactNode, useEffect } from "react";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
import { IconBox, IconCalendarUser, IconCubeSend, IconSettings, IconUser, IconUsersGroup } from "@tabler/icons-react";
import CardStatusChangeTable from "../../components/DealStatusChangeTable/CardStatusChangeTable.tsx";
import CardStatusChangeTable from "../../components/CardStatusChangeTable/CardStatusChangeTable.tsx";
import GeneralTab from "../../tabs/GeneralTab/GeneralTab.tsx";
import { useQueryClient } from "@tanstack/react-query";
import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
@@ -62,8 +62,16 @@ const CardEditDrawer: FC = () => {
);
};
const getTab = (key: string, icon: ReactNode, label: string) => {
if (!modules.has(key)) return;
const getTab = (
key: string,
icon: ReactNode,
label: string,
enablingModules: string[] | null = null, // Show if at least one of modules is in project
) => {
if (!enablingModules) {
enablingModules = [key];
}
if (!enablingModules.some(key => modules.has(key))) return;
return (
<Tabs.Tab
@@ -102,23 +110,19 @@ const CardEditDrawer: FC = () => {
leftSection={<IconSettings />}>
Общее
</Tabs.Tab>
<Tabs.Tab
value={"client"}
leftSection={<IconUser />}>
Клиент
</Tabs.Tab>
<Tabs.Tab
value={"history"}
leftSection={<IconCalendarUser />}>
История
</Tabs.Tab>
{getTab("clients", <IconUser />, "Клиент", ["servicesAndProducts", "clients"])}
{getTab("servicesAndProducts", <IconBox />, "Товары и услуги")}
{getTab("shipment", <IconCubeSend />, "Отгрузка")}
{getTab("employees", <IconUsersGroup />, "Исполнители")}
</Tabs.List>
{getTabPanel("general", <GeneralTab />)}
{getTabPanel("client", <ClientTab />)}
{getTabPanel("clients", <ClientTab />)}
{getTabPanel("history", <CardEditDrawerStatusChangeTable />)}
{getTabPanel("servicesAndProducts", <ProductAndServiceTab />)}
{getTabPanel("shipment", <ShippingTab />)}

View File

@@ -12,6 +12,8 @@ const ClientTab = () => {
const initialValues: CardGeneralFormType = card as CardSchema;
const queryClient = useQueryClient();
if (!card?.client) return;
const form = useForm<CardGeneralFormType>(
{
initialValues: initialValues,
@@ -22,6 +24,8 @@ const ClientTab = () => {
);
const hasChanges = !isEqual(form.values, initialValues);
const updateClientInfo = async (values: CardGeneralFormType) => {
if (!values.client) return;
return ClientService.updateClient({
requestBody: {
data: values.client,

View File

@@ -1,6 +1,18 @@
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 {
Button,
Checkbox,
Divider,
Fieldset,
Flex,
Group,
rem,
ScrollArea,
Stack,
Textarea,
TextInput,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import {
CardSchema,
@@ -44,6 +56,7 @@ const Content: FC<Props> = ({ card }) => {
const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, card.board.project);
const isManagerIncluded = isModuleInProject(Modules.MANAGERS, card.board.project);
const getInitialValues = (card: CardSchema): CardGeneralFormType => {
return {
@@ -67,8 +80,6 @@ const Content: FC<Props> = ({ card }) => {
});
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) => {
@@ -104,6 +115,7 @@ const Content: FC<Props> = ({ card }) => {
});
};
const updateClientInfo = async (values: CardGeneralFormType) => {
if (!values.client) return;
return ClientService.updateClient({
requestBody: {
data: values.client,
@@ -116,7 +128,9 @@ const Content: FC<Props> = ({ card }) => {
await updateClientInfo(values);
}
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse) ? values.shippingWarehouse.name : values.shippingWarehouse;
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse)
? values.shippingWarehouse.name
: values.shippingWarehouse;
await updateCardInfo(
{
...values,
@@ -128,7 +142,7 @@ const Content: FC<Props> = ({ card }) => {
const isShippingWarehouse = (
value: ShippingWarehouseSchema | string | null | undefined,
): value is ShippingWarehouseSchema => {
return !["string", "null", "undefined"].includes(typeof value);
return !!value && !["string"].includes(typeof value);
};
const onCopyGuestUrlClick = () => {
@@ -146,87 +160,101 @@ const Content: FC<Props> = ({ card }) => {
<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>
h={"95vh"}
>
<ScrollArea>
<Stack>
<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")}
/>
{isServicesAndProductsIncluded && (
<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);
}}
/>
)}
{isManagerIncluded && (
<ManagerSelect
placeholder={"Укажите менеджера"}
label={"Менеджер"}
{...form.getInputProps("manager")}
/>
)}
{isServicesAndProductsIncluded && (
<Checkbox
label={"Учет выручки с услуг в статистике"}
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
/>
)}
{project && (
<CardAttributeFields
project={project}
form={form}
/>
)}
</Flex>
</Fieldset>
</Stack>
</ScrollArea>
<Flex
mt={"md"}
gap={rem(10)}
@@ -241,7 +269,7 @@ const Content: FC<Props> = ({ card }) => {
align={"center"}
justify={"space-between"}>
{isServicesAndProductsIncluded && (
<PrintDealBarcodesButton card={card}/>
<PrintDealBarcodesButton card={card} />
)}
<Flex gap={rem(10)}>
{isServicesAndProductsIncluded && (
@@ -303,7 +331,9 @@ const Content: FC<Props> = ({ card }) => {
const GeneralTab: FC = () => {
const { selectedCard } = useCardPageContext();
if (!selectedCard) return <>No card selected</>;
return <Content card={selectedCard} />;
return (
<Content card={selectedCard} />
);
};
export default GeneralTab;

View File

@@ -30,7 +30,7 @@ const ProductAndServiceTab: FC = () => {
useCardProductAndServiceTabState();
const isLocked = Boolean(cardState.card?.billRequest || cardState.card?.group?.billRequest);
const onAddProductClick = () => {
if (!cardProductsState.onCreate || !cardState.card) return;
if (!cardProductsState.onCreate || !cardState.card || !cardState.card.clientId) return;
const productIds = cardState.card.products.map(
product => product.product.id
);
@@ -108,7 +108,7 @@ const ProductAndServiceTab: FC = () => {
await cardState.refetch();
});
};
const onDealKitAdd = (kit: GetServiceKitSchema) => {
const onCardKitAdd = (kit: GetServiceKitSchema) => {
if (!cardState.card) return;
CardService.addKitToCard({
requestBody: {
@@ -130,7 +130,7 @@ const ProductAndServiceTab: FC = () => {
});
};
const onCreateProductClick = () => {
if (!cardState.card) return;
if (!cardState.card || !cardState.card.clientId) return;
modals.openContextModal({
modal: "createProduct",
title: "Создание товара",
@@ -240,7 +240,7 @@ const ProductAndServiceTab: FC = () => {
direction={"column"}
className={styles["deal-container-wrapper"]}>
<CardServicesTable
onKitAdd={onDealKitAdd}
onKitAdd={onCardKitAdd}
{...cardServicesState}
/>

View File

@@ -5,14 +5,14 @@ import CardEditDrawer from "../drawers/CardEditDrawer/CardEditDrawer.tsx";
import { CardPageContextProvider } from "../contexts/CardPageContext.tsx";
import { rem } from "@mantine/core";
import useCardsPageState from "../hooks/useCardsPageState.tsx";
import CardsTable from "../components/DealsTable/CardsTable.tsx";
import CardsTable from "../components/CardsTable/CardsTable.tsx";
import { motion } from "framer-motion";
import CardPrefillDrawer from "../drawers/CardPrefillDrawer/CardPrefillDrawer.tsx";
import { PrefillCardContextProvider } from "../contexts/PrefillCardContext.tsx";
import { useParams } from "@tanstack/react-router";
import { PrefillCardsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
import DisplayMode from "../enums/DisplayMode.ts";
import LeadsPageHeader from "../components/LeadsPageHeader/LeadsPageHeader.tsx";
import CardsPageHeader from "../components/CardsPageHeader/CardsPageHeader.tsx";
import useProjects from "../hooks/useProjects.tsx";
import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
import useBoards from "../hooks/useBoards.tsx";
@@ -81,7 +81,7 @@ export const CardsPage: FC = () => {
>
<PrefillCardContextProvider>
<PrefillCardsWithExcelContextProvider>
<LeadsPageHeader
<CardsPageHeader
form={form}
displayMode={displayMode}
setDisplayMode={setDisplayMode}

View File

@@ -2,10 +2,20 @@ import { ProjectSchema } from "../../../client";
export enum Modules {
SERVICES_AND_PRODUCTS = "servicesAndProducts",
SHIPMENT = "shipment",
EMPLOYEES = "employees",
CLIENTS = "clients",
MANAGERS = "managers",
}
const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => {
console.log(module.toString());
// if servicesAndProducts included, then clients also included
if (module === Modules.CLIENTS) {
if (project?.modules.findIndex(m => m.key === Modules.SERVICES_AND_PRODUCTS.toString()) !== -1) {
return true;
}
}
return project?.modules.findIndex(m => m.key === module.toString()) !== -1;
};

View File

@@ -3,7 +3,7 @@ import { CardSummary, ShippingWarehouseSchema } from "../../../client";
import { ContextModalProps } from "@mantine/modals";
import { useForm } from "@mantine/form";
import { Input, TextInput } from "@mantine/core";
import CardsTable from "../../CardsPage/components/DealsTable/CardsTable.tsx";
import CardsTable from "../../CardsPage/components/CardsTable/CardsTable.tsx";
import { CardPageContextProvider } from "../../CardsPage/contexts/CardPageContext.tsx";
import CardEditDrawer from "../../CardsPage/drawers/CardEditDrawer/CardEditDrawer.tsx";

View File

@@ -27,7 +27,7 @@ export const ProfitChart = () => {
{ name: "expenses", label: "Расходы", color: "red.6" },
],
[
{ name: "dealsCount", label: "Количество сделок", color: "indigo.6" },
{ name: "cardsCount", label: "Количество сделок", color: "indigo.6" },
],
];
@@ -54,6 +54,7 @@ export const ProfitChart = () => {
valueFormatter={(value) => new Intl.NumberFormat("ru-RU").format(value)}
series={series}
fillOpacity={0.5}
key={idx}
/>
);
})}

View File

@@ -62,7 +62,7 @@ export const useProfitTableColumns = ({ groupTableBy, statuses }: Props) => {
size: groupTableBy === GroupStatisticsTable.BY_DATES ? 40 : 80,
},
{
accessorKey: "dealsCount",
accessorKey: "cardsCount",
header: "Кол-во",
size: 40,
},