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 { .tab {
@media (max-width: 48em) { @media (max-width: 48em) {
width: 100%;
border-bottom-width: 3px; border-bottom-width: 3px;
} }
} }

View File

@ -2,11 +2,12 @@ import React, { FC, ReactNode } from "react";
import { IconEdit, IconHistory } from "@tabler/icons-react"; import { IconEdit, IconHistory } from "@tabler/icons-react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Box, Tabs, Text } from "@mantine/core"; 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 DealStatusHistoryTab from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/DealStatusHistoryTab";
import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab"; import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab";
import useIsMobile from "@/hooks/utils/useIsMobile"; import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema, ProjectSchema } from "@/lib/client"; import { DealSchema, ProjectSchema } from "@/lib/client";
import { MODULES } from "@/modules/modules"; import MODULES from "@/modules/modules";
import styles from "../DealEditorDrawer.module.css"; import styles from "../DealEditorDrawer.module.css";
type Props = { type Props = {
@ -50,29 +51,58 @@ const DealEditorBody: FC<Props> = props => {
</Tabs.Tab> </Tabs.Tab>
); );
const getModuleTabs = () => const getModuleTabs = (): ReactNode[] => {
props.project?.builtInModules.map(module => { if (!props.project) return [];
const info = MODULES[module.key].renderInfo; const tabs: ReactNode[] = [];
return getTab(info.key, info.label, info.icon);
});
const getModuleTabPanels = () => for (const module of props.project.builtInModules) {
props.project?.builtInModules.map(module => const moduleInfo = MODULES[module.key];
getTabPanel(module.key, MODULES[module.key]?.getTab?.(props)) 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 ( return (
<Tabs <Tabs
defaultValue="general" defaultValue="general"
orientation={isMobile ? "horizontal" : "vertical"} orientation={isMobile ? "horizontal" : "vertical"}
h={"97vh"} h={isMobile ? "min-content" : "97vh"}
mih={"97vh"} mih={isMobile ? "min-content" : "97vh"}
classNames={{ tab: styles.tab }}> classNames={{ tab: styles.tab }}>
<Tabs.List> <TabsList>
{getTab("general", "Общая информация", <IconEdit />)} {getTab("general", "Общая информация", <IconEdit />)}
{getTab("history", "История", <IconHistory />)} {getTab("history", "История", <IconHistory />)}
{getModuleTabs()} {getModuleTabs()}
</Tabs.List> </TabsList>
{getTabPanel("general", <GeneralTab {...props} />)} {getTabPanel("general", <GeneralTab {...props} />)}
{getTabPanel("history", <DealStatusHistoryTab {...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
*/ */
label: string; label: string;
/**
* Iconname
*/
iconName: string;
/** /**
* Description * Description
*/ */
@ -50,6 +46,10 @@ export type BuiltInModuleSchemaInput = {
* Dependson * Dependson
*/ */
dependsOn: Array<BuiltInModuleSchemaInput>; dependsOn: Array<BuiltInModuleSchemaInput>;
/**
* Tabs
*/
tabs: Array<BuiltInModuleTabSchema>;
}; };
/** /**
@ -68,10 +68,6 @@ export type BuiltInModuleSchemaOutput = {
* Label * Label
*/ */
label: string; label: string;
/**
* Iconname
*/
iconName: string;
/** /**
* Description * Description
*/ */
@ -80,6 +76,32 @@ export type BuiltInModuleSchemaOutput = {
* Dependson * Dependson
*/ */
dependsOn: Array<BuiltInModuleSchemaOutput>; 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(), projectId: z.int(),
}); });
/**
* BuiltInModuleTabSchema
*/
export const zBuiltInModuleTabSchema = z.object({
id: z.int(),
label: z.string(),
iconName: z.string(),
device: z.string(),
});
/** /**
* BuiltInModuleSchema * BuiltInModuleSchema
*/ */
@ -19,7 +29,6 @@ export const zBuiltInModuleSchemaInput = z.object({
id: z.int(), id: z.int(),
key: z.string(), key: z.string(),
label: z.string(), label: z.string(),
iconName: z.string(),
description: z.string(), description: z.string(),
get dependsOn() { get dependsOn() {
return z.array( 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(), id: z.int(),
key: z.string(), key: z.string(),
label: z.string(), label: z.string(),
iconName: z.string(),
description: z.string(), description: z.string(),
get dependsOn() { get dependsOn() {
return z.array( 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 DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal"; import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal"; import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
import DealProductEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DealProductEditorModal/DealProductEditorModal"; import {
import DealServiceEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DealServiceEditorModal/DealServiceEditorModal"; DealProductEditorModal,
import DuplicateServicesModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DuplicateServicesModal/DuplicateServicesModal"; DealServiceEditorModal,
import ProductEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ProductEditorModal/ProductEditorModal"; DuplicateServicesModal,
import ProductServiceEditorModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ProductServiceEditorModal/ProductServiceEditorModal"; ProductEditorModal,
import ServicesKitSelectModal from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ServicesKitSelectModal/ServicesKitSelectModal"; ProductServiceEditorModal,
ServicesKitSelectModal,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals";
export const modals = { export const modals = {
enterNameModal: EnterNameModal, 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 { FC } from "react";
import { DealSchema } from "@/lib/client"; import { DealSchema } from "@/lib/client";
import FulfillmentBaseTabBody from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/FulfillmentBaseTabBody/FulfillmentBaseTabBody"; import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/contexts/FulfillmentBaseContext";
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext"; import FulfillmentBaseTabBody from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/FulfillmentBaseTabBody/FulfillmentBaseTabBody";
type Props = { type Props = {
value: DealSchema; value: DealSchema;

View File

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

View File

@ -1,7 +1,7 @@
import { FC } from "react"; import { FC } from "react";
import { Button, Flex } from "@mantine/core"; import { Button, Flex } from "@mantine/core";
import { modals } from "@mantine/modals"; 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 ProductsActions: FC = () => {
const { deal, dealProductsList, productsCrud, dealProductsCrud } = const { deal, dealProductsList, productsCrud, dealProductsCrud } =

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import { FC } from "react"; import { FC } from "react";
import { IconTrash } from "@tabler/icons-react"; import { IconTrash } from "@tabler/icons-react";
import { isNumber } from "lodash"; 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 { useDebouncedCallback } from "@mantine/hooks";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip"; import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import { DealServiceSchema } from "@/lib/client"; 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 = { type Props = {
value: DealServiceSchema; value: DealServiceSchema;
@ -64,6 +64,7 @@ const DealServiceRow: FC<Props> = ({ value, onChange, onDelete }) => {
display: "flex", display: "flex",
cursor: "pointer", cursor: "pointer",
pointerEvents: "auto", pointerEvents: "auto",
paddingRight: rem(10),
}, },
}} }}
rightSection={ rightSection={

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import ObjectSelect, {
ObjectSelectProps, ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect"; } from "@/components/selects/ObjectSelect/ObjectSelect";
import { ProductSchema } from "@/lib/client"; 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"; import renderProductOption from "./utils/renderProductOption";
type RestProps = { type RestProps = {

View File

@ -5,7 +5,7 @@ import {
Tooltip, Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import { ProductSchema } from "@/lib/client"; 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 = ( const renderProductOption = (
products: ProductSchema[] products: ProductSchema[]

View File

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

View File

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

View File

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

View File

@ -9,8 +9,8 @@ import ObjectSelect, {
ObjectSelectProps, ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect"; } from "@/components/selects/ObjectSelect/ObjectSelect";
import { ServiceSchema } from "@/lib/client"; import { ServiceSchema } from "@/lib/client";
import useServicesList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesList"; import useServicesList from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/lists/useServicesList";
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service"; import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/types/service";
type RestProps = { type RestProps = {
filterType?: ServiceType; 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 { FC, useMemo } from "react";
import { Title } from "@mantine/core"; import { Title, TitleProps } from "@mantine/core";
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext"; 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 { dealServicesList } = useFulfillmentBaseContext();
const total = useMemo( const total = useMemo(
() => () =>
@ -16,9 +18,9 @@ const DealServicesTotalLabel = () => {
return ( return (
<Title <Title
style={{ textAlign: "end" }} style={{ textAlign: "end" }}
mt={"sm"} order={order}
order={3}> {...props}>
Итог: {total.toFixed(2)} Итог: {total.toLocaleString("ru")}
</Title> </Title>
); );
}; };

View File

@ -3,22 +3,25 @@
import { DealSchema } from "@/lib/client"; import { DealSchema } from "@/lib/client";
import { getProductsQueryKey } from "@/lib/client/@tanstack/react-query.gen"; import { getProductsQueryKey } from "@/lib/client/@tanstack/react-query.gen";
import makeContext from "@/lib/contextFactory/contextFactory"; 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, { import useDealProductCrud, {
DealProductsCrud, DealProductsCrud,
} from "../hooks/cruds/useDealProductCrud"; } from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/hooks/cruds/useDealProductCrud";
import { ProductsCrud, useProductsCrud } from "../hooks/cruds/useProductsCrud"; 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, { import useDealProductsList, {
DealProductsList, 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 = { type FulfillmentBaseContextState = {
deal: DealSchema; deal: DealSchema;

View File

@ -29,7 +29,7 @@ export type DealServicesCrud = {
onDelete: (data: DealServiceSchema, onSuccess?: () => void) => void; onDelete: (data: DealServiceSchema, onSuccess?: () => void) => void;
}; };
const useDealServiceCrud = ({ dealId }: Props): DealServicesCrud => { const useDealServicesCrud = ({ dealId }: Props): DealServicesCrud => {
const queryKey = getDealServicesQueryKey({ path: { dealId } }); const queryKey = getDealServicesQueryKey({ path: { dealId } });
const key = "getDealServices"; const key = "getDealServices";
const { queryClient, onError, onSettled } = getCommonQueryClient({ 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, { import BaseFormModal, {
CreateEditFormProps, CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal"; } from "@/modals/base/BaseFormModal/BaseFormModal";
import ProductSelect from "../../components/ProductSelect/ProductSelect"; import ProductSelect
from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductSelect/ProductSelect";
type RestProps = { type RestProps = {
clientId: number; clientId: number;

View File

@ -11,7 +11,7 @@ import {
import BaseFormModal, { import BaseFormModal, {
CreateEditFormProps, CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal"; } 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"; import ServiceWithPriceInput from "./components/ServiceWithPriceInput";
type RestProps = { type RestProps = {

View File

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

View File

@ -11,7 +11,7 @@ import {
import BaseFormModal, { import BaseFormModal, {
CreateEditFormProps, CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal"; } 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"; import BaseFormInputProps from "@/utils/baseFormInputProps";
type Props = CreateEditFormProps< type Props = CreateEditFormProps<

View File

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

View File

@ -5,7 +5,7 @@ import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
import { ServicesKitSchema } from "@/lib/client"; import { ServicesKitSchema } from "@/lib/client";
import BaseFormModalActions from "@/modals/base/BaseFormModal/BaseFormModalActions"; 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 = { type Props = {
onSelect: (kit: ServicesKitSchema) => void; onSelect: (kit: ServicesKitSchema) => void;

View File

@ -3,7 +3,7 @@ import ObjectSelect, {
ObjectSelectProps, ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect"; } from "@/components/selects/ObjectSelect/ObjectSelect";
import { ServicesKitSchema } from "@/lib/client"; 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">; 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 { import {
IconColumns,
IconBox, IconBox,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import {
CommonServicesTab,
ProductsTab,
FulfillmentBaseTab,
} from "./dealModularEditorTabs";
import ModulesType from "./types"; import ModulesType from "./types";
import connectModules from "./connectModules";
export enum ModuleNames { export enum ModuleNames {
FULFILLMENT_BASE = "fulfillment_base", FULFILLMENT_BASE = "fulfillment_base",
} }
const modules: ModulesType = { const MODULES: ModulesType = {
[ModuleNames.FULFILLMENT_BASE]: { [ModuleNames.FULFILLMENT_BASE]: {
renderInfo: { info: {
label: "Фулфиллмент",
key: "fulfillment_base", key: "fulfillment_base",
icon: <IconBox />, 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"; import * as handlebars from "handlebars";
// region types // region types
type Module = { type ModuleTab = {
id: number; id: number;
key: string; key: string;
label: string; label: string;
iconName: string; iconName: string;
moduleId: number;
};
type Module = {
id: number;
label: string;
description: string; description: string;
tabs: ModuleTab[];
}; };
type ModulesResponse = { type ModulesResponse = {
@ -59,10 +66,23 @@ handlebars.registerHelper("uppercase", text => {
return text.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase(); 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[]) => { const generateRows = (modules: Module[]) => {
try { try {
const iconsToImport = new Set<string>();
for (const module of modules) {
for (const tab of module.tabs) {
iconsToImport.add(tab.iconName);
}
}
const data = { const data = {
modules, modules,
iconsToImport: Array.from(iconsToImport),
}; };
const tsxContent = template(data); const tsxContent = template(data);
fs.writeFileSync(OUTPUT_PATH, tsxContent); fs.writeFileSync(OUTPUT_PATH, tsxContent);

View File

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

View File

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