feat: a few tabs for module, deal services tab for mobiles

This commit is contained in:
2025-09-21 09:47:55 +04:00
parent 6e445d5ebf
commit 6d6c430e88
62 changed files with 521 additions and 128 deletions

View File

@ -1,6 +1,7 @@
.tab {
@media (max-width: 48em) {
width: 100%;
border-bottom-width: 3px;
}
}

View File

@ -2,11 +2,12 @@ import React, { FC, ReactNode } from "react";
import { IconEdit, IconHistory } from "@tabler/icons-react";
import { motion } from "framer-motion";
import { Box, Tabs, Text } from "@mantine/core";
import TabsList from "@/app/deals/drawers/DealEditorDrawer/components/TabsList";
import DealStatusHistoryTab from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/DealStatusHistoryTab";
import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema, ProjectSchema } from "@/lib/client";
import { MODULES } from "@/modules/modules";
import MODULES from "@/modules/modules";
import styles from "../DealEditorDrawer.module.css";
type Props = {
@ -50,29 +51,58 @@ const DealEditorBody: FC<Props> = props => {
</Tabs.Tab>
);
const getModuleTabs = () =>
props.project?.builtInModules.map(module => {
const info = MODULES[module.key].renderInfo;
return getTab(info.key, info.label, info.icon);
});
const getModuleTabs = (): ReactNode[] => {
if (!props.project) return [];
const tabs: ReactNode[] = [];
const getModuleTabPanels = () =>
props.project?.builtInModules.map(module =>
getTabPanel(module.key, MODULES[module.key]?.getTab?.(props))
);
for (const module of props.project.builtInModules) {
const moduleInfo = MODULES[module.key];
for (const tab of moduleInfo.tabs) {
if (
(tab.device === "desktop" && isMobile) ||
(tab.device === "mobile" && !isMobile)
)
continue;
tabs.push(getTab(tab.key, tab.label, tab.icon));
}
}
return tabs;
};
const getModuleTabPanels = () => {
if (!props.project) return [];
const tabPanels: ReactNode[] = [];
for (const module of props.project.builtInModules) {
const moduleInfo = MODULES[module.key];
for (const tab of moduleInfo.tabs) {
if (
(tab.device === "desktop" && isMobile) ||
(tab.device === "mobile" && !isMobile)
)
continue;
tabPanels.push(getTabPanel(tab.key, tab.tab(props)));
}
}
return tabPanels;
};
return (
<Tabs
defaultValue="general"
orientation={isMobile ? "horizontal" : "vertical"}
h={"97vh"}
mih={"97vh"}
h={isMobile ? "min-content" : "97vh"}
mih={isMobile ? "min-content" : "97vh"}
classNames={{ tab: styles.tab }}>
<Tabs.List>
<TabsList>
{getTab("general", "Общая информация", <IconEdit />)}
{getTab("history", "История", <IconHistory />)}
{getModuleTabs()}
</Tabs.List>
</TabsList>
{getTabPanel("general", <GeneralTab {...props} />)}
{getTabPanel("history", <DealStatusHistoryTab {...props} />)}

View File

@ -0,0 +1,30 @@
import { FC, ReactNode } from "react";
import { ScrollArea, Tabs } from "@mantine/core";
import useIsMobile from "@/hooks/utils/useIsMobile";
type Props = {
children: ReactNode;
};
const TabsList: FC<Props> = ({ children }) => {
const isMobile = useIsMobile();
if (!isMobile) {
return <Tabs.List>{children}</Tabs.List>;
}
return (
<ScrollArea scrollbarSize={0}>
<Tabs.List
style={{
flexWrap: "nowrap",
minWidth: "100%",
width: "max-content",
}}>
{children}
</Tabs.List>
</ScrollArea>
);
};
export default TabsList;

View File

@ -0,0 +1,24 @@
import { ReactNode } from "react";
import { Button, ButtonProps, Group } from "@mantine/core";
interface Props extends ButtonProps {
children?: ReactNode;
onClick?: () => void;
}
const InlineButton = ({ children, onClick, ...props }: Props) => {
return (
<Button
variant="default"
onClick={onClick}
{...props}>
<Group
gap="sm"
wrap={"nowrap"}>
{children}
</Group>
</Button>
);
};
export default InlineButton;

View File

@ -38,10 +38,6 @@ export type BuiltInModuleSchemaInput = {
* Label
*/
label: string;
/**
* Iconname
*/
iconName: string;
/**
* Description
*/
@ -50,6 +46,10 @@ export type BuiltInModuleSchemaInput = {
* Dependson
*/
dependsOn: Array<BuiltInModuleSchemaInput>;
/**
* Tabs
*/
tabs: Array<BuiltInModuleTabSchema>;
};
/**
@ -68,10 +68,6 @@ export type BuiltInModuleSchemaOutput = {
* Label
*/
label: string;
/**
* Iconname
*/
iconName: string;
/**
* Description
*/
@ -80,6 +76,32 @@ export type BuiltInModuleSchemaOutput = {
* Dependson
*/
dependsOn: Array<BuiltInModuleSchemaOutput>;
/**
* Tabs
*/
tabs: Array<BuiltInModuleTabSchema>;
};
/**
* BuiltInModuleTabSchema
*/
export type BuiltInModuleTabSchema = {
/**
* Id
*/
id: number;
/**
* Label
*/
label: string;
/**
* Iconname
*/
iconName: string;
/**
* Device
*/
device: string;
};
/**

View File

@ -12,6 +12,16 @@ export const zBoardSchema = z.object({
projectId: z.int(),
});
/**
* BuiltInModuleTabSchema
*/
export const zBuiltInModuleTabSchema = z.object({
id: z.int(),
label: z.string(),
iconName: z.string(),
device: z.string(),
});
/**
* BuiltInModuleSchema
*/
@ -19,7 +29,6 @@ export const zBuiltInModuleSchemaInput = z.object({
id: z.int(),
key: z.string(),
label: z.string(),
iconName: z.string(),
description: z.string(),
get dependsOn() {
return z.array(
@ -28,6 +37,7 @@ export const zBuiltInModuleSchemaInput = z.object({
})
);
},
tabs: z.array(zBuiltInModuleTabSchema),
});
/**
@ -37,7 +47,6 @@ export const zBuiltInModuleSchemaOutput = z.object({
id: z.int(),
key: z.string(),
label: z.string(),
iconName: z.string(),
description: z.string(),
get dependsOn() {
return z.array(
@ -46,6 +55,7 @@ export const zBuiltInModuleSchemaOutput = z.object({
})
);
},
tabs: z.array(zBuiltInModuleTabSchema),
});
/**

View File

@ -2,12 +2,14 @@ import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/De
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
import DealProductEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DealProductEditorModal/DealProductEditorModal";
import DealServiceEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DealServiceEditorModal/DealServiceEditorModal";
import DuplicateServicesModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DuplicateServicesModal/DuplicateServicesModal";
import ProductEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ProductEditorModal/ProductEditorModal";
import ProductServiceEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ProductServiceEditorModal/ProductServiceEditorModal";
import ServicesKitSelectModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ServicesKitSelectModal/ServicesKitSelectModal";
import {
DealProductEditorModal,
DealServiceEditorModal,
DuplicateServicesModal,
ProductEditorModal,
ProductServiceEditorModal,
ServicesKitSelectModal,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals";
export const modals = {
enterNameModal: EnterNameModal,

View File

@ -1,13 +0,0 @@
import FulfillmentBaseTab from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/FulfillmentBaseTab";
import { ModuleNames } from "./modules";
import ModulesType from "./types";
const connectModules = (modules: ModulesType) => {
modules[ModuleNames.FULFILLMENT_BASE].getTab = props => (
<FulfillmentBaseTab {...props} />
);
return modules;
};
export default connectModules;

View File

@ -1,7 +1,7 @@
import { FC } from "react";
import { DealSchema } from "@/lib/client";
import FulfillmentBaseTabBody from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/FulfillmentBaseTabBody/FulfillmentBaseTabBody";
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import FulfillmentBaseTabBody from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/FulfillmentBaseTabBody/FulfillmentBaseTabBody";
type Props = {
value: DealSchema;

View File

@ -1,8 +1,8 @@
import { Flex, Stack } from "@mantine/core";
import DealServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/DealServicesTable";
import ProductsActions from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/ProductsActions/ProductsActions";
import TotalPriceLabel from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/TotalPriceLabel/TotalPriceLabel";
import styles from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/FulfillmentBaseTab.module.css";
import ProductsActions from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/components/ProductsActions/ProductsActions";
import TotalPriceLabel from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/components/TotalPriceLabel/TotalPriceLabel";
import DealServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealServicesTable/DealServicesTable";
import styles from "@/modules/dealModularEditorTabs/FulfillmentBase/FulfillmentBase.module.css";
const DealInfoView = () => (
<Stack

View File

@ -1,7 +1,7 @@
import { FC } from "react";
import { Button, Flex } from "@mantine/core";
import { modals } from "@mantine/modals";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
const ProductsActions: FC = () => {
const { deal, dealProductsList, productsCrud, dealProductsCrud } =

View File

@ -1,7 +1,7 @@
import { useMemo } from "react";
import { Flex, Title } from "@mantine/core";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import styles from "../../../../FulfillmentBaseTab.module.css";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import styles from "../../../../../FulfillmentBase.module.css";
const TotalPriceLabel = () => {
const {

View File

@ -1,16 +1,14 @@
import { FC } from "react";
import { Flex, ScrollArea } from "@mantine/core";
import DealServicesTitle from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTitle";
import DealServicesTotalLabel from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTotalLabel";
import ServicesActions from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/ServicesActions";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import DealServiceRow from "./components/DealServiceRow";
import DealServiceRow from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealServicesTable/components/DealServiceRow";
import DealServicesTitle from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealServicesTable/components/DealServicesTitle";
import ServicesActions from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealServicesTable/components/ServicesActions";
import DealServicesTotalLabel from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/DealServicesTotalLabel/DealServicesTotalLabel";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
const DealServicesTable: FC = () => {
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
// const isLocked = isDealLocked(deal); // TODO bills
return (
<Flex
direction={"column"}
@ -20,7 +18,7 @@ const DealServicesTable: FC = () => {
<ScrollArea
flex={1}
scrollbars={"y"}
offsetScrollbars={"y"}>
offsetScrollbars={"present"}>
<Flex
direction={"column"}
gap={"sm"}>
@ -34,7 +32,7 @@ const DealServicesTable: FC = () => {
))}
</Flex>
</ScrollArea>
<DealServicesTotalLabel />
<DealServicesTotalLabel mt={"sm"} />
<ServicesActions />
</Flex>
);

View File

@ -1,11 +1,11 @@
import { FC } from "react";
import { IconTrash } from "@tabler/icons-react";
import { isNumber } from "lodash";
import { Divider, Group, NumberInput, Stack, Text } from "@mantine/core";
import { Divider, Group, NumberInput, rem, Stack, Text } from "@mantine/core";
import { useDebouncedCallback } from "@mantine/hooks";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import { DealServiceSchema } from "@/lib/client";
import LockCheckbox from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/LockCheckbox/LockCheckbox";
import LockCheckbox from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/LockCheckbox/LockCheckbox";
type Props = {
value: DealServiceSchema;
@ -64,6 +64,7 @@ const DealServiceRow: FC<Props> = ({ value, onChange, onDelete }) => {
display: "flex",
cursor: "pointer",
pointerEvents: "auto",
paddingRight: rem(10),
},
}}
rightSection={

View File

@ -1,8 +1,8 @@
import { Button, Flex } from "@mantine/core";
import { modals } from "@mantine/modals";
import { addKitToDeal, ServicesKitSchema } from "@/lib/client";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
const ServicesActions = () => {
const { dealServicesList, dealServicesCrud, deal } =

View File

@ -1,7 +1,7 @@
import { Flex, ScrollArea, Stack } from "@mantine/core";
import DealInfoView from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/DealInfoView";
import ProductView from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/ProductView";
import { useFulfillmentBaseContext } from "../../contexts/FulfillmentBaseContext";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import DealInfoView from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/DealInfoView/DealInfoView";
import ProductView from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/ProductView";
const FulfillmentBaseTabBody = () => {
const { dealProductsList } = useFulfillmentBaseContext();

View File

@ -6,7 +6,7 @@ import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { ProductSchema } from "@/lib/client";
import useProductsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useProductsList";
import useProductsList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useProductsList";
import renderProductOption from "./utils/renderProductOption";
type RestProps = {

View File

@ -5,7 +5,7 @@ import {
Tooltip,
} from "@mantine/core";
import { ProductSchema } from "@/lib/client";
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductFieldsList";
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/components/ProductFieldsList";
const renderProductOption = (
products: ProductSchema[]

View File

@ -16,12 +16,12 @@ import {
duplicateProductServices,
ServicesKitSchema,
} from "@/lib/client";
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductFieldsList";
import ProductViewActions from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductViewActions";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/components/ProductFieldsList";
import ProductViewActions from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/components/ProductViewActions";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
import ProductServicesTable from "./components/ProductServicesTable";
import styles from "../../FulfillmentBaseTab.module.css";
import styles from "../../../FulfillmentBase.module.css";
type Props = {
dealProduct: DealProductSchema;

View File

@ -5,8 +5,8 @@ import { modals } from "@mantine/modals";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import { DealProductSchema, ProductServiceSchema } from "@/lib/client";
import useProductServicesTableColumns from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/hooks/useProductServicesTableColumns";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import useProductServicesTableColumns from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/hooks/useProductServicesTableColumns";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
type Props = {
dealProduct: DealProductSchema;

View File

@ -4,7 +4,7 @@ import { Flex } from "@mantine/core";
import { modals } from "@mantine/modals";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import { DealProductSchema } from "@/lib/client";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
type Props = {
dealProduct: DealProductSchema;

View File

@ -9,8 +9,8 @@ import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { ServiceSchema } from "@/lib/client";
import useServicesList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesList";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
import useServicesList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type RestProps = {
filterType?: ServiceType;

View File

@ -0,0 +1,18 @@
import { FC } from "react";
import { DealSchema } from "@/lib/client";
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import DealServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealServiceTable/DealServicesTable";
type Props = {
value: DealSchema;
};
const CommonServicesTab: FC<Props> = ({ value }) => {
return (
<FulfillmentBaseContextProvider deal={value}>
<DealServicesTable />
</FulfillmentBaseContextProvider>
);
};
export default CommonServicesTab;

View File

@ -0,0 +1,17 @@
import { FC } from "react";
import { DealSchema } from "@/lib/client";
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
type Props = {
value: DealSchema;
};
const ProductsTab: FC<Props> = ({ value }) => {
return (
<FulfillmentBaseContextProvider deal={value}>
<></>
</FulfillmentBaseContextProvider>
);
};
export default ProductsTab;

View File

@ -0,0 +1,40 @@
import { FC } from "react";
import { IconPlus } from "@tabler/icons-react";
import { ButtonProps, Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
type Props = ButtonProps;
const AddDealServiceButton: FC<Props> = props => {
const { dealServicesList, dealServicesCrud, deal } =
useFulfillmentBaseContext();
const onCreateClick = () => {
const serviceIdsToExclude = dealServicesList.dealServices.map(
service => service.service.id
);
modals.openContextModal({
modal: "dealServiceEditorModal",
innerProps: {
onCreate: values =>
dealServicesCrud.onCreate({ ...values, dealId: deal.id }),
serviceIdsToExclude,
isEditing: false,
},
withCloseButton: false,
});
};
return (
<InlineButton
{...props}
onClick={onCreateClick}>
<IconPlus />
<Text>Добавить услугу</Text>
</InlineButton>
);
};
export default AddDealServiceButton;

View File

@ -0,0 +1,66 @@
import { FC } from "react";
import { Flex, ScrollArea, Stack } from "@mantine/core";
import { modals } from "@mantine/modals";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import { DealServiceSchema } from "@/lib/client";
import AddDealServiceButton from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/AddDealServiceButton/AddDealServiceButton";
import useDealServicesTableColumns from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/components/DealServiceTable/useDealServicesTableColumns";
import DealServicesTotalLabel from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/DealServicesTotalLabel/DealServicesTotalLabel";
import { useFulfillmentBaseContext } from "../../../shared/contexts/FulfillmentBaseContext";
const DealServicesTable: FC = () => {
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
const onChange = (item: DealServiceSchema) => {
const serviceIdsToExclude = dealServicesList.dealServices.map(
dealService => dealService.service.id
);
modals.openContextModal({
modal: "dealServiceEditorModal",
innerProps: {
entity: item,
onChange: values =>
dealServicesCrud.onUpdate(
item.dealId,
item.serviceId,
values
),
serviceIdsToExclude,
isEditing: true,
},
withCloseButton: false,
});
};
const columns = useDealServicesTableColumns({
onDelete: dealServicesCrud.onDelete,
onChange,
});
return (
<ScrollArea
m={"xs"}
h={"calc(100vh - 140px)"}
scrollbars={"y"}
type={"scroll"}>
<Stack gap={"sm"}>
<Flex
p={"sm"}
bd={"1px solid var(--mantine-color-default-border)"}
bdrs={"lg"}>
<DealServicesTotalLabel order={5} />
</Flex>
<BaseTable
records={dealServicesList.dealServices}
columns={columns}
groups={undefined}
idAccessor={"serviceId"}
withTableBorder
/>
<AddDealServiceButton size={"lg"} />
</Stack>
</ScrollArea>
);
};
export default DealServicesTable;

View File

@ -0,0 +1,57 @@
import { useMemo } from "react";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { ActionIcon, Flex } from "@mantine/core";
import { DealServiceSchema } from "@/lib/client";
type Props = {
onChange: (dealService: DealServiceSchema) => void;
onDelete: (dealService: DealServiceSchema) => void;
};
const useDealServicesTableColumns = ({ onChange, onDelete }: Props) => {
return useMemo(
() =>
[
{
accessor: "service.name",
title: "Название",
width: "53%",
},
{
accessor: "quantity",
title: "Кол-во",
width: "17%",
},
{
accessor: "price",
title: "Цена",
width: "30%",
},
{
accessor: "actions",
title: "Действия",
textAlign: "center",
width: "0%",
render: dealService => (
<Flex gap={"sm"}>
<ActionIcon
variant={"subtle"}
c={"red"}
onClick={() => onDelete(dealService)}>
<IconTrash />
</ActionIcon>
<ActionIcon
variant={"subtle"}
onClick={() => onChange(dealService)}>
<IconEdit />
</ActionIcon>
</Flex>
),
},
] as DataTableColumn<DealServiceSchema>[],
[onChange, onDelete]
);
};
export default useDealServicesTableColumns;

View File

@ -1,8 +1,10 @@
import { useMemo } from "react";
import { Title } from "@mantine/core";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
import { FC, useMemo } from "react";
import { Title, TitleProps } from "@mantine/core";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
const DealServicesTotalLabel = () => {
type Props = TitleProps;
const DealServicesTotalLabel: FC<Props> = ({ order = 3, ...props }) => {
const { dealServicesList } = useFulfillmentBaseContext();
const total = useMemo(
() =>
@ -16,9 +18,9 @@ const DealServicesTotalLabel = () => {
return (
<Title
style={{ textAlign: "end" }}
mt={"sm"}
order={3}>
Итог: {total.toFixed(2)}
order={order}
{...props}>
Итог: {total.toLocaleString("ru")}
</Title>
);
};

View File

@ -3,22 +3,25 @@
import { DealSchema } from "@/lib/client";
import { getProductsQueryKey } from "@/lib/client/@tanstack/react-query.gen";
import makeContext from "@/lib/contextFactory/contextFactory";
import useDealServicesCrud, {
DealServicesCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/cruds/useDealServiceCrud";
import useProductServiceCrud, {
DealProductServicesCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/cruds/useProductServiceCrud";
import useDealServicesList, {
DealServicesList,
} from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useDealServicesList";
import useDealProductCrud, {
DealProductsCrud,
} from "../hooks/cruds/useDealProductCrud";
import { ProductsCrud, useProductsCrud } from "../hooks/cruds/useProductsCrud";
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useDealProductCrud";
import useDealServicesCrud, {
DealServicesCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useDealServicesCrud";
import {
ProductsCrud,
useProductsCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useProductsCrud";
import useProductServiceCrud, {
DealProductServicesCrud,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useProductServiceCrud";
import useDealProductsList, {
DealProductsList,
} from "../hooks/lists/useDealProductsList";
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useDealProductsList";
import useDealServicesList, {
DealServicesList,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useDealServicesList";
type FulfillmentBaseContextState = {
deal: DealSchema;

View File

@ -29,7 +29,7 @@ export type DealServicesCrud = {
onDelete: (data: DealServiceSchema, onSuccess?: () => void) => void;
};
const useDealServiceCrud = ({ dealId }: Props): DealServicesCrud => {
const useDealServicesCrud = ({ dealId }: Props): DealServicesCrud => {
const queryKey = getDealServicesQueryKey({ path: { dealId } });
const key = "getDealServices";
const { queryClient, onError, onSettled } = getCommonQueryClient({
@ -150,4 +150,4 @@ const useDealServiceCrud = ({ dealId }: Props): DealServicesCrud => {
};
};
export default useDealServiceCrud;
export default useDealServicesCrud;

View File

@ -11,7 +11,8 @@ import {
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
import ProductSelect from "../../components/ProductSelect/ProductSelect";
import ProductSelect
from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductSelect/ProductSelect";
type RestProps = {
clientId: number;

View File

@ -11,7 +11,7 @@ import {
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
import ServiceWithPriceInput from "./components/ServiceWithPriceInput";
type RestProps = {

View File

@ -9,8 +9,8 @@ import {
} from "@mantine/core";
import { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect";
import { ServiceSchema } from "@/lib/client";
import ServiceSelect from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ServiceSelect/ServiceSelect";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
import ServiceSelect from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ServiceSelect/ServiceSelect";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, "data">;
type PriceProps = NumberInputProps;

View File

@ -11,7 +11,7 @@ import {
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductImageDropzone/ProductImageDropzone";
import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductImageDropzone/ProductImageDropzone";
import BaseFormInputProps from "@/utils/baseFormInputProps";
type Props = CreateEditFormProps<

View File

@ -12,8 +12,8 @@ import {
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
import ServiceWithPriceInput from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DealServiceEditorModal/components/ServiceWithPriceInput";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
import ServiceWithPriceInput from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/DealServiceEditorModal/components/ServiceWithPriceInput";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type RestProps = {
quantity: number;

View File

@ -5,7 +5,7 @@ import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import { ServicesKitSchema } from "@/lib/client";
import BaseFormModalActions from "@/modals/base/BaseFormModal/BaseFormModalActions";
import ServicesKitSelect from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ServicesKitSelectModal/components/ServicesKitSelect";
import ServicesKitSelect from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ServicesKitSelectModal/components/ServicesKitSelect";
type Props = {
onSelect: (kit: ServicesKitSchema) => void;

View File

@ -3,7 +3,7 @@ import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { ServicesKitSchema } from "@/lib/client";
import useServicesKitsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesKitsList";
import useServicesKitsList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesKitsList";
type Props = Omit<ObjectSelectProps<ServicesKitSchema>, "data">;

View File

@ -0,0 +1,6 @@
export { default as ServicesKitSelectModal } from "./ServicesKitSelectModal/ServicesKitSelectModal";
export { default as ProductEditorModal } from "./ProductEditorModal/ProductEditorModal";
export { default as DuplicateServicesModal } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/DuplicateServicesModal/DuplicateServicesModal";
export { default as DealServiceEditorModal } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/DealServiceEditorModal/DealServiceEditorModal";
export { default as DealProductEditorModal } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/DealProductEditorModal/DealProductEditorModal";
export { default as ProductServiceEditorModal } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductServiceEditorModal/ProductServiceEditorModal";

View File

@ -0,0 +1,3 @@
export { default as CommonServicesTab } from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/CommonServicesTab";
export { default as FulfillmentBaseTab } from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/FulfillmentBaseTab";
export { default as ProductsTab } from "@/modules/dealModularEditorTabs/FulfillmentBase/mobile/ProductsTab";

View File

@ -1,21 +1,48 @@
import {
IconColumns,
IconBox,
} from "@tabler/icons-react";
import {
CommonServicesTab,
ProductsTab,
FulfillmentBaseTab,
} from "./dealModularEditorTabs";
import ModulesType from "./types";
import connectModules from "./connectModules";
export enum ModuleNames {
FULFILLMENT_BASE = "fulfillment_base",
}
const modules: ModulesType = {
const MODULES: ModulesType = {
[ModuleNames.FULFILLMENT_BASE]: {
renderInfo: {
info: {
key: "fulfillment_base",
label: "Фулфиллмент",
},
tabs: [
{
label: "Услуги",
key: "common_services",
icon: <IconColumns />,
device: "mobile",
tab: (props: any) => <CommonServicesTab {...props} key={"common_services"} />,
},
{
label: "Товары",
key: "products",
icon: <IconBox />,
device: "mobile",
tab: (props: any) => <ProductsTab {...props} key={"products"} />,
},
{
label: "Фулфиллмент",
key: "fulfillment_base",
icon: <IconBox />,
device: "desktop",
tab: (props: any) => <FulfillmentBaseTab {...props} key={"fulfillment_base"} />,
},
]
},
};
export const MODULES = connectModules(modules);
export default MODULES;

View File

@ -4,12 +4,19 @@ import axios, { AxiosResponse } from "axios";
import * as handlebars from "handlebars";
// region types
type Module = {
type ModuleTab = {
id: number;
key: string;
label: string;
iconName: string;
moduleId: number;
};
type Module = {
id: number;
label: string;
description: string;
tabs: ModuleTab[];
};
type ModulesResponse = {
@ -59,10 +66,23 @@ handlebars.registerHelper("uppercase", text => {
return text.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
});
function pascalcase(s: string): string {
return s.replace(/(?:^|_+)([a-zA-Z0-9])/g, (_, c) => c.toUpperCase());
}
handlebars.registerHelper("pascalcase", pascalcase);
const generateRows = (modules: Module[]) => {
try {
const iconsToImport = new Set<string>();
for (const module of modules) {
for (const tab of module.tabs) {
iconsToImport.add(tab.iconName);
}
}
const data = {
modules,
iconsToImport: Array.from(iconsToImport),
};
const tsxContent = template(data);
fs.writeFileSync(OUTPUT_PATH, tsxContent);

View File

@ -1,10 +1,16 @@
import {
{{#each modules}}
{{#if this.iconName}}{{this.iconName}},{{/if}}
{{#each iconsToImport}}
{{this}},
{{/each}}
} from "@tabler/icons-react";
import {
{{#each modules}}
{{#each this.tabs}}
{{pascalcase this.key}}Tab,
{{/each}}
{{/each}}
} from "./dealModularEditorTabs";
import ModulesType from "./types";
import connectModules from "./connectModules";
export enum ModuleNames {
{{#each modules}}
@ -12,16 +18,26 @@ export enum ModuleNames {
{{/each}}
}
const modules: ModulesType = {
const MODULES: ModulesType = {
{{#each modules}}
[ModuleNames.{{uppercase this.key}}]: {
renderInfo: {
info: {
key: "{{this.key}}",
label: "{{this.label}}",
},
tabs: [
{{#each this.tabs}}
{
label: "{{this.label}}",
key: "{{this.key}}",
icon: {{#if this.iconName}}<{{this.iconName}} />{{else}}None{{/if}},
device: "{{this.device}}",
tab: (props: any) => <{{pascalcase this.key}}Tab {...props} key={"{{this.key}}"} />,
},
{{/each}}
]
},
{{/each}}
};
export const MODULES = connectModules(modules);
export default MODULES;

View File

@ -1,12 +1,24 @@
import { ReactNode } from "react";
export type Module = {
renderInfo: {
type GetTab = (props: any) => ReactNode[] | ReactNode;
export type ModuleTab = {
label: string;
key: string;
icon: ReactNode;
device: "mobile" | "desktop" | "both";
tab: (props: any) => ReactNode;
};
export type Module = {
info: {
label: string;
key: string;
};
getTab?: (props: any) => ReactNode;
tabs: ModuleTab[];
getTabs?: GetTab;
getMobileTabs?: GetTab;
getDesktopTabs?: GetTab;
};
type ModulesType = {