feat: modules, products, services, services kits
This commit is contained in:
@ -7,7 +7,8 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"generate-client": "openapi-ts && prettier --write ./src/lib/client/**/*.ts && git add ./src/lib/client"
|
||||
"generate-client": "openapi-ts && prettier --write ./src/lib/client/**/*.ts && git add ./src/lib/client",
|
||||
"generate-modules": "sudo npx tsc ./src/modules/modulesFileGen/modulesFileGen.ts && mv -f ./src/modules/modulesFileGen/modulesFileGen.js ./src/modules/modulesFileGen/modulesFileGen.cjs && sudo node ./src/modules/modulesFileGen/modulesFileGen.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@ -15,6 +16,7 @@
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@mantine/core": "8.1.2",
|
||||
"@mantine/dates": "^8.2.7",
|
||||
"@mantine/dropzone": "^8.3.1",
|
||||
"@mantine/form": "^8.1.3",
|
||||
"@mantine/hooks": "8.1.2",
|
||||
"@mantine/modals": "^8.2.1",
|
||||
@ -31,6 +33,7 @@
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dayjs": "^1.11.15",
|
||||
"framer-motion": "^12.23.7",
|
||||
"handlebars": "^4.7.8",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"lexorank": "^1.0.5",
|
||||
"libphonenumber-js": "^1.12.10",
|
||||
|
||||
@ -3,11 +3,13 @@ import { IconMoodSad } from "@tabler/icons-react";
|
||||
import { Group, Pagination, Stack, Text } from "@mantine/core";
|
||||
import useDealsTableColumns from "@/app/deals/components/desktop/DealsTable/useDealsTableColumns";
|
||||
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
|
||||
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
|
||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||
import { useDrawersContext } from "@/drawers/DrawersContext";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
|
||||
const DealsTable: FC = () => {
|
||||
const { selectedProject } = useProjectsContext();
|
||||
const { deals, paginationInfo, page, setPage, sortingForm, dealsCrud } =
|
||||
useDealsContext();
|
||||
const { openDrawer } = useDrawersContext();
|
||||
@ -20,6 +22,7 @@ const DealsTable: FC = () => {
|
||||
value: deal,
|
||||
onChange: deal => dealsCrud.onUpdate(deal.id, deal),
|
||||
onDelete: dealsCrud.onDelete,
|
||||
project: selectedProject,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@ -35,7 +35,7 @@ const TopToolPanel: FC<Props> = ({ view, setView }) => {
|
||||
title: "Создание проекта",
|
||||
withCloseButton: true,
|
||||
innerProps: {
|
||||
onChange: values => projectsCrud.onCreate(values.name),
|
||||
onChange: projectsCrud.onCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ const CreateBoardButton = () => {
|
||||
<Flex style={{ borderBottom: "2px solid gray" }}>
|
||||
<InPlaceInput
|
||||
placeholder={"Название доски"}
|
||||
onChange={boardsCrud.onCreate}
|
||||
onChange={name => boardsCrud.onCreate({ name })}
|
||||
getChildren={startEditing => (
|
||||
<Box
|
||||
onClick={startEditing}
|
||||
|
||||
@ -11,7 +11,7 @@ const CreateCardButton = () => {
|
||||
const { dealsCrud } = useDealsContext();
|
||||
|
||||
const onSubmit = (values: CreateDealForm) => {
|
||||
dealsCrud.onCreate(values.name);
|
||||
dealsCrud.onCreate(values);
|
||||
setIsCreating(prevState => !prevState);
|
||||
setIsTransitionEnded(false);
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ const CreateStatusButton = () => {
|
||||
className={styles["inner-container"]}>
|
||||
<InPlaceInput
|
||||
placeholder={"Название колонки"}
|
||||
onChange={statusesCrud.onCreate}
|
||||
onChange={name => statusesCrud.onCreate({ name })}
|
||||
getChildren={startEditing => (
|
||||
<Center
|
||||
p={"sm"}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Box, Card, Group, Pill, Stack, Text } from "@mantine/core";
|
||||
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
|
||||
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
|
||||
import { useDrawersContext } from "@/drawers/DrawersContext";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import styles from "./DealCard.module.css";
|
||||
@ -9,6 +10,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const DealCard = ({ deal }: Props) => {
|
||||
const { selectedProject } = useProjectsContext();
|
||||
const { dealsCrud } = useDealsContext();
|
||||
const { openDrawer } = useDrawersContext();
|
||||
|
||||
@ -19,6 +21,7 @@ const DealCard = ({ deal }: Props) => {
|
||||
value: deal,
|
||||
onChange: deal => dealsCrud.onUpdate(deal.id, deal),
|
||||
onDelete: dealsCrud.onDelete,
|
||||
project: selectedProject,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,9 +2,10 @@ import { FC } from "react";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
import { Box, Group, Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { CreateBoardSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
onCreateBoard: (name: string) => void;
|
||||
onCreateBoard: (data: Partial<CreateBoardSchema>) => void;
|
||||
};
|
||||
|
||||
const CreateBoardButton: FC<Props> = ({ onCreateBoard }) => {
|
||||
@ -14,7 +15,7 @@ const CreateBoardButton: FC<Props> = ({ onCreateBoard }) => {
|
||||
title: "Создание доски",
|
||||
withCloseButton: true,
|
||||
innerProps: {
|
||||
onChange: values => onCreateBoard(values.name),
|
||||
onChange: values => onCreateBoard(values),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -6,12 +6,13 @@ import DealEditorBody from "@/app/deals/drawers/DealEditorDrawer/components/Deal
|
||||
import Header from "@/app/deals/drawers/DealEditorDrawer/components/Header";
|
||||
import { DrawerProps } from "@/drawers/types";
|
||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import { DealSchema, ProjectSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
value: DealSchema;
|
||||
onChange: (deal: DealSchema) => void;
|
||||
onDelete: (deal: DealSchema, onSuccess: () => void) => void;
|
||||
project: ProjectSchema | null;
|
||||
};
|
||||
|
||||
const DealEditorDrawer: FC<DrawerProps<Props>> = ({
|
||||
@ -24,7 +25,7 @@ const DealEditorDrawer: FC<DrawerProps<Props>> = ({
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
size={isMobile ? "100%" : "40%"}
|
||||
size={isMobile ? "100%" : "60%"}
|
||||
position={"right"}
|
||||
onClose={onClose}
|
||||
removeScrollProps={{ allowPinchZoom: true }}
|
||||
@ -49,6 +50,7 @@ const DealEditorDrawer: FC<DrawerProps<Props>> = ({
|
||||
props.onChange(deal);
|
||||
}}
|
||||
onDelete={value => props.onDelete(value, onClose)}
|
||||
project={props.project}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
@ -1,17 +1,55 @@
|
||||
import React, { FC } from "react";
|
||||
import { IconCircleDotted, IconEdit } from "@tabler/icons-react";
|
||||
import { Tabs, Text } from "@mantine/core";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import { IconEdit } from "@tabler/icons-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Box, Tabs } from "@mantine/core";
|
||||
import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import { DealSchema, ProjectSchema } from "@/lib/client";
|
||||
import { MODULES } from "@/modules/modules";
|
||||
import styles from "../DealEditorDrawer.module.css";
|
||||
|
||||
type Props = {
|
||||
value: DealSchema;
|
||||
onChange: (deal: DealSchema) => void;
|
||||
onDelete: (deal: DealSchema) => void;
|
||||
project: ProjectSchema | null;
|
||||
};
|
||||
|
||||
const DealEditorBody: FC<Props> = props => {
|
||||
const getTabPanel = (value: string, component: ReactNode): ReactNode => (
|
||||
<Tabs.Panel
|
||||
key={value}
|
||||
value={value}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<Box
|
||||
h={"100%"}
|
||||
w={"100%"}>
|
||||
{component}
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
|
||||
const getModuleTabs = () =>
|
||||
props.project?.builtInModules.map(module => {
|
||||
const moduleRender = MODULES[module.key].renderInfo;
|
||||
return (
|
||||
<Tabs.Tab
|
||||
key={moduleRender.key}
|
||||
value={moduleRender.key}
|
||||
leftSection={moduleRender.icon}>
|
||||
{moduleRender.label}
|
||||
</Tabs.Tab>
|
||||
);
|
||||
});
|
||||
|
||||
const getModuleTabPanels = () =>
|
||||
props.project?.builtInModules.map(module =>
|
||||
getTabPanel(module.key, MODULES[module.key]?.getTab?.(props))
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue="general"
|
||||
@ -22,19 +60,11 @@ const DealEditorBody: FC<Props> = props => {
|
||||
leftSection={<IconEdit />}>
|
||||
Общая информация
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value="mock"
|
||||
leftSection={<IconCircleDotted />}>
|
||||
Mock
|
||||
</Tabs.Tab>
|
||||
{getModuleTabs()}
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="general">
|
||||
<GeneralTab {...props} />
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="mock">
|
||||
<Text>mock</Text>
|
||||
</Tabs.Panel>
|
||||
{getTabPanel("general", <GeneralTab {...props} />)}
|
||||
{getModuleTabPanels()}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { FC } from "react";
|
||||
import { Stack, Text, TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import Footer from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/Footer";
|
||||
import Footer from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/components/Footer";
|
||||
import BoardSelect from "@/components/selects/BoardSelect/BoardSelect";
|
||||
import StatusSelect from "@/components/selects/StatusSelect/StatusSelect";
|
||||
import { BoardSchema, DealSchema, StatusSchema } from "@/lib/client";
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { IconEdit } from "@tabler/icons-react";
|
||||
import { IconBlocks, IconEdit } from "@tabler/icons-react";
|
||||
import { Tabs } from "@mantine/core";
|
||||
import GeneralTab from "@/app/deals/drawers/ProjectEditorDrawer/tabs/GeneralTab/GeneralTab";
|
||||
import {
|
||||
GeneralTab,
|
||||
ModulesTab,
|
||||
} from "@/app/deals/drawers/ProjectEditorDrawer/tabs";
|
||||
import { ProjectSchema } from "@/lib/client";
|
||||
import styles from "../ProjectEditorDrawer.module.css";
|
||||
|
||||
@ -22,10 +25,18 @@ const ProjectEditorBody: FC<Props> = props => {
|
||||
leftSection={<IconEdit />}>
|
||||
Общая информация
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value="modules"
|
||||
leftSection={<IconBlocks />}>
|
||||
Модули
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value="general">
|
||||
<GeneralTab {...props} />
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="modules">
|
||||
<ModulesTab {...props} />
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { Stack, TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import Footer from "@/app/deals/drawers/ProjectEditorDrawer/tabs/GeneralTab/Footer";
|
||||
import Footer from "@/app/deals/drawers/ProjectEditorDrawer/tabs/GeneralTab/components/Footer";
|
||||
import { ProjectSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
@ -10,7 +10,7 @@ type Props = {
|
||||
onDelete: (value: ProjectSchema) => void;
|
||||
};
|
||||
|
||||
const GeneralTab: FC<Props> = ({ value, onChange, onDelete }) => {
|
||||
export const GeneralTab: FC<Props> = ({ value, onChange, onDelete }) => {
|
||||
const form = useForm<ProjectSchema>({
|
||||
initialValues: value,
|
||||
validate: {
|
||||
@ -38,5 +38,3 @@ const GeneralTab: FC<Props> = ({ value, onChange, onDelete }) => {
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralTab;
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import { FC } from "react";
|
||||
import { isEqual } from "lodash";
|
||||
import { Button, Stack } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ProjectSchema } from "@/lib/client";
|
||||
import ModulesTable from "./components/ModulesTable";
|
||||
|
||||
type Props = {
|
||||
value: ProjectSchema;
|
||||
onChange: (value: ProjectSchema) => void;
|
||||
};
|
||||
|
||||
export const ModulesTab: FC<Props> = ({ value, onChange }) => {
|
||||
const form = useForm<ProjectSchema>({
|
||||
initialValues: value,
|
||||
});
|
||||
|
||||
const onSubmit = (values: ProjectSchema) => {
|
||||
onChange(values);
|
||||
form.setInitialValues(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<Stack p={"md"}>
|
||||
<ModulesTable
|
||||
selectedRecords={form.values.builtInModules}
|
||||
onSelectedRecordsChange={modules =>
|
||||
form.setFieldValue("builtInModules", modules)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}
|
||||
disabled={isEqual(value, form.values)}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { FC, useRef } from "react";
|
||||
import { Divider, Stack } from "@mantine/core";
|
||||
import useModulesTableColumns from "@/app/deals/drawers/ProjectEditorDrawer/tabs/ModulesTab/hooks/useModulesTableColumns";
|
||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||
import { BuiltInModuleSchema } from "@/lib/client";
|
||||
import { MODULES } from "@/modules/modules";
|
||||
|
||||
type Props = {
|
||||
selectedRecords: BuiltInModuleSchema[];
|
||||
onSelectedRecordsChange: (records: BuiltInModuleSchema[]) => void;
|
||||
};
|
||||
|
||||
const ModulesTable: FC<Props> = props => {
|
||||
const columns = useModulesTableColumns();
|
||||
const modules = useRef(
|
||||
Object.values(MODULES).map(module => module.modelData)
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<Divider />
|
||||
<BaseTable
|
||||
records={modules.current}
|
||||
columns={columns}
|
||||
verticalSpacing={"md"}
|
||||
{...props}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModulesTable;
|
||||
@ -0,0 +1,24 @@
|
||||
import { useMemo } from "react";
|
||||
import { DataTableColumn } from "mantine-datatable";
|
||||
import { BuiltInModuleSchema } from "@/lib/client";
|
||||
|
||||
const useModulesTableColumns = () => {
|
||||
return useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
accessor: "label",
|
||||
title: "Название",
|
||||
width: "30%",
|
||||
},
|
||||
{
|
||||
title: "Описание",
|
||||
accessor: "description",
|
||||
width: "70%",
|
||||
},
|
||||
] as DataTableColumn<BuiltInModuleSchema>[],
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
export default useModulesTableColumns;
|
||||
2
src/app/deals/drawers/ProjectEditorDrawer/tabs/index.ts
Normal file
2
src/app/deals/drawers/ProjectEditorDrawer/tabs/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { GeneralTab } from "./GeneralTab/GeneralTab";
|
||||
export { ModulesTab } from "./ModulesTab/ModulesTab";
|
||||
@ -13,7 +13,7 @@ const CreateProjectButton: FC = () => {
|
||||
title: "Создание проекта",
|
||||
withCloseButton: true,
|
||||
innerProps: {
|
||||
onChange: values => projectsCrud.onCreate(values.name),
|
||||
onChange: projectsCrud.onCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ const CreateStatusButton: FC = () => {
|
||||
title: "Создание колонки",
|
||||
withCloseButton: true,
|
||||
innerProps: {
|
||||
onChange: values => statusesCrud.onCreate(values.name),
|
||||
onChange: statusesCrud.onCreate,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
108
src/components/selects/ObjectMultiSelect/ObjectMultiSelect.tsx
Normal file
108
src/components/selects/ObjectMultiSelect/ObjectMultiSelect.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { groupBy, omit } from "lodash";
|
||||
import { MultiSelect, MultiSelectProps } from "@mantine/core";
|
||||
|
||||
interface ObjectWithIdAndName {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type MultiselectObjectType<T> = T;
|
||||
|
||||
type ControlledValueProps<T> = {
|
||||
value: MultiselectObjectType<T>[];
|
||||
onChange: (value: MultiselectObjectType<T>[]) => void;
|
||||
};
|
||||
|
||||
type CustomLabelAndKeyProps<T> = {
|
||||
getLabelFn: (item: MultiselectObjectType<T>) => string;
|
||||
getValueFn: (item: MultiselectObjectType<T>) => string;
|
||||
};
|
||||
type RestProps<T> = {
|
||||
defaultValue?: MultiselectObjectType<T>[];
|
||||
onChange: (value: MultiselectObjectType<T>[]) => void;
|
||||
data: MultiselectObjectType<T>[];
|
||||
groupBy?: (item: MultiselectObjectType<T>) => string;
|
||||
filterBy?: (item: MultiselectObjectType<T>) => boolean;
|
||||
};
|
||||
const defaultGetLabelFn = <T extends { name: string }>(item: T): string => {
|
||||
return item.name;
|
||||
};
|
||||
|
||||
const defaultGetValueFn = <T extends { id: number }>(item: T): string => {
|
||||
return item.id.toString();
|
||||
};
|
||||
export type ObjectMultiSelectProps<T> = (RestProps<T> &
|
||||
Partial<ControlledValueProps<T>>) &
|
||||
Omit<MultiSelectProps, "value" | "onChange" | "data"> &
|
||||
(T extends ObjectWithIdAndName
|
||||
? Partial<CustomLabelAndKeyProps<T>>
|
||||
: CustomLabelAndKeyProps<T>);
|
||||
|
||||
const ObjectMultiSelect = <T,>(props: ObjectMultiSelectProps<T>) => {
|
||||
const isControlled = "value" in props;
|
||||
const haveGetValueFn = "getValueFn" in props;
|
||||
const haveGetLabelFn = "getLabelFn" in props;
|
||||
|
||||
const [internalValue, setInternalValue] = useState<
|
||||
MultiselectObjectType<T>[] | undefined
|
||||
>(props.defaultValue);
|
||||
|
||||
const value = (isControlled ? props.value : internalValue) || [];
|
||||
|
||||
const getValueFn =
|
||||
(haveGetValueFn && props.getValueFn) || defaultGetValueFn;
|
||||
const getLabelFn =
|
||||
(haveGetLabelFn && props.getLabelFn) || defaultGetLabelFn;
|
||||
|
||||
const data = useMemo(() => {
|
||||
const propsData = props.filterBy
|
||||
? props.data.filter(props.filterBy)
|
||||
: props.data;
|
||||
if (props.groupBy) {
|
||||
const groupedData = groupBy(propsData, props.groupBy);
|
||||
return Object.entries(groupedData).map(([group, items]) => ({
|
||||
group,
|
||||
items: items.map(item => ({
|
||||
label: getLabelFn(item),
|
||||
value: getValueFn(item),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
return propsData.map(item => ({
|
||||
label: getLabelFn(item),
|
||||
value: getValueFn(item),
|
||||
}));
|
||||
}, [props.data, props.groupBy]);
|
||||
|
||||
const handleOnChange = (event: string[]) => {
|
||||
const objects = props.data.filter(item =>
|
||||
event.includes(getValueFn(item))
|
||||
);
|
||||
if (isControlled) {
|
||||
props.onChange(objects);
|
||||
return;
|
||||
}
|
||||
setInternalValue(objects);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isControlled || !internalValue) return;
|
||||
props.onChange(internalValue);
|
||||
}, [internalValue]);
|
||||
|
||||
const restProps = omit(props, "getValueFn", "getLabelFn", "filterBy");
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
{...restProps}
|
||||
value={value.map(item => getValueFn(item))}
|
||||
onChange={handleOnChange}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObjectMultiSelect;
|
||||
@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { groupBy, omit } from "lodash";
|
||||
import { Select, SelectProps } from "@mantine/core";
|
||||
@ -33,6 +35,7 @@ const defaultGetValueFn = <T extends { id: number }>(item: T): string => {
|
||||
if (!item) return item;
|
||||
return item.id.toString();
|
||||
};
|
||||
|
||||
export type ObjectSelectProps<T> = (RestProps<T> &
|
||||
Partial<ControlledValueProps<T>>) &
|
||||
Omit<SelectProps, "value" | "onChange" | "data"> &
|
||||
|
||||
@ -4,11 +4,10 @@ import { DataTable, DataTableProps } from "mantine-datatable";
|
||||
function BaseTable<T>(props: DataTableProps<T>) {
|
||||
return (
|
||||
<DataTable
|
||||
withTableBorder={false}
|
||||
withTableBorder
|
||||
withRowBorders
|
||||
striped={false}
|
||||
verticalAlign={"center"}
|
||||
borderRadius={"lg"}
|
||||
backgroundColor={"transparent"}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
119
src/components/ui/ImageDropzone/ImageDropzone.tsx
Normal file
119
src/components/ui/ImageDropzone/ImageDropzone.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import { FC } from "react";
|
||||
import { IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
|
||||
import { omit } from "lodash";
|
||||
import {
|
||||
Button,
|
||||
Fieldset,
|
||||
Flex,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
rem,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { Dropzone, DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import UseImageDropzone from "./types";
|
||||
|
||||
interface RestProps {
|
||||
imageDropzone: UseImageDropzone;
|
||||
onDrop: (files: FileWithPath[]) => void;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ImageDropzone: FC<Props> = (props: Props) => {
|
||||
const { showDropzone, setShowDropzone, isLoading, imageUrlInputProps } =
|
||||
props.imageDropzone;
|
||||
|
||||
const restProps = omit(props, ["imageDropzone"]);
|
||||
|
||||
const getDropzone = () => (
|
||||
<Dropzone
|
||||
{...restProps}
|
||||
accept={[
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/x-icon",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/heic",
|
||||
]}
|
||||
multiple={false}
|
||||
onDrop={props.onDrop}>
|
||||
<Group
|
||||
justify="center"
|
||||
gap="xl"
|
||||
style={{ pointerEvents: "none" }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload
|
||||
style={{
|
||||
width: rem(52),
|
||||
height: rem(52),
|
||||
color: "var(--mantine-color-blue-6)",
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX
|
||||
style={{
|
||||
width: rem(52),
|
||||
height: rem(52),
|
||||
color: "var(--mantine-color-red-6)",
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto
|
||||
style={{
|
||||
width: rem(52),
|
||||
height: rem(52),
|
||||
color: "var(--mantine-color-dimmed)",
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Text
|
||||
size="xl"
|
||||
inline>
|
||||
Перенесите изображение или нажмите чтоб выбрать файл
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
);
|
||||
|
||||
const getBody = () => {
|
||||
if (imageUrlInputProps?.value && !showDropzone) {
|
||||
return <Image src={imageUrlInputProps.value} />;
|
||||
}
|
||||
return getDropzone();
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap={rem(10)}
|
||||
direction={"column"}>
|
||||
<Fieldset legend={"Изображение"}>
|
||||
<Flex justify={"center"}>
|
||||
{isLoading ? <Loader /> : getBody()}
|
||||
</Flex>
|
||||
</Fieldset>
|
||||
{!showDropzone && (
|
||||
<Button
|
||||
onClick={() => setShowDropzone(true)}
|
||||
variant={"default"}>
|
||||
Заменить изображение {}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageDropzone;
|
||||
12
src/components/ui/ImageDropzone/types.ts
Normal file
12
src/components/ui/ImageDropzone/types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type UseImageDropzone = {
|
||||
showDropzone: boolean;
|
||||
setShowDropzone: Dispatch<SetStateAction<boolean>>;
|
||||
isLoading: boolean;
|
||||
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
};
|
||||
|
||||
export default UseImageDropzone;
|
||||
38
src/hooks/cruds/baseCrud/getCommonQueryClient.ts
Normal file
38
src/hooks/cruds/baseCrud/getCommonQueryClient.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { HttpValidationError } from "@/lib/client";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
type Props = {
|
||||
key: string;
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
const getCommonQueryClient = ({ key, queryKey }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const onError = (
|
||||
error: AxiosError<HttpValidationError>,
|
||||
_: any,
|
||||
context: any
|
||||
) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(queryKey, context.previous);
|
||||
}
|
||||
};
|
||||
|
||||
const onSettled = () => {
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query: { queryKey: any }) =>
|
||||
query.queryKey[0]?._id === key,
|
||||
});
|
||||
};
|
||||
|
||||
return { queryClient, onError, onSettled };
|
||||
};
|
||||
|
||||
export default getCommonQueryClient;
|
||||
@ -1,14 +1,10 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
||||
import { HttpValidationError } from "@/lib/client";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
import {
|
||||
BaseEntity,
|
||||
@ -17,10 +13,17 @@ import {
|
||||
UpdateMutationOptions,
|
||||
} from "./types";
|
||||
|
||||
type CrudOperations<TEntity, TUpdate> = {
|
||||
onCreate: (name: string) => void;
|
||||
onUpdate: (id: number, update: TUpdate) => void;
|
||||
onDelete: (entity: TEntity) => void;
|
||||
type CrudOperations<
|
||||
TEntity,
|
||||
TUpdate,
|
||||
TCreate,
|
||||
HasGetCreateEntity extends boolean,
|
||||
> = {
|
||||
onCreate: HasGetCreateEntity extends true
|
||||
? (data: Partial<TCreate>, onSuccess?: () => void) => void
|
||||
: (data: TCreate, onSuccess?: () => void) => void;
|
||||
onUpdate: (id: number, update: TUpdate, onSuccess?: () => void) => void;
|
||||
onDelete: (entity: TEntity, onSuccess?: () => void) => void;
|
||||
};
|
||||
|
||||
type UseEntityOperationsProps<TEntity extends BaseEntity, TUpdate, TCreate> = {
|
||||
@ -43,12 +46,32 @@ type UseEntityOperationsProps<TEntity extends BaseEntity, TUpdate, TCreate> = {
|
||||
DeleteMutationOptions
|
||||
>;
|
||||
};
|
||||
getCreateEntity: (name: string) => TCreate | null;
|
||||
getCreateEntity?: (data: Partial<TCreate>) => TCreate | null;
|
||||
getUpdateEntity: (oldEntity: TEntity, update: TUpdate) => TEntity;
|
||||
getDeleteConfirmTitle: (entity: TEntity) => string;
|
||||
};
|
||||
|
||||
const useCrudOperations = <
|
||||
function useCrudOperations<
|
||||
TEntity extends BaseEntity,
|
||||
TUpdate extends object,
|
||||
TCreate extends object,
|
||||
>(
|
||||
props: UseEntityOperationsProps<TEntity, TUpdate, TCreate> & {
|
||||
getCreateEntity: (data: Partial<TCreate>) => TCreate | null;
|
||||
}
|
||||
): CrudOperations<TEntity, TUpdate, TCreate, true>;
|
||||
|
||||
function useCrudOperations<
|
||||
TEntity extends BaseEntity,
|
||||
TUpdate extends object,
|
||||
TCreate extends object,
|
||||
>(
|
||||
props: UseEntityOperationsProps<TEntity, TUpdate, TCreate> & {
|
||||
getCreateEntity?: undefined;
|
||||
}
|
||||
): CrudOperations<TEntity, TUpdate, TCreate, false>;
|
||||
|
||||
function useCrudOperations<
|
||||
TEntity extends BaseEntity,
|
||||
TUpdate extends object,
|
||||
TCreate extends object,
|
||||
@ -61,30 +84,14 @@ const useCrudOperations = <
|
||||
getDeleteConfirmTitle,
|
||||
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>): CrudOperations<
|
||||
TEntity,
|
||||
TUpdate
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const onError = (
|
||||
error: AxiosError<HttpValidationError>,
|
||||
_: any,
|
||||
context: any
|
||||
) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
TUpdate,
|
||||
TCreate,
|
||||
boolean
|
||||
> {
|
||||
const { queryClient, onError, onSettled } = getCommonQueryClient({
|
||||
queryKey,
|
||||
key,
|
||||
});
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(queryKey, context.previous);
|
||||
}
|
||||
};
|
||||
|
||||
const onSettled = () => {
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query: { queryKey: any }) =>
|
||||
query.queryKey[0]?._id === key,
|
||||
});
|
||||
};
|
||||
|
||||
const createMutation = useMutation({
|
||||
...mutations.create,
|
||||
@ -102,6 +109,7 @@ const useCrudOperations = <
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
|
||||
if (!old) return;
|
||||
let updated = old.items.map((entity: TEntity) =>
|
||||
entity.id === update.id
|
||||
? getUpdateEntity(entity, update)
|
||||
@ -145,26 +153,43 @@ const useCrudOperations = <
|
||||
},
|
||||
});
|
||||
|
||||
const onCreate = (name: string) => {
|
||||
const entity = getCreateEntity(name);
|
||||
if (!entity) return;
|
||||
createMutation.mutate({
|
||||
const onCreate = (data: any, onSuccess?: () => void) => {
|
||||
let entity: TCreate;
|
||||
if (getCreateEntity) {
|
||||
const result = getCreateEntity(data);
|
||||
if (!result) return;
|
||||
entity = result;
|
||||
} else {
|
||||
entity = data;
|
||||
}
|
||||
|
||||
createMutation.mutate(
|
||||
{
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: undefined,
|
||||
query: undefined,
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const onUpdate = async (id: number, update: TUpdate) => {
|
||||
updateMutation.mutate({
|
||||
const onUpdate = (id: number, update: TUpdate, onSuccess?: () => void) => {
|
||||
updateMutation.mutate(
|
||||
{
|
||||
body: {
|
||||
entity: update,
|
||||
},
|
||||
path: { pk: id },
|
||||
query: undefined,
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = (entity: TEntity, onSuccess?: () => void) => {
|
||||
@ -175,13 +200,14 @@ const useCrudOperations = <
|
||||
),
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteMutation.mutate({ path: { pk: entity.id } } as any);
|
||||
onSuccess && onSuccess();
|
||||
deleteMutation.mutate({ path: { pk: entity.id } } as any, {
|
||||
onSuccess,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { onCreate, onUpdate, onDelete };
|
||||
};
|
||||
}
|
||||
|
||||
export default useCrudOperations;
|
||||
|
||||
@ -19,7 +19,7 @@ type UseBoardsOperationsProps = {
|
||||
};
|
||||
|
||||
export type BoardsCrud = {
|
||||
onCreate: (name: string) => void;
|
||||
onCreate: (data: Partial<CreateBoardSchema>) => void;
|
||||
onUpdate: (boardId: number, board: UpdateBoardSchema) => void;
|
||||
onDelete: (board: BoardSchema) => void;
|
||||
};
|
||||
@ -38,14 +38,14 @@ export const useBoardsCrud = ({
|
||||
update: updateBoardMutation(),
|
||||
delete: deleteBoardMutation(),
|
||||
},
|
||||
getCreateEntity: name => {
|
||||
getCreateEntity: data => {
|
||||
if (!projectId) return null;
|
||||
const lastBoard = getMaxByLexorank(boards);
|
||||
const newLexorank = getNewLexorank(
|
||||
lastBoard ? LexoRank.parse(lastBoard.lexorank) : null
|
||||
);
|
||||
return {
|
||||
name,
|
||||
name: data.name!,
|
||||
projectId,
|
||||
lexorank: newLexorank.toString(),
|
||||
};
|
||||
|
||||
@ -21,7 +21,7 @@ type UseDealsOperationsProps = {
|
||||
};
|
||||
|
||||
export type DealsCrud = {
|
||||
onCreate: (name: string) => void;
|
||||
onCreate: (entity: Partial<CreateDealSchema>) => void;
|
||||
onUpdate: (dealId: number, deal: UpdateDealSchema) => void;
|
||||
onDelete: (deal: DealSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
@ -40,7 +40,7 @@ export const useDealsCrud = ({
|
||||
update: updateDealMutation(),
|
||||
delete: deleteDealMutation(),
|
||||
},
|
||||
getCreateEntity: name => {
|
||||
getCreateEntity: data => {
|
||||
if (!boardId || statuses.length === 0) return null;
|
||||
const firstStatus = statuses[0];
|
||||
const filteredDeals = deals.filter(
|
||||
@ -55,7 +55,7 @@ export const useDealsCrud = ({
|
||||
firstDeal ? LexoRank.parse(firstDeal.lexorank) : null
|
||||
);
|
||||
return {
|
||||
name,
|
||||
name: data.name!,
|
||||
boardId,
|
||||
statusId: firstStatus.id,
|
||||
lexorank: newLexorank.toString(),
|
||||
|
||||
@ -15,7 +15,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export type ProjectsCrud = {
|
||||
onCreate: (name: string) => void;
|
||||
onCreate: (data: Partial<CreateProjectSchema>) => void;
|
||||
onUpdate: (projectId: number, project: UpdateProjectSchema) => void;
|
||||
onDelete: (project: ProjectSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
@ -33,7 +33,7 @@ export const useProjectsCrud = ({ queryKey }: Props): ProjectsCrud => {
|
||||
update: updateProjectMutation(),
|
||||
delete: deleteProjectMutation(),
|
||||
},
|
||||
getCreateEntity: name => ({ name }),
|
||||
getCreateEntity: data => ({ name: data.name! }),
|
||||
getUpdateEntity: (old, update) => ({
|
||||
...old,
|
||||
name: update.name ?? old.name,
|
||||
|
||||
@ -19,7 +19,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export type StatusesCrud = {
|
||||
onCreate: (name: string) => void;
|
||||
onCreate: (entity: Partial<CreateStatusSchema>) => void;
|
||||
onUpdate: (statusId: number, status: UpdateStatusSchema) => void;
|
||||
onDelete: (status: StatusSchema) => void;
|
||||
};
|
||||
@ -41,14 +41,14 @@ export const useStatusesCrud = ({
|
||||
update: updateStatusMutation(),
|
||||
delete: deleteStatusMutation(),
|
||||
},
|
||||
getCreateEntity: name => {
|
||||
getCreateEntity: data => {
|
||||
if (!boardId) return null;
|
||||
const lastBoard = getMaxByLexorank(statuses);
|
||||
const newLexorank = getNewLexorank(
|
||||
lastBoard ? LexoRank.parse(lastBoard.lexorank) : null
|
||||
);
|
||||
return {
|
||||
name,
|
||||
name: data.name!,
|
||||
boardId,
|
||||
lexorank: newLexorank.toString(),
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,15 +3,36 @@
|
||||
import type { Client, Options as ClientOptions, TDataShape } from "./client";
|
||||
import { client as _heyApiClient } from "./client.gen";
|
||||
import type {
|
||||
AddKitToDealProductData,
|
||||
AddKitToDealProductErrors,
|
||||
AddKitToDealProductResponses,
|
||||
CreateBoardData,
|
||||
CreateBoardErrors,
|
||||
CreateBoardResponses,
|
||||
CreateDealData,
|
||||
CreateDealErrors,
|
||||
CreateDealProductData,
|
||||
CreateDealProductErrors,
|
||||
CreateDealProductResponses,
|
||||
CreateDealProductServiceData,
|
||||
CreateDealProductServiceErrors,
|
||||
CreateDealProductServiceResponses,
|
||||
CreateDealResponses,
|
||||
CreateDealServiceData,
|
||||
CreateDealServiceErrors,
|
||||
CreateDealServiceResponses,
|
||||
CreateProductData,
|
||||
CreateProductErrors,
|
||||
CreateProductResponses,
|
||||
CreateProjectData,
|
||||
CreateProjectErrors,
|
||||
CreateProjectResponses,
|
||||
CreateServiceData,
|
||||
CreateServiceErrors,
|
||||
CreateServiceResponses,
|
||||
CreateServicesKitData,
|
||||
CreateServicesKitErrors,
|
||||
CreateServicesKitResponses,
|
||||
CreateStatusData,
|
||||
CreateStatusErrors,
|
||||
CreateStatusResponses,
|
||||
@ -20,21 +41,57 @@ import type {
|
||||
DeleteBoardResponses,
|
||||
DeleteDealData,
|
||||
DeleteDealErrors,
|
||||
DeleteDealProductData,
|
||||
DeleteDealProductErrors,
|
||||
DeleteDealProductResponses,
|
||||
DeleteDealProductServiceData,
|
||||
DeleteDealProductServiceErrors,
|
||||
DeleteDealProductServiceResponses,
|
||||
DeleteDealResponses,
|
||||
DeleteDealServiceData,
|
||||
DeleteDealServiceErrors,
|
||||
DeleteDealServiceResponses,
|
||||
DeleteProductData,
|
||||
DeleteProductErrors,
|
||||
DeleteProductResponses,
|
||||
DeleteProjectData,
|
||||
DeleteProjectErrors,
|
||||
DeleteProjectResponses,
|
||||
DeleteServiceData,
|
||||
DeleteServiceErrors,
|
||||
DeleteServiceResponses,
|
||||
DeleteServicesKitData,
|
||||
DeleteServicesKitErrors,
|
||||
DeleteServicesKitResponses,
|
||||
DeleteStatusData,
|
||||
DeleteStatusErrors,
|
||||
DeleteStatusResponses,
|
||||
DuplicateProductServicesData,
|
||||
DuplicateProductServicesErrors,
|
||||
DuplicateProductServicesResponses,
|
||||
GetBoardsData,
|
||||
GetBoardsErrors,
|
||||
GetBoardsResponses,
|
||||
GetBuiltInModulesData,
|
||||
GetBuiltInModulesResponses,
|
||||
GetDealProductsData,
|
||||
GetDealProductsErrors,
|
||||
GetDealProductsResponses,
|
||||
GetDealsData,
|
||||
GetDealsErrors,
|
||||
GetDealServicesData,
|
||||
GetDealServicesErrors,
|
||||
GetDealServicesResponses,
|
||||
GetDealsResponses,
|
||||
GetProductsData,
|
||||
GetProductsErrors,
|
||||
GetProductsResponses,
|
||||
GetProjectsData,
|
||||
GetProjectsResponses,
|
||||
GetServicesData,
|
||||
GetServicesKitsData,
|
||||
GetServicesKitsResponses,
|
||||
GetServicesResponses,
|
||||
GetStatusesData,
|
||||
GetStatusesErrors,
|
||||
GetStatusesResponses,
|
||||
@ -43,45 +100,115 @@ import type {
|
||||
UpdateBoardResponses,
|
||||
UpdateDealData,
|
||||
UpdateDealErrors,
|
||||
UpdateDealProductData,
|
||||
UpdateDealProductErrors,
|
||||
UpdateDealProductResponses,
|
||||
UpdateDealProductServiceData,
|
||||
UpdateDealProductServiceErrors,
|
||||
UpdateDealProductServiceResponses,
|
||||
UpdateDealResponses,
|
||||
UpdateDealServiceData,
|
||||
UpdateDealServiceErrors,
|
||||
UpdateDealServiceResponses,
|
||||
UpdateProductData,
|
||||
UpdateProductErrors,
|
||||
UpdateProductResponses,
|
||||
UpdateProjectData,
|
||||
UpdateProjectErrors,
|
||||
UpdateProjectResponses,
|
||||
UpdateServiceData,
|
||||
UpdateServiceErrors,
|
||||
UpdateServiceResponses,
|
||||
UpdateServicesKitData,
|
||||
UpdateServicesKitErrors,
|
||||
UpdateServicesKitResponses,
|
||||
UpdateStatusData,
|
||||
UpdateStatusErrors,
|
||||
UpdateStatusResponses,
|
||||
} from "./types.gen";
|
||||
import {
|
||||
zAddKitToDealProductData,
|
||||
zAddKitToDealProductResponse,
|
||||
zCreateBoardData,
|
||||
zCreateBoardResponse2,
|
||||
zCreateDealData,
|
||||
zCreateDealProductData,
|
||||
zCreateDealProductResponse2,
|
||||
zCreateDealProductServiceData,
|
||||
zCreateDealProductServiceResponse,
|
||||
zCreateDealResponse2,
|
||||
zCreateDealServiceData,
|
||||
zCreateDealServiceResponse2,
|
||||
zCreateProductData,
|
||||
zCreateProductResponse2,
|
||||
zCreateProjectData,
|
||||
zCreateProjectResponse2,
|
||||
zCreateServiceData,
|
||||
zCreateServiceResponse2,
|
||||
zCreateServicesKitData,
|
||||
zCreateServicesKitResponse2,
|
||||
zCreateStatusData,
|
||||
zCreateStatusResponse2,
|
||||
zDeleteBoardData,
|
||||
zDeleteBoardResponse2,
|
||||
zDeleteDealData,
|
||||
zDeleteDealProductData,
|
||||
zDeleteDealProductResponse2,
|
||||
zDeleteDealProductServiceData,
|
||||
zDeleteDealProductServiceResponse,
|
||||
zDeleteDealResponse2,
|
||||
zDeleteDealServiceData,
|
||||
zDeleteDealServiceResponse2,
|
||||
zDeleteProductData,
|
||||
zDeleteProductResponse2,
|
||||
zDeleteProjectData,
|
||||
zDeleteProjectResponse2,
|
||||
zDeleteServiceData,
|
||||
zDeleteServiceResponse2,
|
||||
zDeleteServicesKitData,
|
||||
zDeleteServicesKitResponse2,
|
||||
zDeleteStatusData,
|
||||
zDeleteStatusResponse2,
|
||||
zDuplicateProductServicesData,
|
||||
zDuplicateProductServicesResponse,
|
||||
zGetBoardsData,
|
||||
zGetBoardsResponse2,
|
||||
zGetBuiltInModulesData,
|
||||
zGetBuiltInModulesResponse,
|
||||
zGetDealProductsData,
|
||||
zGetDealProductsResponse2,
|
||||
zGetDealsData,
|
||||
zGetDealServicesData,
|
||||
zGetDealServicesResponse2,
|
||||
zGetDealsResponse2,
|
||||
zGetProductsData,
|
||||
zGetProductsResponse2,
|
||||
zGetProjectsData,
|
||||
zGetProjectsResponse2,
|
||||
zGetServicesData,
|
||||
zGetServicesKitsData,
|
||||
zGetServicesKitsResponse,
|
||||
zGetServicesResponse2,
|
||||
zGetStatusesData,
|
||||
zGetStatusesResponse2,
|
||||
zUpdateBoardData,
|
||||
zUpdateBoardResponse2,
|
||||
zUpdateDealData,
|
||||
zUpdateDealProductData,
|
||||
zUpdateDealProductResponse2,
|
||||
zUpdateDealProductServiceData,
|
||||
zUpdateDealProductServiceResponse,
|
||||
zUpdateDealResponse2,
|
||||
zUpdateDealServiceData,
|
||||
zUpdateDealServiceResponse2,
|
||||
zUpdateProductData,
|
||||
zUpdateProductResponse2,
|
||||
zUpdateProjectData,
|
||||
zUpdateProjectResponse2,
|
||||
zUpdateServiceData,
|
||||
zUpdateServiceResponse2,
|
||||
zUpdateServicesKitData,
|
||||
zUpdateServicesKitResponse2,
|
||||
zUpdateStatusData,
|
||||
zUpdateStatusResponse2,
|
||||
} from "./zod.gen";
|
||||
@ -303,6 +430,29 @@ export const updateDeal = <ThrowOnError extends boolean = false>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Built In Modules
|
||||
*/
|
||||
export const getBuiltInModules = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetBuiltInModulesData, ThrowOnError>
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
GetBuiltInModulesResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetBuiltInModulesData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetBuiltInModulesResponse.parseAsync(data);
|
||||
},
|
||||
url: "/module/built-in/",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Projects
|
||||
*/
|
||||
@ -502,3 +652,634 @@ export const updateStatus = <ThrowOnError extends boolean = false>(
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Deal Products
|
||||
*/
|
||||
export const getDealProducts = <ThrowOnError extends boolean = false>(
|
||||
options: Options<GetDealProductsData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).get<
|
||||
GetDealProductsResponses,
|
||||
GetDealProductsErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetDealProductsData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetDealProductsResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/{dealId}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Deal Product
|
||||
*/
|
||||
export const createDealProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateDealProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateDealProductResponses,
|
||||
CreateDealProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateDealProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateDealProductResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Deal Product
|
||||
*/
|
||||
export const deleteDealProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteDealProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteDealProductResponses,
|
||||
DeleteDealProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteDealProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteDealProductResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/{dealId}/product/{productId}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Deal Product
|
||||
*/
|
||||
export const updateDealProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateDealProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateDealProductResponses,
|
||||
UpdateDealProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateDealProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateDealProductResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/{dealId}/product/{productId}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add Kit To Deal Product
|
||||
*/
|
||||
export const addKitToDealProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<AddKitToDealProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
AddKitToDealProductResponses,
|
||||
AddKitToDealProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zAddKitToDealProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zAddKitToDealProductResponse.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/add-services-kit",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Deal Product Service
|
||||
*/
|
||||
export const createDealProductService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateDealProductServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateDealProductServiceResponses,
|
||||
CreateDealProductServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateDealProductServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateDealProductServiceResponse.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/service",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Deal Product Service
|
||||
*/
|
||||
export const deleteDealProductService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteDealProductServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteDealProductServiceResponses,
|
||||
DeleteDealProductServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteDealProductServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteDealProductServiceResponse.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/{dealId}/product/{productId}/service/{serviceId}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Deal Product Service
|
||||
*/
|
||||
export const updateDealProductService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateDealProductServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateDealProductServiceResponses,
|
||||
UpdateDealProductServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateDealProductServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateDealProductServiceResponse.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/{dealId}/product/{productId}/service/{serviceId}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy Product Services
|
||||
*/
|
||||
export const duplicateProductServices = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DuplicateProductServicesData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
DuplicateProductServicesResponses,
|
||||
DuplicateProductServicesErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDuplicateProductServicesData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDuplicateProductServicesResponse.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-product/services/duplicate",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Deal Services
|
||||
*/
|
||||
export const getDealServices = <ThrowOnError extends boolean = false>(
|
||||
options: Options<GetDealServicesData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).get<
|
||||
GetDealServicesResponses,
|
||||
GetDealServicesErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetDealServicesData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetDealServicesResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-service/{dealId}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Deal Service
|
||||
*/
|
||||
export const createDealService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateDealServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateDealServiceResponses,
|
||||
CreateDealServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateDealServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateDealServiceResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-service/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Deal Service
|
||||
*/
|
||||
export const deleteDealService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteDealServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteDealServiceResponses,
|
||||
DeleteDealServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteDealServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteDealServiceResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-service/{dealId}/service/{serviceId}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Deal Service
|
||||
*/
|
||||
export const updateDealService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateDealServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateDealServiceResponses,
|
||||
UpdateDealServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateDealServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateDealServiceResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/deal-service/{dealId}/service/{serviceId}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Products
|
||||
*/
|
||||
export const getProducts = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetProductsData, ThrowOnError>
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
GetProductsResponses,
|
||||
GetProductsErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetProductsData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetProductsResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/product/",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Product
|
||||
*/
|
||||
export const createProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateProductResponses,
|
||||
CreateProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateProductResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/product/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Product
|
||||
*/
|
||||
export const deleteProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteProductResponses,
|
||||
DeleteProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteProductResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/product/{pk}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Product
|
||||
*/
|
||||
export const updateProduct = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateProductData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateProductResponses,
|
||||
UpdateProductErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateProductData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateProductResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/product/{pk}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Services
|
||||
*/
|
||||
export const getServices = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetServicesData, ThrowOnError>
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
GetServicesResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetServicesData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetServicesResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service/",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Service
|
||||
*/
|
||||
export const createService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateServiceResponses,
|
||||
CreateServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateServiceResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Service
|
||||
*/
|
||||
export const deleteService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteServiceResponses,
|
||||
DeleteServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteServiceResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service/{pk}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Service
|
||||
*/
|
||||
export const updateService = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateServiceData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateServiceResponses,
|
||||
UpdateServiceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateServiceData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateServiceResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/service/{pk}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Services Kits
|
||||
*/
|
||||
export const getServicesKits = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetServicesKitsData, ThrowOnError>
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
GetServicesKitsResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zGetServicesKitsData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zGetServicesKitsResponse.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/services-kit/",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Services Kit
|
||||
*/
|
||||
export const createServicesKit = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateServicesKitData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateServicesKitResponses,
|
||||
CreateServicesKitErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateServicesKitData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateServicesKitResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/services-kit/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Services Kit
|
||||
*/
|
||||
export const deleteServicesKit = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteServicesKitData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteServicesKitResponses,
|
||||
DeleteServicesKitErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteServicesKitData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteServicesKitResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/services-kit/{pk}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update Services Kit
|
||||
*/
|
||||
export const updateServicesKit = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UpdateServicesKitData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).patch<
|
||||
UpdateServicesKitResponses,
|
||||
UpdateServicesKitErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zUpdateServicesKitData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUpdateServicesKitResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/modules/fulfillment-base/services-kit/{pk}",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,17 @@ export const zBoardSchema = z.object({
|
||||
projectId: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
* BuiltInModuleSchema
|
||||
*/
|
||||
export const zBuiltInModuleSchema = z.object({
|
||||
id: z.int(),
|
||||
key: z.string(),
|
||||
label: z.string(),
|
||||
iconName: z.string(),
|
||||
description: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateBoardSchema
|
||||
*/
|
||||
@ -36,6 +47,104 @@ export const zCreateBoardResponse = z.object({
|
||||
entity: zBoardSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealProductSchema
|
||||
*/
|
||||
export const zCreateDealProductSchema = z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
quantity: z.int(),
|
||||
comment: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealProductRequest
|
||||
*/
|
||||
export const zCreateDealProductRequest = z.object({
|
||||
entity: zCreateDealProductSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductSchema
|
||||
*/
|
||||
export const zProductSchema = z.object({
|
||||
name: z.string(),
|
||||
article: z.string(),
|
||||
factoryArticle: z.string(),
|
||||
brand: z.union([z.string(), z.null()]),
|
||||
color: z.union([z.string(), z.null()]),
|
||||
composition: z.union([z.string(), z.null()]),
|
||||
size: z.union([z.string(), z.null()]),
|
||||
additionalInfo: z.union([z.string(), z.null()]),
|
||||
id: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ServiceCategorySchema
|
||||
*/
|
||||
export const zServiceCategorySchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
dealServiceRank: z.string(),
|
||||
productServiceRank: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ServicePriceRangeSchema
|
||||
*/
|
||||
export const zServicePriceRangeSchema = z.object({
|
||||
id: z.union([z.int(), z.null()]),
|
||||
fromQuantity: z.int(),
|
||||
toQuantity: z.int(),
|
||||
price: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ServiceSchema
|
||||
*/
|
||||
export const zServiceSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
category: zServiceCategorySchema,
|
||||
price: z.number(),
|
||||
serviceType: z.int(),
|
||||
priceRanges: z.array(zServicePriceRangeSchema),
|
||||
cost: z.union([z.number(), z.null()]),
|
||||
lexorank: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductServiceSchema
|
||||
*/
|
||||
export const zProductServiceSchema = z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
serviceId: z.int(),
|
||||
service: zServiceSchema,
|
||||
price: z.number(),
|
||||
isFixedPrice: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DealProductSchema
|
||||
*/
|
||||
export const zDealProductSchema = z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
product: zProductSchema,
|
||||
quantity: z.int(),
|
||||
comment: z.string(),
|
||||
productServices: z.array(zProductServiceSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealProductResponse
|
||||
*/
|
||||
export const zCreateDealProductResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zDealProductSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealSchema
|
||||
*/
|
||||
@ -84,6 +193,97 @@ export const zCreateDealResponse = z.object({
|
||||
entity: zDealSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealServiceSchema
|
||||
*/
|
||||
export const zCreateDealServiceSchema = z.object({
|
||||
dealId: z.int(),
|
||||
serviceId: z.int(),
|
||||
quantity: z.int(),
|
||||
price: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealServiceRequest
|
||||
*/
|
||||
export const zCreateDealServiceRequest = z.object({
|
||||
entity: zCreateDealServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* DealServiceSchema
|
||||
*/
|
||||
export const zDealServiceSchema = z.object({
|
||||
dealId: z.int(),
|
||||
serviceId: z.int(),
|
||||
service: zServiceSchema,
|
||||
quantity: z.int(),
|
||||
price: z.number(),
|
||||
isFixedPrice: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateDealServiceResponse
|
||||
*/
|
||||
export const zCreateDealServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zDealServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductSchema
|
||||
*/
|
||||
export const zCreateProductSchema = z.object({
|
||||
name: z.string(),
|
||||
article: z.string(),
|
||||
factoryArticle: z.string(),
|
||||
brand: z.union([z.string(), z.null()]),
|
||||
color: z.union([z.string(), z.null()]),
|
||||
composition: z.union([z.string(), z.null()]),
|
||||
size: z.union([z.string(), z.null()]),
|
||||
additionalInfo: z.union([z.string(), z.null()]),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductRequest
|
||||
*/
|
||||
export const zCreateProductRequest = z.object({
|
||||
entity: zCreateProductSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductResponse
|
||||
*/
|
||||
export const zCreateProductResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zProductSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductServiceSchema
|
||||
*/
|
||||
export const zCreateProductServiceSchema = z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
serviceId: z.int(),
|
||||
price: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductServiceRequest
|
||||
*/
|
||||
export const zCreateProductServiceRequest = z.object({
|
||||
entity: zCreateProductServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductServiceResponse
|
||||
*/
|
||||
export const zCreateProductServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zProductServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProjectSchema
|
||||
*/
|
||||
@ -104,6 +304,7 @@ export const zCreateProjectRequest = z.object({
|
||||
export const zProjectSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
builtInModules: z.array(zBuiltInModuleSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -114,6 +315,69 @@ export const zCreateProjectResponse = z.object({
|
||||
entity: zProjectSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceSchema
|
||||
*/
|
||||
export const zCreateServiceSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
category: zServiceCategorySchema,
|
||||
price: z.number(),
|
||||
serviceType: z.int(),
|
||||
priceRanges: z.array(zServicePriceRangeSchema),
|
||||
cost: z.union([z.number(), z.null()]),
|
||||
lexorank: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceRequest
|
||||
*/
|
||||
export const zCreateServiceRequest = z.object({
|
||||
entity: zCreateServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServiceResponse
|
||||
*/
|
||||
export const zCreateServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServicesKitSchema
|
||||
*/
|
||||
export const zCreateServicesKitSchema = z.object({
|
||||
name: z.string(),
|
||||
serviceType: z.int(),
|
||||
servicesIds: z.array(z.int()),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServicesKitRequest
|
||||
*/
|
||||
export const zCreateServicesKitRequest = z.object({
|
||||
entity: zCreateServicesKitSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* ServicesKitSchema
|
||||
*/
|
||||
export const zServicesKitSchema = z.object({
|
||||
name: z.string(),
|
||||
serviceType: z.int(),
|
||||
id: z.int(),
|
||||
services: z.array(zServiceSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateServicesKitResponse
|
||||
*/
|
||||
export const zCreateServicesKitResponse = z.object({
|
||||
message: z.string(),
|
||||
entity: zServicesKitSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateStatusSchema
|
||||
*/
|
||||
@ -138,6 +402,22 @@ export const zCreateStatusResponse = z.object({
|
||||
entity: zStatusSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* DealProductAddKitRequest
|
||||
*/
|
||||
export const zDealProductAddKitRequest = z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
kitId: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DealProductAddKitResponse
|
||||
*/
|
||||
export const zDealProductAddKitResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteBoardResponse
|
||||
*/
|
||||
@ -145,6 +425,13 @@ export const zDeleteBoardResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteDealProductResponse
|
||||
*/
|
||||
export const zDeleteDealProductResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteDealResponse
|
||||
*/
|
||||
@ -152,6 +439,27 @@ export const zDeleteDealResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteDealServiceResponse
|
||||
*/
|
||||
export const zDeleteDealServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteProductResponse
|
||||
*/
|
||||
export const zDeleteProductResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteProductServiceResponse
|
||||
*/
|
||||
export const zDeleteProductServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteProjectResponse
|
||||
*/
|
||||
@ -159,6 +467,20 @@ export const zDeleteProjectResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteServiceResponse
|
||||
*/
|
||||
export const zDeleteServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteServicesKitResponse
|
||||
*/
|
||||
export const zDeleteServicesKitResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteStatusResponse
|
||||
*/
|
||||
@ -166,6 +488,13 @@ export const zDeleteStatusResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetAllBuiltInModulesResponse
|
||||
*/
|
||||
export const zGetAllBuiltInModulesResponse = z.object({
|
||||
items: z.array(zBuiltInModuleSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetBoardsResponse
|
||||
*/
|
||||
@ -173,6 +502,20 @@ export const zGetBoardsResponse = z.object({
|
||||
items: z.array(zBoardSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetDealProductsResponse
|
||||
*/
|
||||
export const zGetDealProductsResponse = z.object({
|
||||
items: z.array(zDealProductSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetDealServicesResponse
|
||||
*/
|
||||
export const zGetDealServicesResponse = z.object({
|
||||
items: z.array(zDealServiceSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* PaginationInfoSchema
|
||||
*/
|
||||
@ -189,6 +532,13 @@ export const zGetDealsResponse = z.object({
|
||||
paginationInfo: zPaginationInfoSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* GetProductsResponse
|
||||
*/
|
||||
export const zGetProductsResponse = z.object({
|
||||
items: z.array(zProductSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetProjectsResponse
|
||||
*/
|
||||
@ -196,6 +546,20 @@ export const zGetProjectsResponse = z.object({
|
||||
items: z.array(zProjectSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetServicesKitResponse
|
||||
*/
|
||||
export const zGetServicesKitResponse = z.object({
|
||||
items: z.array(zServicesKitSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetServicesResponse
|
||||
*/
|
||||
export const zGetServicesResponse = z.object({
|
||||
items: z.array(zServiceSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* GetStatusesResponse
|
||||
*/
|
||||
@ -219,6 +583,31 @@ export const zHttpValidationError = z.object({
|
||||
detail: z.optional(z.array(zValidationError)),
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductImageSchema
|
||||
*/
|
||||
export const zProductImageSchema = z.object({
|
||||
id: z.int(),
|
||||
productId: z.int(),
|
||||
imageUrl: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductServicesDuplicateRequest
|
||||
*/
|
||||
export const zProductServicesDuplicateRequest = z.object({
|
||||
dealId: z.int(),
|
||||
sourceDealProductId: z.int(),
|
||||
targetDealProductIds: z.array(z.int()),
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductServicesDuplicateResponse
|
||||
*/
|
||||
export const zProductServicesDuplicateResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
export const zSortDir = z.enum(["asc", "desc"]);
|
||||
|
||||
/**
|
||||
@ -243,6 +632,28 @@ export const zUpdateBoardResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealProductSchema
|
||||
*/
|
||||
export const zUpdateDealProductSchema = z.object({
|
||||
quantity: z.int(),
|
||||
comment: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealProductRequest
|
||||
*/
|
||||
export const zUpdateDealProductRequest = z.object({
|
||||
entity: zUpdateDealProductSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealProductResponse
|
||||
*/
|
||||
export const zUpdateDealProductResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealSchema
|
||||
*/
|
||||
@ -267,11 +678,86 @@ export const zUpdateDealResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealServiceSchema
|
||||
*/
|
||||
export const zUpdateDealServiceSchema = z.object({
|
||||
quantity: z.int(),
|
||||
price: z.number(),
|
||||
isFixedPrice: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealServiceRequest
|
||||
*/
|
||||
export const zUpdateDealServiceRequest = z.object({
|
||||
entity: zUpdateDealServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateDealServiceResponse
|
||||
*/
|
||||
export const zUpdateDealServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProductSchema
|
||||
*/
|
||||
export const zUpdateProductSchema = z.object({
|
||||
name: z.optional(z.union([z.string(), z.null()])),
|
||||
article: z.optional(z.union([z.string(), z.null()])),
|
||||
factoryArticle: z.optional(z.union([z.string(), z.null()])),
|
||||
brand: z.optional(z.union([z.string(), z.null()])),
|
||||
color: z.optional(z.union([z.string(), z.null()])),
|
||||
composition: z.optional(z.union([z.string(), z.null()])),
|
||||
size: z.optional(z.union([z.string(), z.null()])),
|
||||
additionalInfo: z.optional(z.union([z.string(), z.null()])),
|
||||
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProductRequest
|
||||
*/
|
||||
export const zUpdateProductRequest = z.object({
|
||||
entity: zUpdateProductSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProductResponse
|
||||
*/
|
||||
export const zUpdateProductResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProductServiceSchema
|
||||
*/
|
||||
export const zUpdateProductServiceSchema = z.object({
|
||||
price: z.number(),
|
||||
isFixedPrice: z.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProductServiceRequest
|
||||
*/
|
||||
export const zUpdateProductServiceRequest = z.object({
|
||||
entity: zUpdateProductServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProductServiceResponse
|
||||
*/
|
||||
export const zUpdateProductServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateProjectSchema
|
||||
*/
|
||||
export const zUpdateProjectSchema = z.object({
|
||||
name: z.optional(z.union([z.string(), z.null()])),
|
||||
builtInModules: z.optional(z.array(zBuiltInModuleSchema)),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -288,6 +774,57 @@ export const zUpdateProjectResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceSchema
|
||||
*/
|
||||
export const zUpdateServiceSchema = z.object({
|
||||
id: z.int(),
|
||||
name: z.string(),
|
||||
category: zServiceCategorySchema,
|
||||
price: z.number(),
|
||||
serviceType: z.int(),
|
||||
priceRanges: z.array(zServicePriceRangeSchema),
|
||||
cost: z.union([z.number(), z.null()]),
|
||||
lexorank: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceRequest
|
||||
*/
|
||||
export const zUpdateServiceRequest = z.object({
|
||||
entity: zUpdateServiceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServiceResponse
|
||||
*/
|
||||
export const zUpdateServiceResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServicesKitSchema
|
||||
*/
|
||||
export const zUpdateServicesKitSchema = z.object({
|
||||
name: z.string(),
|
||||
serviceType: z.int(),
|
||||
servicesIds: z.array(z.int()),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServicesKitRequest
|
||||
*/
|
||||
export const zUpdateServicesKitRequest = z.object({
|
||||
entity: zUpdateServicesKitSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateServicesKitResponse
|
||||
*/
|
||||
export const zUpdateServicesKitResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* UpdateStatusSchema
|
||||
*/
|
||||
@ -420,6 +957,17 @@ export const zUpdateDealData = z.object({
|
||||
*/
|
||||
export const zUpdateDealResponse2 = zUpdateDealResponse;
|
||||
|
||||
export const zGetBuiltInModulesData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetBuiltInModulesResponse = zGetAllBuiltInModulesResponse;
|
||||
|
||||
export const zGetProjectsData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
@ -517,3 +1065,321 @@ export const zUpdateStatusData = z.object({
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateStatusResponse2 = zUpdateStatusResponse;
|
||||
|
||||
export const zGetDealProductsData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetDealProductsResponse2 = zGetDealProductsResponse;
|
||||
|
||||
export const zCreateDealProductData = z.object({
|
||||
body: zCreateDealProductRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateDealProductResponse2 = zCreateDealProductResponse;
|
||||
|
||||
export const zDeleteDealProductData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteDealProductResponse2 = zDeleteDealProductResponse;
|
||||
|
||||
export const zUpdateDealProductData = z.object({
|
||||
body: zUpdateDealProductRequest,
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateDealProductResponse2 = zUpdateDealProductResponse;
|
||||
|
||||
export const zAddKitToDealProductData = z.object({
|
||||
body: zDealProductAddKitRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zAddKitToDealProductResponse = zDealProductAddKitResponse;
|
||||
|
||||
export const zCreateDealProductServiceData = z.object({
|
||||
body: zCreateProductServiceRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateDealProductServiceResponse = zCreateProductServiceResponse;
|
||||
|
||||
export const zDeleteDealProductServiceData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
serviceId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteDealProductServiceResponse = zDeleteProductServiceResponse;
|
||||
|
||||
export const zUpdateDealProductServiceData = z.object({
|
||||
body: zUpdateProductServiceRequest,
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
productId: z.int(),
|
||||
serviceId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateDealProductServiceResponse = zUpdateProductServiceResponse;
|
||||
|
||||
export const zDuplicateProductServicesData = z.object({
|
||||
body: zProductServicesDuplicateRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDuplicateProductServicesResponse =
|
||||
zProductServicesDuplicateResponse;
|
||||
|
||||
export const zGetDealServicesData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetDealServicesResponse2 = zGetDealServicesResponse;
|
||||
|
||||
export const zCreateDealServiceData = z.object({
|
||||
body: zCreateDealServiceRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateDealServiceResponse2 = zCreateDealServiceResponse;
|
||||
|
||||
export const zDeleteDealServiceData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
serviceId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteDealServiceResponse2 = zDeleteDealServiceResponse;
|
||||
|
||||
export const zUpdateDealServiceData = z.object({
|
||||
body: zUpdateDealServiceRequest,
|
||||
path: z.object({
|
||||
dealId: z.int(),
|
||||
serviceId: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateDealServiceResponse2 = zUpdateDealServiceResponse;
|
||||
|
||||
export const zGetProductsData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(
|
||||
z.object({
|
||||
searchInput: z.optional(z.union([z.string(), z.null()])),
|
||||
page: z.optional(z.union([z.int(), z.null()])),
|
||||
itemsPerPage: z.optional(z.union([z.int(), z.null()])),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetProductsResponse2 = zGetProductsResponse;
|
||||
|
||||
export const zCreateProductData = z.object({
|
||||
body: zCreateProductRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateProductResponse2 = zCreateProductResponse;
|
||||
|
||||
export const zDeleteProductData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteProductResponse2 = zDeleteProductResponse;
|
||||
|
||||
export const zUpdateProductData = z.object({
|
||||
body: zUpdateProductRequest,
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateProductResponse2 = zUpdateProductResponse;
|
||||
|
||||
export const zGetServicesData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetServicesResponse2 = zGetServicesResponse;
|
||||
|
||||
export const zCreateServiceData = z.object({
|
||||
body: zCreateServiceRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateServiceResponse2 = zCreateServiceResponse;
|
||||
|
||||
export const zDeleteServiceData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteServiceResponse2 = zDeleteServiceResponse;
|
||||
|
||||
export const zUpdateServiceData = z.object({
|
||||
body: zUpdateServiceRequest,
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateServiceResponse2 = zUpdateServiceResponse;
|
||||
|
||||
export const zGetServicesKitsData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zGetServicesKitsResponse = zGetServicesKitResponse;
|
||||
|
||||
export const zCreateServicesKitData = z.object({
|
||||
body: zCreateServicesKitRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateServicesKitResponse2 = zCreateServicesKitResponse;
|
||||
|
||||
export const zDeleteServicesKitData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteServicesKitResponse2 = zDeleteServicesKitResponse;
|
||||
|
||||
export const zUpdateServicesKitData = z.object({
|
||||
body: zUpdateServicesKitRequest,
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUpdateServicesKitResponse2 = zUpdateServicesKitResponse;
|
||||
|
||||
63
src/modals/base/BaseFormModal/BaseFormModal.tsx
Normal file
63
src/modals/base/BaseFormModal/BaseFormModal.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Flex } from "@mantine/core";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import BaseFormModalActions from "@/modals/base/BaseFormModal/BaseFormModalActions";
|
||||
|
||||
export type CreateProps<TCreate> = {
|
||||
onCreate: (values: TCreate) => void;
|
||||
isEditing: false;
|
||||
};
|
||||
|
||||
export type EditProps<TEntity, TUpdate> = {
|
||||
onChange: (values: TUpdate) => void;
|
||||
entity: TEntity;
|
||||
isEditing: true;
|
||||
};
|
||||
|
||||
export type CreateEditFormProps<
|
||||
TCreate,
|
||||
TUpdate = TCreate,
|
||||
TEntity = TUpdate,
|
||||
> = CreateProps<TCreate> | EditProps<TEntity, TUpdate>;
|
||||
|
||||
export type BaseFormProps<T> = {
|
||||
form: UseFormReturnType<Partial<T>>;
|
||||
onClose: () => void;
|
||||
closeOnSubmit?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type Props<TCreate, TUpdate, TEntity> = BaseFormProps<TEntity> &
|
||||
CreateEditFormProps<TCreate, TUpdate, TEntity>;
|
||||
|
||||
const BaseFormModal = <TCreate, TUpdate = TCreate, TEntity = TUpdate>(
|
||||
props: Props<TCreate, TUpdate, TEntity>
|
||||
) => {
|
||||
const { closeOnSubmit = false } = props;
|
||||
|
||||
const onSubmit = (values: Partial<TEntity>) => {
|
||||
if (props.isEditing) {
|
||||
props.onChange({ ...props.entity, ...values } as TUpdate);
|
||||
} else {
|
||||
props.onCreate(values as TCreate);
|
||||
}
|
||||
|
||||
if (closeOnSubmit) {
|
||||
props.form.reset();
|
||||
props.onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={props.form.onSubmit(values => onSubmit(values))}>
|
||||
<Flex
|
||||
gap={"xs"}
|
||||
direction={"column"}>
|
||||
{props.children}
|
||||
<BaseFormModalActions {...props} />
|
||||
</Flex>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseFormModal;
|
||||
25
src/modals/base/BaseFormModal/BaseFormModalActions.tsx
Normal file
25
src/modals/base/BaseFormModal/BaseFormModalActions.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { FC } from "react";
|
||||
import { Button, Flex } from "@mantine/core";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const BaseFormModalActions: FC<Props> = ({ onClose }) => (
|
||||
<Flex
|
||||
justify={"flex-end"}
|
||||
gap={"xs"}>
|
||||
<Button
|
||||
variant={"subtle"}
|
||||
onClick={onClose}>
|
||||
Отменить
|
||||
</Button>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default BaseFormModalActions;
|
||||
@ -2,10 +2,22 @@ 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/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/DealProductEditorModal/DealProductEditorModal";
|
||||
import DealServiceEditorModal from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/DealServiceEditorModal/DealServiceEditorModal";
|
||||
import DuplicateServicesModal from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/DuplicateServicesModal/DuplicateServicesModal";
|
||||
import ProductEditorModal from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/ProductEditorModal/ProductEditorModal";
|
||||
import ProductServiceEditorModal from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/ProductServiceEditorModal/ProductServiceEditorModal";
|
||||
import ServicesKitSelectModal from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/ServicesKitSelectModal/ServicesKitSelectModal";
|
||||
|
||||
export const modals = {
|
||||
enterNameModal: EnterNameModal,
|
||||
dealsTableFiltersModal: DealsTableFiltersModal,
|
||||
dealsBoardFiltersModal: DealsBoardFiltersModal,
|
||||
dealsScheduleFiltersModal: DealsScheduleFiltersModal,
|
||||
productEditorModal: ProductEditorModal,
|
||||
dealProductEditorModal: DealProductEditorModal,
|
||||
dealServiceEditorModal: DealServiceEditorModal,
|
||||
productServiceEditorModal: ProductServiceEditorModal,
|
||||
duplicateServicesModal: DuplicateServicesModal,
|
||||
servicesKitSelectModal: ServicesKitSelectModal,
|
||||
};
|
||||
|
||||
13
src/modules/connectModules.tsx
Normal file
13
src/modules/connectModules.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import FulfillmentBaseTab from "@/modules/dealModules/dealEditorTabs/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;
|
||||
@ -0,0 +1,38 @@
|
||||
.container {
|
||||
display: flex;
|
||||
//flex-direction: column;
|
||||
gap: rem(10);
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.container-disabled {
|
||||
}
|
||||
|
||||
.products-list {
|
||||
width: 52%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-container-wrapper {
|
||||
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||
border-radius: var(--item-border-radius);
|
||||
padding: rem(10);
|
||||
}
|
||||
|
||||
.card-container-buttons {
|
||||
gap: rem(10);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: auto;
|
||||
width: 100%;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import { FC } from "react";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import FulfillmentBaseTabBody from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/FulfillmentBaseTabBody/FulfillmentBaseTabBody";
|
||||
import { FulfillmentBaseContextProvider } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
type Props = {
|
||||
value: DealSchema;
|
||||
};
|
||||
|
||||
const FulfillmentBaseTab: FC<Props> = ({ value }) => {
|
||||
return (
|
||||
<FulfillmentBaseContextProvider deal={value}>
|
||||
<FulfillmentBaseTabBody />
|
||||
</FulfillmentBaseContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FulfillmentBaseTab;
|
||||
@ -0,0 +1,71 @@
|
||||
import { FC } from "react";
|
||||
import { Flex, rem } from "@mantine/core";
|
||||
import DealServiceRow from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServiceRow";
|
||||
import DealServicesTitle from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServicesTitle";
|
||||
import DealServicesTotalLabel from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServicesTotalLabel";
|
||||
import ServicesActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/ServicesActions";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
const DealServicesTable: FC = () => {
|
||||
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
|
||||
|
||||
// const isLocked = isDealLocked(deal); // TODO bills
|
||||
|
||||
// const [currentService, setCurrentService] = useState<
|
||||
// DealServiceSchema | undefined
|
||||
// >();
|
||||
// const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
||||
|
||||
// const onEmployeeClick = (item: CardServiceSchema) => {
|
||||
// if (!onChange) return;
|
||||
// setCurrentService(item);
|
||||
// setEmployeesModalVisible(true);
|
||||
// };
|
||||
// const onEmployeeModalClose = () => {
|
||||
// setEmployeesModalVisible(false);
|
||||
// setCurrentService(undefined);
|
||||
// };
|
||||
// const getCurrentEmployees = (): UserSchema[] => {
|
||||
// if (!currentService) return [];
|
||||
// const item = items.find(
|
||||
// i => i.service.id === currentService.service.id
|
||||
// );
|
||||
// if (!item) return [];
|
||||
// return item.employees;
|
||||
// };
|
||||
// const onEmployeesChange = (items: UserSchema[]) => {
|
||||
// if (!currentService || !onChange) return;
|
||||
// debouncedOnChange({
|
||||
// ...currentService,
|
||||
// employees: items,
|
||||
// });
|
||||
// };
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
h={"100%"}>
|
||||
<Flex
|
||||
h={"100%"}
|
||||
direction={"column"}>
|
||||
<DealServicesTitle />
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}>
|
||||
{dealServicesList.dealServices.map(dealService => (
|
||||
<DealServiceRow
|
||||
key={dealService.service.id}
|
||||
value={dealService}
|
||||
onDelete={dealServicesCrud.onDelete}
|
||||
onChange={dealServicesCrud.onUpdate}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<DealServicesTotalLabel />
|
||||
<ServicesActions />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
export default DealServicesTable;
|
||||
@ -0,0 +1,114 @@
|
||||
import { FC } from "react";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import { isNumber } from "lodash";
|
||||
import {
|
||||
ActionIcon,
|
||||
Divider,
|
||||
Group,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
import { DealServiceSchema } from "@/lib/client";
|
||||
import LockCheckbox from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/LockCheckbox/LockCheckbox";
|
||||
|
||||
type Props = {
|
||||
value: DealServiceSchema;
|
||||
onChange: (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
value: DealServiceSchema
|
||||
) => void;
|
||||
onDelete: (value: DealServiceSchema) => void;
|
||||
};
|
||||
|
||||
const DealServiceRow: FC<Props> = ({ value, onChange, onDelete }) => {
|
||||
const debouncedOnChange = useDebouncedCallback(
|
||||
async (item: DealServiceSchema) => {
|
||||
onChange(item.dealId, item.serviceId, item);
|
||||
},
|
||||
200
|
||||
);
|
||||
|
||||
const onQuantityChange = (item: DealServiceSchema, quantity: number) => {
|
||||
debouncedOnChange({ ...item, quantity });
|
||||
};
|
||||
|
||||
const onPriceChange = (item: DealServiceSchema, price: number) => {
|
||||
debouncedOnChange({
|
||||
...item,
|
||||
price,
|
||||
isFixedPrice: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
w={"100%"}
|
||||
gap={"xs"}>
|
||||
<Divider />
|
||||
<Text>{value.service.name}</Text>
|
||||
<Group>
|
||||
<Tooltip
|
||||
onClick={() => onDelete(value)}
|
||||
label="Удалить услугу">
|
||||
<ActionIcon variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{/*<Tooltip label="Сотрудники">*/}
|
||||
{/* <ActionIcon*/}
|
||||
{/* onClick={() => onEmployeeClick(service)}*/}
|
||||
{/* variant={"default"}>*/}
|
||||
{/* <IconUsersGroup />*/}
|
||||
{/* </ActionIcon>*/}
|
||||
{/*</Tooltip>*/}
|
||||
<NumberInput
|
||||
flex={1}
|
||||
suffix={" шт."}
|
||||
onChange={event =>
|
||||
isNumber(event) && onQuantityChange(value, event)
|
||||
}
|
||||
value={value.quantity}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
/>
|
||||
<NumberInput
|
||||
flex={1}
|
||||
onChange={event =>
|
||||
isNumber(event) && onPriceChange(value, event)
|
||||
}
|
||||
suffix={"₽"}
|
||||
value={value.price}
|
||||
disabled={value.isFixedPrice}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
rightSectionProps={{
|
||||
style: {
|
||||
display: "flex",
|
||||
cursor: "pointer",
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
}}
|
||||
rightSection={
|
||||
<LockCheckbox
|
||||
label={"Зафиксировать цену"}
|
||||
variant={"default"}
|
||||
value={value.isFixedPrice}
|
||||
onChange={isFixedPrice =>
|
||||
debouncedOnChange({
|
||||
...value,
|
||||
isFixedPrice,
|
||||
})
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealServiceRow;
|
||||
@ -0,0 +1,12 @@
|
||||
import { rem, Title } from "@mantine/core";
|
||||
|
||||
const DealServicesTitle = () => (
|
||||
<Title
|
||||
order={3}
|
||||
w={"100%"}
|
||||
mb={rem(10)}>
|
||||
Общие услуги
|
||||
</Title>
|
||||
);
|
||||
|
||||
export default DealServicesTitle;
|
||||
@ -0,0 +1,26 @@
|
||||
import { useMemo } from "react";
|
||||
import { rem, Title } from "@mantine/core";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
const DealServicesTotalLabel = () => {
|
||||
const { dealServicesList } = useFulfillmentBaseContext();
|
||||
const total = useMemo(
|
||||
() =>
|
||||
dealServicesList.dealServices.reduce(
|
||||
(acc, item) => acc + item.price * item.quantity,
|
||||
0
|
||||
),
|
||||
[dealServicesList.dealServices]
|
||||
);
|
||||
|
||||
return (
|
||||
<Title
|
||||
style={{ textAlign: "end" }}
|
||||
mt={rem(10)}
|
||||
order={3}>
|
||||
Итог: {total}₽
|
||||
</Title>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealServicesTotalLabel;
|
||||
@ -0,0 +1,58 @@
|
||||
import { Button, Flex, rem } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
const ServicesActions = () => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
// const onAddKitClick = () => {
|
||||
// if (!onKitAdd) return;
|
||||
// modals.openContextModal({
|
||||
// modal: "servicesKitSelectModal",
|
||||
// innerProps: {
|
||||
// onSelect: onKitAdd,
|
||||
// serviceType: ServiceType.DEAL_SERVICE,
|
||||
// },
|
||||
// withCloseButton: false,
|
||||
// });
|
||||
// };
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
mt={"auto"}>
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
fullWidth
|
||||
variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
{/*<Button*/}
|
||||
{/* onClick={onAddKitClick}*/}
|
||||
{/* fullWidth*/}
|
||||
{/* variant={"default"}>*/}
|
||||
{/* Добавить набор услуг*/}
|
||||
{/*</Button>*/}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesActions;
|
||||
@ -0,0 +1,34 @@
|
||||
import { Flex, ScrollArea, Stack } from "@mantine/core";
|
||||
import DealServicesTable from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/DealServicesTable";
|
||||
import ProductsActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductsActions/ProductsActions";
|
||||
import ProductView from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/ProductView";
|
||||
import { useFulfillmentBaseContext } from "../../contexts/FulfillmentBaseContext";
|
||||
|
||||
const FulfillmentBaseTabBody = () => {
|
||||
const { dealProductsList } = useFulfillmentBaseContext();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
p={"md"}
|
||||
gap={"xs"}>
|
||||
<ScrollArea
|
||||
offsetScrollbars={"y"}
|
||||
mah={"91vh"}>
|
||||
<Stack>
|
||||
{dealProductsList.dealProducts.map((dealProduct, index) => (
|
||||
<ProductView
|
||||
dealProduct={dealProduct}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
<Stack>
|
||||
<DealServicesTable />
|
||||
<ProductsActions />
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default FulfillmentBaseTabBody;
|
||||
@ -0,0 +1,115 @@
|
||||
import ShippingWarehouseAutocomplete
|
||||
from "../../../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||||
import { CardService, ShippingWarehouseSchema } from "../../../../../../client";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useCardPageContext } from "../../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
|
||||
import { Button, Checkbox, Stack } from "@mantine/core";
|
||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { isEqual } from "lodash";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../../../../redux/store.ts";
|
||||
|
||||
|
||||
type GeneralDataFormType = {
|
||||
shippingWarehouse?: ShippingWarehouseSchema | null | string;
|
||||
isServicesProfitAccounted: boolean;
|
||||
}
|
||||
|
||||
const GeneralDataForm = () => {
|
||||
const { selectedCard: card, refetchCard } = useCardPageContext();
|
||||
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||
if (!card) return <></>;
|
||||
|
||||
const [initialValues, setInitialValues] = useState<GeneralDataFormType>(card);
|
||||
|
||||
const form = useForm<GeneralDataFormType>({
|
||||
initialValues,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const data = card ?? {};
|
||||
setInitialValues(data);
|
||||
form.setValues(data);
|
||||
}, [card]);
|
||||
|
||||
const isShippingWarehouse = (
|
||||
value: ShippingWarehouseSchema | string | null | undefined,
|
||||
): value is ShippingWarehouseSchema => {
|
||||
return !!value && !["string"].includes(typeof value);
|
||||
};
|
||||
|
||||
const onSubmit = (values: GeneralDataFormType) => {
|
||||
if (!card) return;
|
||||
|
||||
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse)
|
||||
? values.shippingWarehouse.name
|
||||
: values.shippingWarehouse;
|
||||
|
||||
CardService.updateProductsAndServicesGeneralInfo({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
data: {
|
||||
...values,
|
||||
shippingWarehouse,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
|
||||
refetchCard();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||
<Stack>
|
||||
<ShippingWarehouseAutocomplete
|
||||
placeholder={isDealsViewer ? "" : "Введите склад отгрузки"}
|
||||
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);
|
||||
}}
|
||||
readOnly={isDealsViewer}
|
||||
/>
|
||||
{!isDealsViewer && (
|
||||
<>
|
||||
<Checkbox
|
||||
label={"Учет выручки в статистике"}
|
||||
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
|
||||
/>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}
|
||||
disabled={isEqual(initialValues, form.values)}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralDataForm;
|
||||
@ -0,0 +1,28 @@
|
||||
import { FC } from "react";
|
||||
import { IconLock, IconLockOpen } from "@tabler/icons-react";
|
||||
import { ActionIcon, CheckboxProps, Tooltip } from "@mantine/core";
|
||||
|
||||
type RestProps = {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
};
|
||||
|
||||
type Props = Omit<CheckboxProps, "value" | "onChange"> & RestProps;
|
||||
|
||||
const LockCheckbox: FC<Props> = props => {
|
||||
const getIcon = () => (props.value ? <IconLock /> : <IconLockOpen />);
|
||||
|
||||
const handleChange = () => props.onChange(!props.value);
|
||||
|
||||
return (
|
||||
<Tooltip label={props.label}>
|
||||
<ActionIcon
|
||||
onClick={handleChange}
|
||||
variant={props.variant}>
|
||||
{getIcon()}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default LockCheckbox;
|
||||
@ -0,0 +1,68 @@
|
||||
import { CardSchema } from "../../../../../../client";
|
||||
import ButtonCopy from "../../../../../../components/ButtonCopy/ButtonCopy.tsx";
|
||||
import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
|
||||
import { getCurrentDateTimeForFilename } from "../../../../../../shared/lib/date.ts";
|
||||
import FileSaver from "file-saver";
|
||||
import { Button, Popover, Stack } from "@mantine/core";
|
||||
|
||||
type Props = {
|
||||
card: CardSchema;
|
||||
}
|
||||
|
||||
const PaymentLinkButton = ({ card }: Props) => {
|
||||
if ((!card.billRequests || card.billRequests.length === 0) && (!card?.group?.billRequests || card?.group?.billRequests.length === 0)) {
|
||||
return (
|
||||
<ButtonCopyControlled
|
||||
onCopyClick={() => {
|
||||
const date =
|
||||
getCurrentDateTimeForFilename();
|
||||
FileSaver.saveAs(
|
||||
`${import.meta.env.VITE_API_URL}/card/billing-document/${card.id}`,
|
||||
`bill_${card.id}_${date}.pdf`,
|
||||
);
|
||||
}}
|
||||
copied={false}
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
>
|
||||
Ссылка на оплату (PDF)
|
||||
</ButtonCopyControlled>
|
||||
);
|
||||
}
|
||||
|
||||
const requests = (card?.group ? card?.group?.billRequests : card.billRequests) ?? [];
|
||||
const urls = requests.map(request => request.pdfUrl).filter(url => url !== null);
|
||||
|
||||
if (urls.length === 1) {
|
||||
return (
|
||||
<ButtonCopy
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
value={urls[0]}
|
||||
>
|
||||
Ссылка на оплату
|
||||
</ButtonCopy>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover width={380} position="bottom" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button variant={"default"}>Ссылки на оплату</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Stack gap={"md"}>
|
||||
{urls.map((url, i) => (
|
||||
<ButtonCopy
|
||||
key={i}
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
value={url}
|
||||
>
|
||||
{`Ссылка на оплату (часть ${String(i + 1)})`}
|
||||
</ButtonCopy>
|
||||
))}
|
||||
</Stack>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentLinkButton;
|
||||
@ -0,0 +1,63 @@
|
||||
import { ActionIcon, Group, Tooltip } from "@mantine/core";
|
||||
import styles from "../../../../../../pages/CardsPage/ui/CardsPage.module.css";
|
||||
import { CardSchema, CardService } from "../../../../../../client";
|
||||
import { base64ToBlob } from "../../../../../../shared/lib/utils.ts";
|
||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||
import { IconBarcode, IconPrinter } from "@tabler/icons-react";
|
||||
|
||||
|
||||
type Props = {
|
||||
card: CardSchema;
|
||||
}
|
||||
|
||||
const PrintDealBarcodesButton = ({ card }: Props) => {
|
||||
return (
|
||||
<Group wrap={"nowrap"}>
|
||||
<Tooltip
|
||||
className={styles["print-deals-button"]}
|
||||
label={"Распечатать штрихкоды сделки"}
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={async () => {
|
||||
const response =
|
||||
await CardService.getCardProductsBarcodesPdf({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
},
|
||||
});
|
||||
const pdfBlob = base64ToBlob(
|
||||
response.base64String,
|
||||
response.mimeType,
|
||||
);
|
||||
const pdfUrl = URL.createObjectURL(pdfBlob);
|
||||
const pdfWindow = window.open(pdfUrl);
|
||||
if (!pdfWindow) {
|
||||
notifications.error({ message: "Ошибка" });
|
||||
return;
|
||||
}
|
||||
pdfWindow.onload = () => {
|
||||
pdfWindow.print();
|
||||
};
|
||||
}}
|
||||
variant={"default"}>
|
||||
<IconBarcode />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={"Распечатать сделку"}>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
const pdfWindow = window.open(
|
||||
`${import.meta.env.VITE_API_URL}/card/tech-spec/${card.id}`,
|
||||
);
|
||||
if (!pdfWindow) return;
|
||||
pdfWindow.print();
|
||||
}}
|
||||
variant={"default"}>
|
||||
<IconPrinter />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintDealBarcodesButton;
|
||||
@ -0,0 +1,68 @@
|
||||
import { FC } from "react";
|
||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import ImageDropzone from "@/components/ui/ImageDropzone/ImageDropzone";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
import useImageDropzone from "./useImageDropzone";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ProductImageDropzone: FC<Props> = ({
|
||||
imageUrlInputProps,
|
||||
productId,
|
||||
}: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!productId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// TODO SEND REQUEST
|
||||
|
||||
// ProductService.uploadProductImage({
|
||||
// productId,
|
||||
// formData: {
|
||||
// upload_file: file,
|
||||
// },
|
||||
// })
|
||||
// .then(({ ok, message, imageUrl }) => {
|
||||
// notifications.guess(ok, { message });
|
||||
// setIsLoading(false);
|
||||
//
|
||||
// if (!ok || !imageUrl) {
|
||||
// setShowDropzone(true);
|
||||
// return;
|
||||
// }
|
||||
// imageUrlInputProps?.onChange(imageUrl);
|
||||
// setShowDropzone(false);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// notifications.error({ message: error.toString() });
|
||||
// setShowDropzone(true);
|
||||
// setIsLoading(false);
|
||||
// });
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageDropzone
|
||||
onDrop={onDrop}
|
||||
imageDropzone={imageDropzoneProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductImageDropzone;
|
||||
@ -0,0 +1,27 @@
|
||||
import { useState } from "react";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type Props = {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
};
|
||||
|
||||
const useImageDropzone = ({ imageUrlInputProps }: Props) => {
|
||||
const [showDropzone, setShowDropzone] = useState(
|
||||
!(
|
||||
typeof imageUrlInputProps?.value === "string" &&
|
||||
imageUrlInputProps.value.trim() !== ""
|
||||
)
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
return {
|
||||
showDropzone,
|
||||
setShowDropzone,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
imageUrlInputProps,
|
||||
};
|
||||
};
|
||||
|
||||
export default useImageDropzone;
|
||||
@ -0,0 +1,55 @@
|
||||
import { FC, useState } from "react";
|
||||
import { omit } from "lodash";
|
||||
import { Loader, OptionsFilter } from "@mantine/core";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import useProductsList from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/hooks/lists/useProductsList";
|
||||
import renderProductOption from "./utils/renderProductOption";
|
||||
|
||||
type RestProps = {
|
||||
clientId: number;
|
||||
};
|
||||
|
||||
const MAX_PRODUCTS = 200;
|
||||
|
||||
type Props = Omit<ObjectSelectProps<ProductSchema>, "data"> & RestProps;
|
||||
|
||||
const ProductSelect: FC<Props> = (props: Props) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [debounced] = useDebouncedValue(searchValue, 500);
|
||||
const { products, isLoading } = useProductsList({
|
||||
// clientId: props.clientId,
|
||||
searchInput: debounced,
|
||||
page: 0,
|
||||
itemsPerPage: MAX_PRODUCTS,
|
||||
});
|
||||
const restProps = omit(props, ["clientId"]);
|
||||
|
||||
const optionsFilter: OptionsFilter = ({ options }) => options;
|
||||
const setSearchValueImpl = (value: string) => {
|
||||
const names = products.map(product => product.name);
|
||||
if (names.includes(value)) return;
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
rightSection={
|
||||
isLoading || searchValue !== debounced ? (
|
||||
<Loader size={"sm"} />
|
||||
) : null
|
||||
}
|
||||
onSearchChange={setSearchValueImpl}
|
||||
renderOption={renderProductOption(products)}
|
||||
searchable
|
||||
{...restProps}
|
||||
data={products}
|
||||
filter={optionsFilter}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductSelect;
|
||||
@ -0,0 +1,51 @@
|
||||
import {
|
||||
ComboboxItem,
|
||||
ComboboxLikeRenderOptionInput,
|
||||
SelectProps,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import ProductFieldsList from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductFieldsList";
|
||||
|
||||
const renderProductOption = (
|
||||
products: ProductSchema[]
|
||||
): SelectProps["renderOption"] => {
|
||||
return (item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const product = products.find(
|
||||
product => product.id === Number(item.option.value)
|
||||
);
|
||||
if (!product) return item.option.label;
|
||||
// const imageUrl =
|
||||
// product.images && product.images[0]
|
||||
// ? product.images[0].imageUrl
|
||||
// : undefined;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
multiline
|
||||
label={
|
||||
<>
|
||||
<ProductFieldsList product={product} />
|
||||
{/*{imageUrl && (*/}
|
||||
{/* <Image*/}
|
||||
{/* src={imageUrl}*/}
|
||||
{/* alt={product.name}*/}
|
||||
{/* maw={rem(250)}*/}
|
||||
{/* />*/}
|
||||
{/*)}*/}
|
||||
</>
|
||||
}>
|
||||
<div>
|
||||
{product.name}
|
||||
<br />
|
||||
{/*{product.barcodes && (*/}
|
||||
{/* <Text size={"xs"}>{product.barcodes[0]}</Text>*/}
|
||||
{/*)}*/}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default renderProductOption;
|
||||
@ -0,0 +1,36 @@
|
||||
.container {
|
||||
display: flex;
|
||||
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||
border-radius: var(--item-border-radius);
|
||||
gap: rem(20);
|
||||
padding: rem(10);
|
||||
margin-bottom: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
max-height: rem(250);
|
||||
max-width: rem(250);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.services-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.data-container {
|
||||
max-width: rem(250);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.attributes-container {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
import { FC } from "react";
|
||||
import { isNumber } from "lodash";
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
Image,
|
||||
NumberInput,
|
||||
rem,
|
||||
Stack,
|
||||
Textarea,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
import { modals } from "@mantine/modals";
|
||||
import {
|
||||
addKitToDealProduct,
|
||||
DealProductSchema,
|
||||
duplicateProductServices,
|
||||
ServicesKitSchema,
|
||||
} from "@/lib/client";
|
||||
import ProductFieldsList from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductFieldsList";
|
||||
import ProductViewActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductViewActions";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
import { ServiceType } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/types/service";
|
||||
import ProductServicesTable from "./components/ProductServicesTable";
|
||||
|
||||
type Props = {
|
||||
dealProduct: DealProductSchema;
|
||||
};
|
||||
|
||||
const ProductView: FC<Props> = ({ dealProduct }) => {
|
||||
const { dealProductsCrud, deal, dealProductsList } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const debouncedOnChange = useDebouncedCallback(
|
||||
(newValues: Partial<DealProductSchema>) => {
|
||||
dealProductsCrud.onUpdate(
|
||||
dealProduct.dealId,
|
||||
dealProduct.productId,
|
||||
{
|
||||
...dealProduct,
|
||||
...newValues,
|
||||
}
|
||||
);
|
||||
},
|
||||
200
|
||||
);
|
||||
|
||||
const duplicateServices = (
|
||||
sourceDealProduct: DealProductSchema,
|
||||
targetDealProducts: DealProductSchema[]
|
||||
) => {
|
||||
duplicateProductServices({
|
||||
body: {
|
||||
dealId: deal.id,
|
||||
sourceDealProductId: sourceDealProduct.productId,
|
||||
targetDealProductIds: targetDealProducts.map(p => p.productId),
|
||||
},
|
||||
})
|
||||
.then(() => dealProductsList.refetch())
|
||||
.catch(err => console.error(err));
|
||||
};
|
||||
|
||||
const onDuplicateServices = (sourceDealProduct: DealProductSchema) => {
|
||||
modals.openContextModal({
|
||||
modal: "duplicateServicesModal",
|
||||
title: "Дублирование услуг",
|
||||
size: "lg",
|
||||
innerProps: {
|
||||
dealProducts: dealProductsList.dealProducts,
|
||||
sourceDealProduct,
|
||||
duplicateServices,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const onServicesKitAdd = (servicesKit: ServicesKitSchema) => {
|
||||
addKitToDealProduct({
|
||||
body: {
|
||||
dealId: dealProduct.dealId,
|
||||
productId: dealProduct.productId,
|
||||
kitId: servicesKit.id,
|
||||
},
|
||||
})
|
||||
.then(() => dealProductsList.refetch())
|
||||
.catch(err => console.error(err));
|
||||
};
|
||||
|
||||
const onAddKitClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "servicesKitSelectModal",
|
||||
innerProps: {
|
||||
onSelect: onServicesKitAdd,
|
||||
serviceType: ServiceType.PRODUCT_SERVICE,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
p={"sm"}
|
||||
style={{ display: "flex", flexDirection: "row" }}>
|
||||
<Stack gap={"sm"}>
|
||||
<Image
|
||||
flex={1}
|
||||
radius={rem(10)}
|
||||
fit={"cover"}
|
||||
// src={dealProduct.product.imageUrl}
|
||||
/>
|
||||
<Title order={3}>{dealProduct.product.name}</Title>
|
||||
<ProductFieldsList product={dealProduct.product} />
|
||||
{/*<Text>*/}
|
||||
{/* Штрихкоды:*/}
|
||||
{/*{value.product.barcodes.join(", ")}*/}
|
||||
{/*</Text>*/}
|
||||
<Box />
|
||||
<NumberInput
|
||||
mt={rem(10)}
|
||||
suffix={" шт."}
|
||||
value={dealProduct.quantity}
|
||||
onChange={quantity =>
|
||||
isNumber(quantity) && debouncedOnChange({ quantity })
|
||||
}
|
||||
placeholder={"Введите количество товара"}
|
||||
/>
|
||||
<Textarea
|
||||
mih={rem(140)}
|
||||
styles={{
|
||||
wrapper: { height: "90%" },
|
||||
input: { height: "90%" },
|
||||
}}
|
||||
my={rem(10)}
|
||||
defaultValue={dealProduct.comment}
|
||||
onChange={event =>
|
||||
debouncedOnChange({
|
||||
comment: event.currentTarget.value,
|
||||
})
|
||||
}
|
||||
placeholder={"Комментарий"}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<ProductServicesTable
|
||||
dealProduct={dealProduct}
|
||||
onDuplicateServices={() => onDuplicateServices(dealProduct)}
|
||||
onKitAdd={onAddKitClick}
|
||||
/>
|
||||
<ProductViewActions dealProduct={dealProduct} />
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductView;
|
||||
@ -0,0 +1,37 @@
|
||||
import { FC } from "react";
|
||||
import { isNil } from "lodash";
|
||||
import { Text } from "@mantine/core";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
|
||||
type ProductFieldNames = {
|
||||
[K in keyof ProductSchema]: string;
|
||||
};
|
||||
|
||||
export const ProductFieldNames: Partial<ProductFieldNames> = {
|
||||
color: "Цвет",
|
||||
article: "Артикул",
|
||||
size: "Размер",
|
||||
brand: "Бренд",
|
||||
composition: "Состав",
|
||||
additionalInfo: "Доп. информация",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
product: ProductSchema;
|
||||
};
|
||||
|
||||
const ProductFieldsList: FC<Props> = ({ product }) => {
|
||||
const fieldList = Object.entries(product).map(([key, value]) => {
|
||||
const fieldName = ProductFieldNames[key as keyof ProductSchema];
|
||||
if (!fieldName || isNil(value) || value === "") return null;
|
||||
return (
|
||||
<Text key={fieldName}>
|
||||
{fieldName}: {value.toString()}{" "}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
return <>{fieldList}</>;
|
||||
};
|
||||
|
||||
export default ProductFieldsList;
|
||||
@ -0,0 +1,146 @@
|
||||
import { FC } from "react";
|
||||
import { IconMoodSad } from "@tabler/icons-react";
|
||||
import { Button, Flex, Group, Stack, Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||
import { DealProductSchema, ProductServiceSchema } from "@/lib/client";
|
||||
import useProductServicesTableColumns from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/hooks/useProductServicesTableColumns";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
type Props = {
|
||||
dealProduct: DealProductSchema;
|
||||
onDuplicateServices?: () => void;
|
||||
onKitAdd?: () => void;
|
||||
};
|
||||
|
||||
const ProductServicesTable: FC<Props> = ({
|
||||
dealProduct,
|
||||
onDuplicateServices,
|
||||
onKitAdd,
|
||||
}) => {
|
||||
const { productServiceCrud } = useFulfillmentBaseContext();
|
||||
|
||||
const onChange = (item: ProductServiceSchema) => {
|
||||
const excludeServiceIds = dealProduct.productServices.map(
|
||||
productService => productService.service.id
|
||||
);
|
||||
|
||||
modals.openContextModal({
|
||||
modal: "productServiceEditorModal",
|
||||
innerProps: {
|
||||
entity: item,
|
||||
onChange: values =>
|
||||
productServiceCrud.onUpdate(
|
||||
item.dealId,
|
||||
item.productId,
|
||||
item.serviceId,
|
||||
values
|
||||
),
|
||||
excludeServiceIds,
|
||||
quantity: dealProduct.quantity,
|
||||
isEditing: true,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const columns = useProductServicesTableColumns({
|
||||
data: dealProduct.productServices,
|
||||
quantity: dealProduct.quantity,
|
||||
onDelete: productServiceCrud.onDelete,
|
||||
onChange,
|
||||
});
|
||||
|
||||
// const [currentService, setCurrentService] = useState<
|
||||
// ProductServiceSchema | undefined
|
||||
// >();
|
||||
// const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
||||
|
||||
const onCreateClick = () => {
|
||||
const excludeServiceIds = dealProduct.productServices.map(
|
||||
productService => productService.service.id
|
||||
);
|
||||
|
||||
modals.openContextModal({
|
||||
modal: "productServiceEditorModal",
|
||||
innerProps: {
|
||||
onCreate: values =>
|
||||
productServiceCrud.onCreate({ ...dealProduct, ...values }),
|
||||
excludeServiceIds,
|
||||
quantity: dealProduct.quantity,
|
||||
isEditing: false,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
// const onEmployeeClick = (item: CardProductServiceSchema) => {
|
||||
// if (!onChange) return;
|
||||
// setCurrentService(item);
|
||||
// setEmployeesModalVisible(true);
|
||||
// };
|
||||
// const onEmployeeModalClose = () => {
|
||||
// setEmployeesModalVisible(false);
|
||||
// setCurrentService(undefined);
|
||||
// };
|
||||
// const getCurrentEmployees = (): UserSchema[] => {
|
||||
// if (!currentService) return [];
|
||||
// const item = items.find(
|
||||
// i => i.service.id === currentService.service.id
|
||||
// );
|
||||
// if (!item) return [];
|
||||
// return item.employees;
|
||||
// };
|
||||
// const onEmployeesChange = (items: UserSchema[]) => {
|
||||
// if (!currentService || !onChange) return;
|
||||
// onChange({
|
||||
// ...currentService,
|
||||
// employees: items,
|
||||
// });
|
||||
// };
|
||||
|
||||
const isEmptyTable = dealProduct.productServices.length === 0;
|
||||
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<BaseTable
|
||||
records={dealProduct.productServices}
|
||||
columns={columns}
|
||||
groups={undefined}
|
||||
idAccessor={"serviceId"}
|
||||
style={{
|
||||
height: isEmptyTable ? "8rem" : "auto",
|
||||
}}
|
||||
emptyState={
|
||||
<Group
|
||||
gap={"xs"}
|
||||
mt={isEmptyTable ? "xl" : 0}>
|
||||
<Text>Нет услуг</Text>
|
||||
<IconMoodSad />
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
justify={"flex-end"}
|
||||
gap={"xs"}
|
||||
pt={isEmptyTable ? 0 : "xs"}>
|
||||
<Button
|
||||
onClick={() => onKitAdd && onKitAdd()}
|
||||
variant={"default"}>
|
||||
Добавить набор услуг
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => onDuplicateServices && onDuplicateServices()}
|
||||
variant={"default"}>
|
||||
Продублировать услуги
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default ProductServicesTable;
|
||||
@ -0,0 +1,64 @@
|
||||
import { FC } from "react";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { ActionIcon, Flex, rem, Tooltip } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { DealProductSchema } from "@/lib/client";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
type Props = {
|
||||
dealProduct: DealProductSchema;
|
||||
};
|
||||
|
||||
const ProductViewActions: FC<Props> = ({ dealProduct }) => {
|
||||
const { dealProductsCrud, dealProductsList, productsCrud } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const onProductEditClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "productEditorModal",
|
||||
title: "Редактирование товара",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: values =>
|
||||
productsCrud.onUpdate(
|
||||
dealProduct.productId,
|
||||
values,
|
||||
dealProductsList.refetch
|
||||
),
|
||||
entity: dealProduct.product,
|
||||
isEditing: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
mt={"auto"}
|
||||
ml={"auto"}
|
||||
gap={rem(10)}>
|
||||
{/*<Tooltip*/}
|
||||
{/* onClick={onPrintBarcodeClick}*/}
|
||||
{/* label="Печать штрихкода">*/}
|
||||
{/* <ActionIcon variant={"default"}>*/}
|
||||
{/* <IconBarcode />*/}
|
||||
{/* </ActionIcon>*/}
|
||||
{/*</Tooltip>*/}
|
||||
<Tooltip
|
||||
onClick={onProductEditClick}
|
||||
label="Редактировать товар">
|
||||
<ActionIcon variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
onClick={() => dealProductsCrud.onDelete(dealProduct)}
|
||||
label="Удалить товар">
|
||||
<ActionIcon variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductViewActions;
|
||||
@ -0,0 +1,70 @@
|
||||
import { useMemo } from "react";
|
||||
import { DataTableColumn } from "mantine-datatable";
|
||||
import { ProductServiceSchema } from "@/lib/client";
|
||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
|
||||
type Props = {
|
||||
data: ProductServiceSchema[];
|
||||
quantity: number;
|
||||
onChange: (dealProductService: ProductServiceSchema) => void;
|
||||
onDelete: (dealProductService: ProductServiceSchema) => void;
|
||||
};
|
||||
|
||||
const useProductServicesTableColumns = ({ data, quantity, onChange, onDelete }: Props) => {
|
||||
const totalPrice = useMemo(
|
||||
() => data.reduce((acc, row) => acc + row.price * quantity, 0),
|
||||
[data, quantity]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
accessor: "actions",
|
||||
title: "Действия",
|
||||
textAlign: "center",
|
||||
width: "0%",
|
||||
render: dealProductService => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon
|
||||
onClick={() => onDelete(dealProductService)}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onChange(dealProductService)}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{/*<Tooltip label="Сотрудники">*/}
|
||||
{/* <ActionIcon*/}
|
||||
{/* onClick={() =>*/}
|
||||
{/* onEmployeeClick(row.original)*/}
|
||||
{/* }*/}
|
||||
{/* variant={"default"}>*/}
|
||||
{/* <IconUsersGroup />*/}
|
||||
{/* </ActionIcon>*/}
|
||||
{/*</Tooltip>*/}
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessor: "service.name",
|
||||
title: "Услуга",
|
||||
},
|
||||
{
|
||||
accessor: "price",
|
||||
title: "Цена",
|
||||
Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}₽</>,
|
||||
},
|
||||
] as DataTableColumn<ProductServiceSchema>[],
|
||||
[totalPrice]
|
||||
);
|
||||
};
|
||||
|
||||
export default useProductServicesTableColumns;
|
||||
@ -0,0 +1,60 @@
|
||||
import { FC } from "react";
|
||||
import { Button, Flex } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
const ProductsActions: FC = () => {
|
||||
const { deal, dealProductsList, productsCrud, dealProductsCrud } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const onCreateProductClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "productEditorModal",
|
||||
title: "Создание товара",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: productsCrud.onCreate,
|
||||
isEditing: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onCreateDealProductClick = () => {
|
||||
const productIdsToExclude = dealProductsList.dealProducts.map(
|
||||
product => product.product.id
|
||||
);
|
||||
|
||||
modals.openContextModal({
|
||||
modal: "dealProductEditorModal",
|
||||
title: "Добавление товара",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: values =>
|
||||
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
||||
productIdsToExclude,
|
||||
isEditing: false,
|
||||
clientId: 0, // TODO add clients
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
w={"100%"}
|
||||
direction={"column"}
|
||||
gap={"sm"}>
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={onCreateProductClick}>
|
||||
Создать товар
|
||||
</Button>
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={onCreateDealProductClick}>
|
||||
Добавить товар
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductsActions;
|
||||
@ -0,0 +1,51 @@
|
||||
import { FC } from "react";
|
||||
import { omit } from "lodash";
|
||||
import {
|
||||
ComboboxItem,
|
||||
ComboboxParsedItemGroup,
|
||||
OptionsFilter,
|
||||
} from "@mantine/core";
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ServiceSchema } from "@/lib/client";
|
||||
import useServicesList from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesList";
|
||||
import { ServiceType } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
type RestProps = {
|
||||
filterType?: ServiceType;
|
||||
};
|
||||
|
||||
type Props = Omit<ObjectSelectProps<ServiceSchema>, "data"> & RestProps;
|
||||
|
||||
const ServiceSelect: FC<Props> = props => {
|
||||
const { services } = useServicesList();
|
||||
const data = props.filterType
|
||||
? services.filter(service => service.serviceType === props.filterType)
|
||||
: services;
|
||||
|
||||
const restProps = omit(props, ["filterType"]);
|
||||
|
||||
const optionsFilter: OptionsFilter = ({ options, search }) => {
|
||||
return (options as ComboboxParsedItemGroup[]).map(option => {
|
||||
return {
|
||||
...option,
|
||||
items: option.items.filter((item: ComboboxItem) =>
|
||||
item.label.toLowerCase().includes(search.toLowerCase())
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
{...restProps}
|
||||
data={data}
|
||||
searchable
|
||||
groupBy={service => service.category.name}
|
||||
filter={optionsFilter}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceSelect;
|
||||
@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
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/dealModules/dealEditorTabs/FulfillmentBaseTab/hooks/cruds/useDealServiceCrud";
|
||||
import useProductServiceCrud, {
|
||||
DealProductServicesCrud,
|
||||
} from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/hooks/cruds/useProductServiceCrud";
|
||||
import useDealServicesList, {
|
||||
DealServicesList,
|
||||
} from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/hooks/lists/useDealServicesList";
|
||||
import useDealProductCrud, {
|
||||
DealProductsCrud,
|
||||
} from "../hooks/cruds/useDealProductCrud";
|
||||
import { ProductsCrud, useProductsCrud } from "../hooks/cruds/useProductsCrud";
|
||||
import useDealProductsList, {
|
||||
DealProductsList,
|
||||
} from "../hooks/lists/useDealProductsList";
|
||||
|
||||
type FulfillmentBaseContextState = {
|
||||
deal: DealSchema;
|
||||
productsCrud: ProductsCrud;
|
||||
dealProductsList: DealProductsList;
|
||||
dealProductsCrud: DealProductsCrud;
|
||||
dealServicesList: DealServicesList;
|
||||
dealServicesCrud: DealServicesCrud;
|
||||
productServiceCrud: DealProductServicesCrud;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
deal: DealSchema;
|
||||
};
|
||||
|
||||
const useFulfillmentBaseContextState = ({
|
||||
deal,
|
||||
}: Props): FulfillmentBaseContextState => {
|
||||
const productQueryKey = getProductsQueryKey();
|
||||
const productsCrud = useProductsCrud({ queryKey: productQueryKey });
|
||||
|
||||
const dealProductsList = useDealProductsList({ dealId: deal.id });
|
||||
const dealProductsCrud = useDealProductCrud({ dealId: deal.id });
|
||||
|
||||
const dealServicesList = useDealServicesList({ dealId: deal.id });
|
||||
const dealServicesCrud = useDealServicesCrud({ dealId: deal.id });
|
||||
|
||||
const productServiceCrud = useProductServiceCrud({
|
||||
refetchDealProducts: dealProductsList.refetch,
|
||||
});
|
||||
|
||||
return {
|
||||
deal,
|
||||
productsCrud,
|
||||
dealProductsList,
|
||||
dealProductsCrud,
|
||||
dealServicesList,
|
||||
dealServicesCrud,
|
||||
productServiceCrud,
|
||||
};
|
||||
};
|
||||
|
||||
export const [FulfillmentBaseContextProvider, useFulfillmentBaseContext] =
|
||||
makeContext<FulfillmentBaseContextState, Props>(
|
||||
useFulfillmentBaseContextState,
|
||||
"FulfillmentBase"
|
||||
);
|
||||
@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
||||
import {
|
||||
CreateDealProductSchema,
|
||||
DealProductSchema,
|
||||
UpdateDealProductSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createDealProductMutation,
|
||||
deleteDealProductMutation,
|
||||
getDealProductsQueryKey,
|
||||
updateDealProductMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealProductsCrud = {
|
||||
onCreate: (data: CreateDealProductSchema) => void;
|
||||
onUpdate: (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
data: UpdateDealProductSchema
|
||||
) => void;
|
||||
onDelete: (data: DealProductSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
|
||||
const useDealProductCrud = ({ dealId }: Props): DealProductsCrud => {
|
||||
const queryKey = getDealProductsQueryKey({ path: { dealId } });
|
||||
const key = "getDealProducts";
|
||||
const { queryClient, onError, onSettled } = getCommonQueryClient({
|
||||
queryKey,
|
||||
key,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createDealProductMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...updateDealProductMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({
|
||||
body: { entity: update },
|
||||
path: { dealId, productId },
|
||||
}) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealProductSchema[] }) => {
|
||||
const updated: DealProductSchema[] = old.items.map(
|
||||
(entity: DealProductSchema) =>
|
||||
entity.dealId === dealId &&
|
||||
entity.productId === productId
|
||||
? { ...entity, ...update }
|
||||
: entity
|
||||
);
|
||||
|
||||
return {
|
||||
...old,
|
||||
items: updated,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteDealProductMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({ path: { dealId, productId } }) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealProductSchema[] }) => {
|
||||
const filtered = old.items.filter(
|
||||
e => e.dealId !== dealId && e.productId !== productId
|
||||
);
|
||||
return {
|
||||
...old,
|
||||
items: filtered,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const onCreate = (entity: CreateDealProductSchema) => {
|
||||
createMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdate = (
|
||||
dealId: number,
|
||||
productId: number,
|
||||
entity: UpdateDealProductSchema
|
||||
) => {
|
||||
updateMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: {
|
||||
dealId,
|
||||
productId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (entity: DealProductSchema, onSuccess?: () => void) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление товара из сделки",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить "{entity.product.name}" из
|
||||
сделки?
|
||||
</Text>
|
||||
),
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteMutation.mutate({ path: entity }, { onSuccess });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealProductCrud;
|
||||
@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
||||
import {
|
||||
CreateDealServiceSchema,
|
||||
DealServiceSchema,
|
||||
UpdateDealServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createDealServiceMutation,
|
||||
deleteDealServiceMutation,
|
||||
getDealServicesQueryKey,
|
||||
updateDealServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealServicesCrud = {
|
||||
onCreate: (data: CreateDealServiceSchema) => void;
|
||||
onUpdate: (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
data: UpdateDealServiceSchema
|
||||
) => void;
|
||||
onDelete: (data: DealServiceSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
|
||||
const useDealServiceCrud = ({ dealId }: Props): DealServicesCrud => {
|
||||
const queryKey = getDealServicesQueryKey({ path: { dealId } });
|
||||
const key = "getDealServices";
|
||||
const { queryClient, onError, onSettled } = getCommonQueryClient({
|
||||
queryKey,
|
||||
key,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createDealServiceMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...updateDealServiceMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({
|
||||
body: { entity: update },
|
||||
path: { dealId, serviceId },
|
||||
}) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealServiceSchema[] }) => {
|
||||
const updated: DealServiceSchema[] = old.items.map(
|
||||
(entity: DealServiceSchema) =>
|
||||
entity.dealId === dealId &&
|
||||
entity.serviceId === serviceId
|
||||
? { ...entity, ...update }
|
||||
: entity
|
||||
);
|
||||
|
||||
return {
|
||||
...old,
|
||||
items: updated,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteDealServiceMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({ path: { dealId, serviceId } }) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealServiceSchema[] }) => {
|
||||
const filtered = old.items.filter(
|
||||
e => e.dealId !== dealId && e.serviceId !== serviceId
|
||||
);
|
||||
return {
|
||||
...old,
|
||||
items: filtered,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const onCreate = (entity: CreateDealServiceSchema) => {
|
||||
createMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdate = (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
entity: UpdateDealServiceSchema
|
||||
) => {
|
||||
updateMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: {
|
||||
dealId,
|
||||
serviceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (entity: DealServiceSchema, onSuccess?: () => void) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление услуги из сделки",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить "{entity.service.name}" из
|
||||
сделки?
|
||||
</Text>
|
||||
),
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteMutation.mutate({ path: entity }, { onSuccess });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealServiceCrud;
|
||||
@ -0,0 +1,102 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import {
|
||||
CreateProductServiceSchema,
|
||||
ProductServiceSchema,
|
||||
HttpValidationError,
|
||||
UpdateProductServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createDealProductServiceMutation,
|
||||
deleteDealProductServiceMutation,
|
||||
updateDealProductServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
export type DealProductServicesCrud = {
|
||||
onCreate: (data: CreateProductServiceSchema) => void;
|
||||
onUpdate: (
|
||||
dealId: number,
|
||||
productId: number,
|
||||
serviceId: number,
|
||||
data: UpdateProductServiceSchema
|
||||
) => void;
|
||||
onDelete: (data: ProductServiceSchema) => void;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
refetchDealProducts: () => void;
|
||||
};
|
||||
|
||||
const useProductServiceCrud = ({
|
||||
refetchDealProducts,
|
||||
}: Props): DealProductServicesCrud => {
|
||||
const onError = (error: AxiosError<HttpValidationError>, _: any) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createDealProductServiceMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...updateDealProductServiceMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteDealProductServiceMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreate = (entity: CreateProductServiceSchema) => {
|
||||
createMutation.mutate(
|
||||
{
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
},
|
||||
{ onSuccess: refetchDealProducts }
|
||||
);
|
||||
};
|
||||
|
||||
const onUpdate = (
|
||||
dealId: number,
|
||||
productId: number,
|
||||
serviceId: number,
|
||||
entity: UpdateProductServiceSchema
|
||||
) => {
|
||||
updateMutation.mutate(
|
||||
{
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: {
|
||||
dealId,
|
||||
productId,
|
||||
serviceId,
|
||||
},
|
||||
},
|
||||
{ onSuccess: refetchDealProducts }
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = (entity: ProductServiceSchema) => {
|
||||
deleteMutation.mutate(
|
||||
{ path: entity },
|
||||
{ onSuccess: refetchDealProducts }
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProductServiceCrud;
|
||||
@ -0,0 +1,49 @@
|
||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||
import {
|
||||
CreateProductSchema,
|
||||
ProductSchema,
|
||||
UpdateProductSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createProductMutation,
|
||||
deleteProductMutation,
|
||||
updateProductMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type UseProductsProps = {
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
export type ProductsCrud = {
|
||||
onCreate: (product: CreateProductSchema) => void;
|
||||
onUpdate: (
|
||||
productId: number,
|
||||
product: UpdateProductSchema,
|
||||
onSuccess?: () => void
|
||||
) => void;
|
||||
onDelete: (product: ProductSchema) => void;
|
||||
};
|
||||
|
||||
export const useProductsCrud = ({
|
||||
queryKey,
|
||||
}: UseProductsProps): ProductsCrud => {
|
||||
return useCrudOperations<
|
||||
ProductSchema,
|
||||
UpdateProductSchema,
|
||||
CreateProductSchema
|
||||
>({
|
||||
key: "getProducts",
|
||||
queryKey,
|
||||
mutations: {
|
||||
create: createProductMutation(),
|
||||
update: updateProductMutation(),
|
||||
delete: deleteProductMutation(),
|
||||
},
|
||||
getUpdateEntity: (old, update) =>
|
||||
({
|
||||
...old,
|
||||
...update,
|
||||
}) as ProductSchema,
|
||||
getDeleteConfirmTitle: () => "Удаление товара",
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||
import {
|
||||
CreateServiceSchema,
|
||||
ServiceSchema,
|
||||
UpdateServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createServiceMutation,
|
||||
deleteServiceMutation,
|
||||
updateServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type UseServicesProps = {
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
export type ServicesCrud = {
|
||||
onCreate: (service: CreateServiceSchema) => void;
|
||||
onUpdate: (
|
||||
serviceId: number,
|
||||
service: UpdateServiceSchema,
|
||||
onSuccess?: () => void
|
||||
) => void;
|
||||
onDelete: (service: ServiceSchema) => void;
|
||||
};
|
||||
|
||||
export const useServicesCrud = ({
|
||||
queryKey,
|
||||
}: UseServicesProps): ServicesCrud => {
|
||||
return useCrudOperations<
|
||||
ServiceSchema,
|
||||
UpdateServiceSchema,
|
||||
CreateServiceSchema
|
||||
>({
|
||||
key: "getServices",
|
||||
queryKey,
|
||||
mutations: {
|
||||
create: createServiceMutation(),
|
||||
update: updateServiceMutation(),
|
||||
delete: deleteServiceMutation(),
|
||||
},
|
||||
getUpdateEntity: (old, update) =>
|
||||
({
|
||||
...old,
|
||||
...update,
|
||||
}) as ServiceSchema,
|
||||
getDeleteConfirmTitle: () => "Удаление услуги",
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { DealProductSchema } from "@/lib/client";
|
||||
import {
|
||||
getDealProductsOptions,
|
||||
getDealProductsQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealProductsList = {
|
||||
dealProducts: DealProductSchema[];
|
||||
setDealProducts: (dealProducts: DealProductSchema[]) => void;
|
||||
refetch: () => void;
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
const useDealProductsList = ({ dealId }: Props): DealProductsList => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = {
|
||||
path: { dealId },
|
||||
};
|
||||
const { data, refetch } = useQuery({
|
||||
...getDealProductsOptions(options),
|
||||
enabled: !!dealId,
|
||||
});
|
||||
|
||||
const queryKey = getDealProductsQueryKey(options);
|
||||
|
||||
const setDealProducts = (dealProducts: DealProductSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealProductSchema[] }) => ({
|
||||
...old,
|
||||
items: dealProducts,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
dealProducts: data?.items ?? [],
|
||||
setDealProducts,
|
||||
refetch,
|
||||
queryKey,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealProductsList;
|
||||
@ -0,0 +1,49 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { DealServiceSchema } from "@/lib/client";
|
||||
import {
|
||||
getDealServicesOptions,
|
||||
getDealServicesQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealServicesList = {
|
||||
dealServices: DealServiceSchema[];
|
||||
setDealServices: (dealServices: DealServiceSchema[]) => void;
|
||||
refetch: () => void;
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
const useDealServicesList = ({ dealId }: Props): DealServicesList => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = {
|
||||
path: { dealId },
|
||||
};
|
||||
const { data, refetch } = useQuery({
|
||||
...getDealServicesOptions(options),
|
||||
enabled: !!dealId,
|
||||
});
|
||||
|
||||
const queryKey = getDealServicesQueryKey(options);
|
||||
|
||||
const setDealServices = (dealServices: DealServiceSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealServiceSchema[] }) => ({
|
||||
...old,
|
||||
items: dealServices,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
dealServices: data?.items ?? [],
|
||||
setDealServices,
|
||||
refetch,
|
||||
queryKey,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealServicesList;
|
||||
@ -0,0 +1,42 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import {
|
||||
getProductsOptions,
|
||||
getProductsQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
searchInput: string;
|
||||
page?: number;
|
||||
itemsPerPage?: number;
|
||||
};
|
||||
|
||||
const useProductsList = ({ searchInput, page, itemsPerPage }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = {
|
||||
query: { searchInput, page, itemsPerPage },
|
||||
};
|
||||
const { data, refetch, isLoading } = useQuery(getProductsOptions(options));
|
||||
|
||||
const queryKey = getProductsQueryKey(options);
|
||||
|
||||
const setProducts = (products: ProductSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ProductSchema[] }) => ({
|
||||
...old,
|
||||
items: products,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
products: data?.items ?? [],
|
||||
setProducts,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProductsList;
|
||||
@ -0,0 +1,33 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ServicesKitSchema } from "@/lib/client";
|
||||
import {
|
||||
getServicesKitsOptions,
|
||||
getServicesKitsQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
const useServicesKitsList = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data, refetch, isLoading } = useQuery(getServicesKitsOptions());
|
||||
|
||||
const queryKey = getServicesKitsQueryKey();
|
||||
|
||||
const setServicesKits = (servicesKits: ServicesKitSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ServicesKitSchema[] }) => ({
|
||||
...old,
|
||||
items: servicesKits,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
servicesKits: data?.items ?? [],
|
||||
setServicesKits,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServicesKitsList;
|
||||
@ -0,0 +1,34 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ServiceSchema } from "@/lib/client";
|
||||
import {
|
||||
getServicesOptions,
|
||||
getServicesQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
|
||||
const useServicesList = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data, refetch, isLoading } = useQuery(getServicesOptions());
|
||||
|
||||
const queryKey = getServicesQueryKey();
|
||||
|
||||
const setServices = (services: ServiceSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ServiceSchema[] }) => ({
|
||||
...old,
|
||||
items: services,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
services: data?.items ?? [],
|
||||
setServices,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServicesList;
|
||||
@ -0,0 +1,148 @@
|
||||
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
|
||||
import { CardService, CardServiceSchema, CardProductSchema } from "../../../../../client";
|
||||
import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
const useCardState = () => {
|
||||
|
||||
const { selectedCard, setSelectedCard } = useCardPageContext();
|
||||
const recalculate = async () => {
|
||||
return CardService.recalculateCardPrice({
|
||||
requestBody: {
|
||||
cardId: selectedCard?.id || -1,
|
||||
},
|
||||
});
|
||||
};
|
||||
const refetchCard = async () => {
|
||||
if (!selectedCard) return;
|
||||
|
||||
return CardService.getCardById({ cardId: selectedCard.id }).then(
|
||||
async card => {
|
||||
setSelectedCard(card);
|
||||
},
|
||||
);
|
||||
};
|
||||
const refetch = async () => {
|
||||
if (!selectedCard) return;
|
||||
await refetchCard();
|
||||
const { ok, message } = await recalculate();
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
|
||||
await refetchCard();
|
||||
};
|
||||
return {
|
||||
card: selectedCard,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
const useCardServicesState = (): CRUDTableProps<CardServiceSchema> => {
|
||||
const { card, refetch } = useCardState();
|
||||
const refetchAndRecalculate = async () => {
|
||||
await refetch();
|
||||
};
|
||||
const onCreate = (item: CardServiceSchema) => {
|
||||
if (!card) return;
|
||||
CardService.addCardService({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
serviceId: item.service.id,
|
||||
quantity: item.quantity,
|
||||
price: item.price,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onDelete = (item: CardServiceSchema) => {
|
||||
if (!card) return;
|
||||
CardService.deleteCardService({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
serviceId: item.service.id,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onChange = (item: CardServiceSchema) => {
|
||||
if (!card) return;
|
||||
CardService.updateCardService({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
service: item,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
return {
|
||||
items: card?.services || [],
|
||||
onCreate,
|
||||
onDelete,
|
||||
onChange,
|
||||
};
|
||||
};
|
||||
|
||||
const useDealProductsState = (): CRUDTableProps<CardProductSchema> => {
|
||||
const { card, refetch } = useCardState();
|
||||
const refetchAndRecalculate = async () => {
|
||||
await refetch();
|
||||
};
|
||||
const onCreate = (item: CardProductSchema) => {
|
||||
if (!card) return;
|
||||
CardService.addCardProduct({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
product: item,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onDelete = (item: CardProductSchema) => {
|
||||
if (!card) return;
|
||||
CardService.deleteCardProduct({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
productId: item.product.id,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onChange = (item: CardProductSchema) => {
|
||||
if (!card) return;
|
||||
CardService.updateCardProduct({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
product: item,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
return {
|
||||
items: card?.products || [],
|
||||
onCreate,
|
||||
onDelete,
|
||||
onChange,
|
||||
};
|
||||
};
|
||||
const useCardProductAndServiceTabState = () => {
|
||||
const cardState = useCardState();
|
||||
const cardProductsState = useDealProductsState();
|
||||
const cardServicesState = useCardServicesState();
|
||||
return {
|
||||
cardState,
|
||||
cardProductsState,
|
||||
cardServicesState,
|
||||
};
|
||||
};
|
||||
export default useCardProductAndServiceTabState;
|
||||
@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { NumberInput, Textarea } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateDealProductSchema,
|
||||
DealProductSchema,
|
||||
UpdateDealProductSchema,
|
||||
} from "@/lib/client";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ProductSelect from "../../components/ProductSelect/ProductSelect";
|
||||
|
||||
type RestProps = {
|
||||
clientId: number;
|
||||
productIdsToExclude?: number[];
|
||||
};
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateDealProductSchema,
|
||||
UpdateDealProductSchema,
|
||||
DealProductSchema
|
||||
> &
|
||||
RestProps;
|
||||
|
||||
const DealProductEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<Partial<DealProductSchema>>({
|
||||
initialValues: innerProps.isEditing
|
||||
? innerProps.entity
|
||||
: {
|
||||
quantity: 1,
|
||||
comment: "",
|
||||
},
|
||||
validate: {
|
||||
product: product => !product && "Необходимо выбрать товар",
|
||||
quantity: quantity =>
|
||||
(!quantity || quantity === 0) &&
|
||||
"Количество должно быть больше 0",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => {
|
||||
context.closeContextModal(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={onClose}>
|
||||
<ProductSelect
|
||||
placeholder={"Выберите товар"}
|
||||
label={"Товар"}
|
||||
clientId={innerProps.clientId}
|
||||
disabled={innerProps.isEditing}
|
||||
filterBy={item =>
|
||||
!(innerProps.productIdsToExclude || []).includes(item.id)
|
||||
}
|
||||
{...form.getInputProps("product")}
|
||||
onChange={product => {
|
||||
form.setFieldValue("product", product);
|
||||
form.setFieldValue("productId", product.id);
|
||||
}}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите количество"}
|
||||
label={"Количество"}
|
||||
min={1}
|
||||
{...form.getInputProps("quantity")}
|
||||
/>
|
||||
<Textarea
|
||||
placeholder={"Введите комментарий"}
|
||||
label={"Комментарий"}
|
||||
{...form.getInputProps("comment")}
|
||||
/>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealProductEditorModal;
|
||||
@ -0,0 +1,114 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ComboboxItem,
|
||||
ComboboxItemGroup,
|
||||
NumberInput,
|
||||
OptionsFilter,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateDealServiceSchema,
|
||||
DealServiceSchema,
|
||||
UpdateDealServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import { ServiceType } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/types/service";
|
||||
import ServiceWithPriceInput from "./components/ServiceWithPriceInput";
|
||||
|
||||
type RestProps = {
|
||||
serviceIdsToExclude?: number[];
|
||||
};
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateDealServiceSchema,
|
||||
UpdateDealServiceSchema,
|
||||
DealServiceSchema
|
||||
> &
|
||||
RestProps;
|
||||
|
||||
const DealServiceEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<Partial<DealServiceSchema>>({
|
||||
initialValues: innerProps.isEditing
|
||||
? innerProps.entity
|
||||
: {
|
||||
service: undefined,
|
||||
serviceId: undefined,
|
||||
quantity: 1,
|
||||
isFixedPrice: false,
|
||||
},
|
||||
validate: {
|
||||
service: service => !service && "Необходимо выбрать услугу",
|
||||
quantity: quantity =>
|
||||
(!quantity || quantity === 0) &&
|
||||
"Количество должно быть больше 0",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
const serviceOptionsFilter = ({
|
||||
options,
|
||||
}: {
|
||||
options: ComboboxItemGroup[];
|
||||
}) => {
|
||||
if (!innerProps.serviceIdsToExclude) return options;
|
||||
const productServiceIds = innerProps.serviceIdsToExclude;
|
||||
return (options as ComboboxItemGroup[]).map(({ items, group }) => {
|
||||
return {
|
||||
group,
|
||||
items: items.filter(
|
||||
item =>
|
||||
!productServiceIds.includes(
|
||||
Number((item as ComboboxItem).value)
|
||||
)
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={onClose}>
|
||||
<ServiceWithPriceInput
|
||||
serviceProps={{
|
||||
...form.getInputProps("service"),
|
||||
onChange: service => {
|
||||
form.setFieldValue("service", service);
|
||||
form.setFieldValue("serviceId", service.id);
|
||||
},
|
||||
label: "Услуга",
|
||||
placeholder: "Выберите услугу",
|
||||
disabled: innerProps.isEditing,
|
||||
filter: serviceOptionsFilter as OptionsFilter,
|
||||
}}
|
||||
priceProps={{
|
||||
...form.getInputProps("price"),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
}}
|
||||
quantity={form.values.quantity || 1}
|
||||
filterType={ServiceType.DEAL_SERVICE}
|
||||
lockOnEdit={innerProps.isEditing}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите количество"}
|
||||
label={"Количество"}
|
||||
min={1}
|
||||
{...form.getInputProps("quantity")}
|
||||
/>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealServiceEditorModal;
|
||||
@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
NumberInput,
|
||||
NumberInputProps,
|
||||
Stack,
|
||||
StackProps,
|
||||
} from "@mantine/core";
|
||||
import { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ServiceSchema } from "@/lib/client";
|
||||
import ServiceSelect from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ServiceSelect/ServiceSelect";
|
||||
import { ServiceType } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, "data">;
|
||||
type PriceProps = NumberInputProps;
|
||||
|
||||
type Props = {
|
||||
serviceProps: ServiceProps;
|
||||
priceProps: PriceProps;
|
||||
quantity: number;
|
||||
containerProps?: StackProps;
|
||||
filterType?: ServiceType;
|
||||
lockOnEdit?: boolean;
|
||||
};
|
||||
|
||||
const ServiceWithPriceInput: FC<Props> = ({
|
||||
serviceProps,
|
||||
priceProps,
|
||||
quantity,
|
||||
containerProps,
|
||||
filterType = ServiceType.PRODUCT_SERVICE,
|
||||
lockOnEdit = true,
|
||||
}) => {
|
||||
const [price, setPrice] = useState<number | undefined>(
|
||||
typeof priceProps.value === "number" ? priceProps.value : undefined
|
||||
);
|
||||
const [service, setService] = useState<ServiceSchema | undefined>(
|
||||
serviceProps.value
|
||||
);
|
||||
const isFirstRender = useRef(true);
|
||||
|
||||
const getPriceBasedOnQuantity = (): number | null => {
|
||||
if (!service || !service.priceRanges.length) return null;
|
||||
const range =
|
||||
service.priceRanges.find(
|
||||
priceRange =>
|
||||
quantity >= priceRange.fromQuantity &&
|
||||
quantity <= priceRange.toQuantity
|
||||
) || service.priceRanges[0];
|
||||
|
||||
return range.price;
|
||||
};
|
||||
|
||||
const setPriceBasedOnService = () => {
|
||||
if (!service) return;
|
||||
const rangePrice = getPriceBasedOnQuantity();
|
||||
setPrice(rangePrice || service.price);
|
||||
};
|
||||
|
||||
const onPriceManualChange = (value: number | string) => {
|
||||
if (typeof value !== "number") return;
|
||||
setPrice(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current && lockOnEdit) return;
|
||||
const price = getPriceBasedOnQuantity();
|
||||
if (price) setPrice(price);
|
||||
}, [quantity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current && lockOnEdit) return;
|
||||
if (!priceProps.onChange || price === undefined) return;
|
||||
priceProps.onChange(price);
|
||||
}, [price]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!serviceProps.onChange ||
|
||||
!service ||
|
||||
(price && isFirstRender.current && lockOnEdit)
|
||||
)
|
||||
return;
|
||||
setPriceBasedOnService();
|
||||
serviceProps.onChange(service);
|
||||
}, [service]);
|
||||
|
||||
useEffect(() => {
|
||||
isFirstRender.current = false;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={"xs"}
|
||||
{...containerProps}>
|
||||
<ServiceSelect
|
||||
{...serviceProps}
|
||||
value={service}
|
||||
onChange={setService}
|
||||
filterType={filterType}
|
||||
disabled={lockOnEdit}
|
||||
/>
|
||||
<NumberInput
|
||||
{...priceProps}
|
||||
onChange={onPriceManualChange}
|
||||
defaultValue={priceProps.value}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceWithPriceInput;
|
||||
@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button, Flex, Text } from "@mantine/core";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import ObjectMultiSelect from "@/components/selects/ObjectMultiSelect/ObjectMultiSelect";
|
||||
import { DealProductSchema } from "@/lib/client";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
type Props = {
|
||||
dealProducts: DealProductSchema[];
|
||||
sourceDealProduct: DealProductSchema;
|
||||
duplicateServices: (
|
||||
sourceDealProduct: DealProductSchema,
|
||||
targetDealProducts: DealProductSchema[]
|
||||
) => void;
|
||||
};
|
||||
|
||||
const DuplicateServicesModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const [selectedDealProducts, setSelectedDealProducts] = useState<
|
||||
DealProductSchema[]
|
||||
>([]);
|
||||
|
||||
const onDealProductSelect = () => {
|
||||
if (!selectedDealProducts) {
|
||||
notifications.error({
|
||||
message:
|
||||
"Выберите товары на которые необходимо продублировать услуги",
|
||||
});
|
||||
return;
|
||||
}
|
||||
innerProps.duplicateServices(
|
||||
innerProps.sourceDealProduct,
|
||||
selectedDealProducts
|
||||
);
|
||||
context.closeContextModal(id);
|
||||
};
|
||||
|
||||
const onDuplicateAllClick = () => {
|
||||
innerProps.duplicateServices(
|
||||
innerProps.sourceDealProduct,
|
||||
innerProps.dealProducts.filter(
|
||||
item => item !== innerProps.sourceDealProduct
|
||||
)
|
||||
);
|
||||
context.closeContextModal(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"xs"}>
|
||||
<ObjectMultiSelect
|
||||
w={"100%"}
|
||||
label={"Товары"}
|
||||
placeholder={
|
||||
"Выберите товары на которые нужно продублировать услуги"
|
||||
}
|
||||
onChange={setSelectedDealProducts}
|
||||
value={selectedDealProducts}
|
||||
data={innerProps.dealProducts}
|
||||
getLabelFn={item => item.product.name}
|
||||
getValueFn={item => item.product.id.toString()}
|
||||
filterBy={item => item !== innerProps.sourceDealProduct}
|
||||
/>
|
||||
<Flex
|
||||
gap={"xs"}
|
||||
justify={"flex-end"}>
|
||||
<Button
|
||||
variant={"subtle"}
|
||||
onClick={() => context.closeContextModal(id)}>
|
||||
<Text>Отменить</Text>
|
||||
</Button>
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={onDuplicateAllClick}>
|
||||
<Text>Продублировать на все товары</Text>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onDealProductSelect}
|
||||
variant={"default"}>
|
||||
<Text>Продублировать</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default DuplicateServicesModal;
|
||||
@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import { Fieldset, Flex, rem, TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateProductSchema,
|
||||
ProductSchema,
|
||||
UpdateProductSchema,
|
||||
} from "@/lib/client";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ProductImageDropzone from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductImageDropzone/ProductImageDropzone";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateProductSchema,
|
||||
UpdateProductSchema,
|
||||
ProductSchema
|
||||
>;
|
||||
|
||||
const ProductEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = "entity" in innerProps;
|
||||
|
||||
const initialValues: Partial<ProductSchema> = isEditing
|
||||
? innerProps.entity!
|
||||
: {
|
||||
name: "",
|
||||
article: "",
|
||||
factoryArticle: "",
|
||||
brand: "",
|
||||
composition: "",
|
||||
color: "",
|
||||
size: "",
|
||||
additionalInfo: "",
|
||||
};
|
||||
|
||||
const form = useForm<Partial<ProductSchema>>({
|
||||
initialValues,
|
||||
validate: {
|
||||
name: name =>
|
||||
!name || name.trim() !== ""
|
||||
? null
|
||||
: "Необходимо ввести название товара",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={onClose}>
|
||||
<Flex
|
||||
gap={rem(10)}
|
||||
direction={"column"}>
|
||||
<Fieldset legend={"Основные характеристики"}>
|
||||
<TextInput
|
||||
placeholder={"Введите название товара"}
|
||||
label={"Название товара"}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите артикул"}
|
||||
label={"Артикул"}
|
||||
{...form.getInputProps("article")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите складской артикул"}
|
||||
label={"Складской артикул"}
|
||||
{...form.getInputProps("factoryArticle")}
|
||||
/>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Дополнительные характеристики"}>
|
||||
<TextInput
|
||||
placeholder={"Введите бренд"}
|
||||
label={"Бренд"}
|
||||
{...form.getInputProps("brand")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите состав"}
|
||||
label={"Состав"}
|
||||
{...form.getInputProps("composition")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите цвет"}
|
||||
label={"Цвет"}
|
||||
{...form.getInputProps("color")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите размер"}
|
||||
label={"Размер"}
|
||||
{...form.getInputProps("size")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите доп. информацию"}
|
||||
label={"Доп. информация"}
|
||||
{...form.getInputProps("additionalInfo")}
|
||||
/>
|
||||
</Fieldset>
|
||||
{isEditing && (
|
||||
<ProductImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"imageUrl"
|
||||
) as BaseFormInputProps<string>
|
||||
}
|
||||
productId={innerProps.entity.id}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
export default ProductEditorModal;
|
||||
@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { isNil, isNumber } from "lodash";
|
||||
import { Checkbox, Flex } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateProductServiceSchema,
|
||||
ProductServiceSchema,
|
||||
UpdateProductServiceSchema,
|
||||
} from "@/lib/client/index.js";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ServiceWithPriceInput from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/DealServiceEditorModal/components/ServiceWithPriceInput";
|
||||
import { ServiceType } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
type RestProps = {
|
||||
quantity: number;
|
||||
excludeServiceIds: number[];
|
||||
};
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateProductServiceSchema,
|
||||
UpdateProductServiceSchema,
|
||||
ProductServiceSchema
|
||||
> &
|
||||
RestProps;
|
||||
|
||||
const ProductServiceEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const initialValues: Partial<ProductServiceSchema> = innerProps.isEditing
|
||||
? innerProps.entity
|
||||
: {
|
||||
service: undefined,
|
||||
serviceId: undefined,
|
||||
price: undefined,
|
||||
// employees: [],
|
||||
isFixedPrice: false,
|
||||
};
|
||||
|
||||
const form = useForm<Partial<ProductServiceSchema>>({
|
||||
initialValues,
|
||||
validate: {
|
||||
service: service =>
|
||||
(isNil(service) || service.id < 0) && "Укажите услугу",
|
||||
price: price => (!isNumber(price) || price < 0) && "Укажите цену",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
onClose={onClose}
|
||||
closeOnSubmit>
|
||||
<Flex
|
||||
w={"100%"}
|
||||
direction={"column"}
|
||||
gap={"xs"}>
|
||||
<ServiceWithPriceInput
|
||||
serviceProps={{
|
||||
...form.getInputProps("service"),
|
||||
onChange: value => {
|
||||
form.setFieldValue("service", value);
|
||||
form.setFieldValue("serviceId", value.id);
|
||||
},
|
||||
label: "Услуга",
|
||||
placeholder: "Выберите услугу",
|
||||
disabled: innerProps.isEditing,
|
||||
filterBy: item =>
|
||||
!innerProps.excludeServiceIds.includes(item.id) ||
|
||||
innerProps.isEditing,
|
||||
}}
|
||||
priceProps={{
|
||||
...form.getInputProps("price"),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
}}
|
||||
filterType={ServiceType.PRODUCT_SERVICE}
|
||||
lockOnEdit={innerProps.isEditing}
|
||||
quantity={innerProps.quantity}
|
||||
/>
|
||||
<Checkbox
|
||||
{...form.getInputProps("isFixedPrice", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
label={"Зафиксировать цену"}
|
||||
placeholder={"Зафиксировать цену"}
|
||||
/>
|
||||
</Flex>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
export default ProductServiceEditorModal;
|
||||
@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { Flex } from "@mantine/core";
|
||||
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/dealModules/dealEditorTabs/FulfillmentBaseTab/modals/ServicesKitSelectModal/components/ServicesKitSelect";
|
||||
|
||||
type Props = {
|
||||
onSelect: (kit: ServicesKitSchema) => void;
|
||||
serviceType: number;
|
||||
};
|
||||
|
||||
type ServicesKitForm = {
|
||||
servicesKit: ServicesKitSchema;
|
||||
};
|
||||
|
||||
const ServicesKitSelectModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<ServicesKitForm>({
|
||||
validate: {
|
||||
servicesKit: servicesKit => !servicesKit && "Выберите сервис",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
const onSubmit = (values: ServicesKitForm) => {
|
||||
innerProps.onSelect(values.servicesKit);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<Flex
|
||||
gap={"xs"}
|
||||
direction={"column"}>
|
||||
<Flex>
|
||||
<ServicesKitSelect
|
||||
w={"100%"}
|
||||
{...form.getInputProps("servicesKit")}
|
||||
filterBy={item =>
|
||||
item.serviceType === innerProps.serviceType
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<BaseFormModalActions onClose={onClose} />
|
||||
</Flex>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesKitSelectModal;
|
||||
@ -0,0 +1,23 @@
|
||||
import { FC } from "react";
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ServicesKitSchema } from "@/lib/client";
|
||||
import useServicesKitsList from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesKitsList";
|
||||
|
||||
type Props = Omit<ObjectSelectProps<ServicesKitSchema>, "data">;
|
||||
|
||||
const ServicesKitSelect: FC<Props> = props => {
|
||||
const { servicesKits } = useServicesKitsList();
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
label={"Набор услуг"}
|
||||
placeholder={"Выберите набор услуг"}
|
||||
data={servicesKits}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesKitSelect;
|
||||
@ -0,0 +1,4 @@
|
||||
export enum ServiceType {
|
||||
DEAL_SERVICE,
|
||||
PRODUCT_SERVICE,
|
||||
}
|
||||
28
src/modules/modules.tsx
Normal file
28
src/modules/modules.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import {
|
||||
IconBox,
|
||||
} from "@tabler/icons-react";
|
||||
import ModulesType from "./types";
|
||||
import connectModules from "./connectModules";
|
||||
|
||||
export enum ModuleNames {
|
||||
FULFILLMENT_BASE = "fulfillment_base",
|
||||
}
|
||||
|
||||
const modules: ModulesType = {
|
||||
[ModuleNames.FULFILLMENT_BASE]: {
|
||||
renderInfo: {
|
||||
label: "Фулфиллмент",
|
||||
key: "fulfillment_base",
|
||||
icon: <IconBox />,
|
||||
},
|
||||
modelData: {
|
||||
id: 1,
|
||||
key: "fulfillment_base",
|
||||
label: "Фулфиллмент",
|
||||
iconName: "IconBox",
|
||||
description: "Создание товаров и услуг, их привязка к сделкам",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const MODULES = connectModules(modules);
|
||||
86
src/modules/modulesFileGen/modulesFileGen.ts
Normal file
86
src/modules/modulesFileGen/modulesFileGen.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import * as handlebars from "handlebars";
|
||||
|
||||
// region types
|
||||
type Module = {
|
||||
id: number;
|
||||
key: string;
|
||||
label: string;
|
||||
iconName: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type ModulesResponse = {
|
||||
items: Module[];
|
||||
};
|
||||
|
||||
type Args = {
|
||||
[key: string]: string | boolean;
|
||||
};
|
||||
// endregion
|
||||
|
||||
// region utils
|
||||
const getArgs = (): Args =>
|
||||
process.argv.slice(2).reduce((args: Args, arg: string) => {
|
||||
if (arg.startsWith("--")) {
|
||||
// Handle long arguments like --port=8000
|
||||
const [key, value] = arg.slice(2).split("=");
|
||||
args[key] = value !== undefined ? value : true;
|
||||
} else if (arg.startsWith("-") && arg.length > 1) {
|
||||
// Handle short arguments like -p=8000
|
||||
const [key, value] = arg.slice(1).split("=");
|
||||
args[key] = value !== undefined ? value : true;
|
||||
}
|
||||
return args;
|
||||
}, {});
|
||||
// endregion
|
||||
|
||||
const kwargs = getArgs();
|
||||
|
||||
// region constants
|
||||
const HOST = kwargs.host ?? kwargs.h ?? "127.0.0.1";
|
||||
const PORT = kwargs.port ?? kwargs.p ?? "8000";
|
||||
const ENDPOINT = `http://${HOST}:${PORT}/api/module/built-in/`;
|
||||
|
||||
const TEMPLATE_PATH = path.join(
|
||||
__dirname,
|
||||
"templates",
|
||||
"modulesFileTemplate.hbs"
|
||||
);
|
||||
const OUTPUT_PATH = path.join(__dirname, "..", "modules.tsx");
|
||||
// endregion
|
||||
|
||||
const templateSource = fs.readFileSync(TEMPLATE_PATH, "utf8");
|
||||
const template = handlebars.compile(templateSource);
|
||||
|
||||
handlebars.registerHelper("uppercase", text => {
|
||||
return text.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
|
||||
});
|
||||
|
||||
const generateRows = (modules: Module[]) => {
|
||||
try {
|
||||
const data = {
|
||||
modules,
|
||||
};
|
||||
const tsxContent = template(data);
|
||||
fs.writeFileSync(OUTPUT_PATH, tsxContent);
|
||||
console.log("File successfully generated.");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const modulesFileGen = () => {
|
||||
console.log("Start file generation...");
|
||||
|
||||
axios
|
||||
.get(ENDPOINT)
|
||||
.then((response: AxiosResponse<ModulesResponse>) => {
|
||||
generateRows(response.data.items);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
modulesFileGen();
|
||||
34
src/modules/modulesFileGen/templates/modulesFileTemplate.hbs
Normal file
34
src/modules/modulesFileGen/templates/modulesFileTemplate.hbs
Normal file
@ -0,0 +1,34 @@
|
||||
import {
|
||||
{{#each modules}}
|
||||
{{#if this.iconName}}{{this.iconName}},{{/if}}
|
||||
{{/each}}
|
||||
} from "@tabler/icons-react";
|
||||
import ModulesType from "./types";
|
||||
import connectModules from "./connectModules";
|
||||
|
||||
export enum ModuleNames {
|
||||
{{#each modules}}
|
||||
{{uppercase this.key}} = "{{this.key}}",
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
const modules: ModulesType = {
|
||||
{{#each modules}}
|
||||
[ModuleNames.{{uppercase this.key}}]: {
|
||||
renderInfo: {
|
||||
label: "{{this.label}}",
|
||||
key: "{{this.key}}",
|
||||
icon: {{#if this.iconName}}<{{this.iconName}} />{{else}}None{{/if}},
|
||||
},
|
||||
modelData: {
|
||||
id: {{this.id}},
|
||||
key: "{{this.key}}",
|
||||
label: "{{this.label}}",
|
||||
iconName: "{{this.iconName}}",
|
||||
description: "{{this.description}}",
|
||||
},
|
||||
},
|
||||
{{/each}}
|
||||
};
|
||||
|
||||
export const MODULES = connectModules(modules);
|
||||
18
src/modules/types.tsx
Normal file
18
src/modules/types.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { ReactNode } from "react";
|
||||
import { BuiltInModuleSchema } from "@/lib/client";
|
||||
|
||||
export type Module = {
|
||||
renderInfo: {
|
||||
label: string;
|
||||
key: string;
|
||||
icon: ReactNode;
|
||||
};
|
||||
modelData: BuiltInModuleSchema;
|
||||
getTab?: (props: any) => ReactNode;
|
||||
};
|
||||
|
||||
type ModulesType = {
|
||||
[key: string]: Module;
|
||||
};
|
||||
|
||||
export default ModulesType;
|
||||
8
src/modules/utils/isModuleInProject.ts
Normal file
8
src/modules/utils/isModuleInProject.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { ProjectSchema } from "../../client";
|
||||
import { ModuleNames } from "../modules.tsx";
|
||||
|
||||
const isModuleInProject = (module: ModuleNames, project?: ProjectSchema | null) => {
|
||||
return project?.modules.findIndex(m => m.key === module.toString()) !== -1;
|
||||
};
|
||||
|
||||
export default isModuleInProject;
|
||||
7
src/utils/baseFormInputProps.ts
Normal file
7
src/utils/baseFormInputProps.ts
Normal file
@ -0,0 +1,7 @@
|
||||
type BaseFormInputProps<T> = {
|
||||
onChange: (value: T) => void;
|
||||
value: T;
|
||||
error?: string | null;
|
||||
};
|
||||
|
||||
export default BaseFormInputProps;
|
||||
49
yarn.lock
49
yarn.lock
@ -2890,6 +2890,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mantine/dropzone@npm:^8.3.1":
|
||||
version: 8.3.1
|
||||
resolution: "@mantine/dropzone@npm:8.3.1"
|
||||
dependencies:
|
||||
react-dropzone: "npm:14.3.8"
|
||||
peerDependencies:
|
||||
"@mantine/core": 8.3.1
|
||||
"@mantine/hooks": 8.3.1
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
checksum: 10c0/643c1224925b4575ea7fecef58c26c4bb520f097fb308464c099502ae444a22cc2abd52e96bd84513da9741a49e12ecec0f5a2147322a212c17de00659ec206a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mantine/form@npm:^8.1.3":
|
||||
version: 8.2.1
|
||||
resolution: "@mantine/form@npm:8.2.1"
|
||||
@ -5050,6 +5064,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"attr-accept@npm:^2.2.4":
|
||||
version: 2.2.5
|
||||
resolution: "attr-accept@npm:2.2.5"
|
||||
checksum: 10c0/9b4cb82213925cab2d568f71b3f1c7a7778f9192829aac39a281e5418cd00c04a88f873eb89f187e0bf786fa34f8d52936f178e62cbefb9254d57ecd88ada99b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"autoprefixer@npm:^10.4.21":
|
||||
version: 10.4.21
|
||||
resolution: "autoprefixer@npm:10.4.21"
|
||||
@ -6108,6 +6129,7 @@ __metadata:
|
||||
"@ianvs/prettier-plugin-sort-imports": "npm:^4.4.2"
|
||||
"@mantine/core": "npm:8.1.2"
|
||||
"@mantine/dates": "npm:^8.2.7"
|
||||
"@mantine/dropzone": "npm:^8.3.1"
|
||||
"@mantine/form": "npm:^8.1.3"
|
||||
"@mantine/hooks": "npm:8.1.2"
|
||||
"@mantine/modals": "npm:^8.2.1"
|
||||
@ -6146,6 +6168,7 @@ __metadata:
|
||||
eslint-plugin-jsx-a11y: "npm:^6.10.2"
|
||||
eslint-plugin-react: "npm:^7.37.5"
|
||||
framer-motion: "npm:^12.23.7"
|
||||
handlebars: "npm:^4.7.8"
|
||||
i18n-iso-countries: "npm:^7.14.0"
|
||||
jest: "npm:^30.0.0"
|
||||
jest-environment-jsdom: "npm:^30.0.0"
|
||||
@ -7554,6 +7577,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-selector@npm:^2.1.0":
|
||||
version: 2.1.2
|
||||
resolution: "file-selector@npm:2.1.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.7.0"
|
||||
checksum: 10c0/fe827e0e95410aacfcc3eabc38c29cc36055257f03c1c06b631a2b5af9730c142ad2c52f5d64724d02231709617bda984701f52bd1f4b7aca50fb6585a27c1d2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"filelist@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "filelist@npm:1.0.4"
|
||||
@ -8087,7 +8119,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"handlebars@npm:4.7.8":
|
||||
"handlebars@npm:4.7.8, handlebars@npm:^4.7.8":
|
||||
version: 4.7.8
|
||||
resolution: "handlebars@npm:4.7.8"
|
||||
dependencies:
|
||||
@ -11671,6 +11703,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-dropzone@npm:14.3.8":
|
||||
version: 14.3.8
|
||||
resolution: "react-dropzone@npm:14.3.8"
|
||||
dependencies:
|
||||
attr-accept: "npm:^2.2.4"
|
||||
file-selector: "npm:^2.1.0"
|
||||
prop-types: "npm:^15.8.1"
|
||||
peerDependencies:
|
||||
react: ">= 16.8 || 18.0.0"
|
||||
checksum: 10c0/e17b1832783cda7b8824fe9370e99185d1abbdd5e4980b2985d6321c5768c8de18ff7b9ad550c809ee9743269dea608ff74d5208062754ce8377ad022897b278
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-imask@npm:^7.6.1":
|
||||
version: 7.6.1
|
||||
resolution: "react-imask@npm:7.6.1"
|
||||
@ -13604,7 +13649,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0":
|
||||
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.7.0, tslib@npm:^2.8.0":
|
||||
version: 2.8.1
|
||||
resolution: "tslib@npm:2.8.1"
|
||||
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||
|
||||
Reference in New Issue
Block a user