feat: generation of modules from the server, moved modules fields from the general tab

This commit is contained in:
2025-03-05 16:56:39 +04:00
parent 5d19d254da
commit 56135ae10c
85 changed files with 924 additions and 367 deletions

3
generateModules.sh Executable file
View File

@@ -0,0 +1,3 @@
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

View File

@@ -62,6 +62,7 @@
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^8.42.3",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.7",
"@types/node": "^22.13.9",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/eslint-plugin": "^7.16.1",

View File

@@ -300,6 +300,9 @@ export type { ProductGenerateBarcodeResponse } from './models/ProductGenerateBar
export type { ProductGetBarcodeImageResponse } from './models/ProductGetBarcodeImageResponse'; export type { ProductGetBarcodeImageResponse } from './models/ProductGetBarcodeImageResponse';
export type { ProductGetResponse } from './models/ProductGetResponse'; export type { ProductGetResponse } from './models/ProductGetResponse';
export type { ProductImageSchema } from './models/ProductImageSchema'; export type { ProductImageSchema } from './models/ProductImageSchema';
export type { ProductsAndServicesGeneralInfoRequest } from './models/ProductsAndServicesGeneralInfoRequest';
export type { ProductsAndServicesGeneralInfoResponse } from './models/ProductsAndServicesGeneralInfoResponse';
export type { ProductsAndServicesGeneralInfoSchema } from './models/ProductsAndServicesGeneralInfoSchema';
export type { ProductSchema } from './models/ProductSchema'; export type { ProductSchema } from './models/ProductSchema';
export type { ProductUpdateRequest } from './models/ProductUpdateRequest'; export type { ProductUpdateRequest } from './models/ProductUpdateRequest';
export type { ProductUpdateResponse } from './models/ProductUpdateResponse'; export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
@@ -357,6 +360,10 @@ export type { UpdateBoardOrderRequest } from './models/UpdateBoardOrderRequest';
export type { UpdateBoardOrderResponse } from './models/UpdateBoardOrderResponse'; export type { UpdateBoardOrderResponse } from './models/UpdateBoardOrderResponse';
export type { UpdateBoardRequest } from './models/UpdateBoardRequest'; export type { UpdateBoardRequest } from './models/UpdateBoardRequest';
export type { UpdateBoardResponse } from './models/UpdateBoardResponse'; export type { UpdateBoardResponse } from './models/UpdateBoardResponse';
export type { UpdateCardClientRequest } from './models/UpdateCardClientRequest';
export type { UpdateCardClientResponse } from './models/UpdateCardClientResponse';
export type { UpdateCardManagerRequest } from './models/UpdateCardManagerRequest';
export type { UpdateCardManagerResponse } from './models/UpdateCardManagerResponse';
export type { UpdateDepartmentRequest } from './models/UpdateDepartmentRequest'; export type { UpdateDepartmentRequest } from './models/UpdateDepartmentRequest';
export type { UpdateDepartmentResponse } from './models/UpdateDepartmentResponse'; export type { UpdateDepartmentResponse } from './models/UpdateDepartmentResponse';
export type { UpdateDepartmentSectionRequest } from './models/UpdateDepartmentSectionRequest'; export type { UpdateDepartmentSectionRequest } from './models/UpdateDepartmentSectionRequest';

View File

@@ -9,7 +9,6 @@ export type CardGeneralInfoSchema = {
isDeleted: boolean; isDeleted: boolean;
isCompleted: boolean; isCompleted: boolean;
comment: string; comment: string;
shippingWarehouse?: (string | null);
manager?: (UserSchema | null); manager?: (UserSchema | null);
boardId: number; boardId: number;
statusId: number; statusId: number;

View File

@@ -6,6 +6,7 @@ export type ModuleSchema = {
id: number; id: number;
key: string; key: string;
label: string; label: string;
iconName?: (string | null);
isDeleted: boolean; isDeleted: boolean;
}; };

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ProductsAndServicesGeneralInfoSchema } from './ProductsAndServicesGeneralInfoSchema';
export type ProductsAndServicesGeneralInfoRequest = {
cardId: number;
data: ProductsAndServicesGeneralInfoSchema;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductsAndServicesGeneralInfoResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductsAndServicesGeneralInfoSchema = {
shippingWarehouse?: (string | null);
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateCardClientRequest = {
cardId: number;
clientId: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateCardClientResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateCardManagerRequest = {
cardId: number;
managerId: (number | null);
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateCardManagerResponse = {
ok: boolean;
message: string;
};

View File

@@ -59,6 +59,12 @@ import type { GetCardProductsBarcodesPdfResponse } from '../models/GetCardProduc
import type { ManageEmployeeRequest } from '../models/ManageEmployeeRequest'; import type { ManageEmployeeRequest } from '../models/ManageEmployeeRequest';
import type { ManageEmployeeResponse } from '../models/ManageEmployeeResponse'; import type { ManageEmployeeResponse } from '../models/ManageEmployeeResponse';
import type { ParseCardsExcelResponse } from '../models/ParseCardsExcelResponse'; import type { ParseCardsExcelResponse } from '../models/ParseCardsExcelResponse';
import type { ProductsAndServicesGeneralInfoRequest } from '../models/ProductsAndServicesGeneralInfoRequest';
import type { ProductsAndServicesGeneralInfoResponse } from '../models/ProductsAndServicesGeneralInfoResponse';
import type { UpdateCardClientRequest } from '../models/UpdateCardClientRequest';
import type { UpdateCardClientResponse } from '../models/UpdateCardClientResponse';
import type { UpdateCardManagerRequest } from '../models/UpdateCardManagerRequest';
import type { UpdateCardManagerResponse } from '../models/UpdateCardManagerResponse';
import type { CancelablePromise } from '../core/CancelablePromise'; import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
@@ -236,6 +242,66 @@ export class CardService {
}, },
}); });
} }
/**
* Update Products And Services General Info
* @returns ProductsAndServicesGeneralInfoResponse Successful Response
* @throws ApiError
*/
public static updateProductsAndServicesGeneralInfo({
requestBody,
}: {
requestBody: ProductsAndServicesGeneralInfoRequest,
}): CancelablePromise<ProductsAndServicesGeneralInfoResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/card/update-products-and-services-general-info',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Update Card Manager
* @returns UpdateCardManagerResponse Successful Response
* @throws ApiError
*/
public static updateCardManager({
requestBody,
}: {
requestBody: UpdateCardManagerRequest,
}): CancelablePromise<UpdateCardManagerResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/card/update-card-manager',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Update Card Client
* @returns UpdateCardClientResponse Successful Response
* @throws ApiError
*/
public static updateCardClient({
requestBody,
}: {
requestBody: UpdateCardClientRequest,
}): CancelablePromise<UpdateCardClientResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/card/update-card-client',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/** /**
* Add Kit To Card * Add Kit To Card
* @returns CardAddKitResponse Successful Response * @returns CardAddKitResponse Successful Response

View File

@@ -3,7 +3,7 @@ import { UseFormReturnType } from "@mantine/form";
import { rem, Stack } from "@mantine/core"; import { rem, Stack } from "@mantine/core";
import { ReactNode } from "react"; import { ReactNode } from "react";
import CardAttributeField from "./components/CardAttributeField.tsx"; import CardAttributeField from "./components/CardAttributeField.tsx";
import { CardGeneralFormType } from "../../pages/CardsPage/tabs/GeneralTab/GeneralTab.tsx"; import { CardGeneralFormType } from "../../pages/CardsPage/drawers/CardEditDrawer/tabs/GeneralTab/GeneralTab.tsx";
type Props = { type Props = {
project: ProjectSchema; project: ProjectSchema;

View File

@@ -2,7 +2,7 @@ import { AttributeSchema } from "../../../client";
import { Checkbox, Group, NumberInput, TextInput, Tooltip } from "@mantine/core"; import { Checkbox, Group, NumberInput, TextInput, Tooltip } from "@mantine/core";
import { UseFormReturnType } from "@mantine/form"; import { UseFormReturnType } from "@mantine/form";
import { DatePickerInput, DateTimePicker } from "@mantine/dates"; import { DatePickerInput, DateTimePicker } from "@mantine/dates";
import { CardGeneralFormType } from "../../../pages/CardsPage/tabs/GeneralTab/GeneralTab.tsx"; import { CardGeneralFormType } from "../../../pages/CardsPage/drawers/CardEditDrawer/tabs/GeneralTab/GeneralTab.tsx";
import { IconInfoCircle } from "@tabler/icons-react"; import { IconInfoCircle } from "@tabler/icons-react";
type Props = { type Props = {

View File

@@ -10,7 +10,7 @@ type SelectProps = Omit<ObjectSelectProps<StatusSchema | null>, "data" | "getLab
type Props = OtherProps & SelectProps; type Props = OtherProps & SelectProps;
const DealStatusSelect: FC<Props> = ({ board, ...props}) => { const CardStatusSelect: FC<Props> = ({ board, ...props}) => {
const [isInitial, setIsInitial] = useState<boolean>(true); const [isInitial, setIsInitial] = useState<boolean>(true);
const filteredData = board?.statuses.filter( const filteredData = board?.statuses.filter(
@@ -37,4 +37,4 @@ const DealStatusSelect: FC<Props> = ({ board, ...props}) => {
/> />
); );
}; };
export default DealStatusSelect; export default CardStatusSelect;

View File

@@ -5,6 +5,8 @@ import { Flex, rem, Text, TextInput, useMantineColorScheme } from "@mantine/core
import { IconGripHorizontal } from "@tabler/icons-react"; import { IconGripHorizontal } from "@tabler/icons-react";
import { useDebouncedValue } from "@mantine/hooks"; import { useDebouncedValue } from "@mantine/hooks";
import { notifications } from "../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../shared/lib/notifications.ts";
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
type Props = { type Props = {
cards: CardSummary[]; cards: CardSummary[];
@@ -15,6 +17,9 @@ export const CardGroupView: FC<Props> = ({ cards, group }) => {
const theme = useMantineColorScheme(); const theme = useMantineColorScheme();
const [name, setName] = useState<string>(group.name || ""); const [name, setName] = useState<string>(group.name || "");
const [debouncedName] = useDebouncedValue(name, 200); const [debouncedName] = useDebouncedValue(name, 200);
const { selectedProject } = useProjectsContext();
const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
const totalPrice = useMemo(() => cards.reduce((acc, card) => acc + card.totalPrice, 0), [cards]); const totalPrice = useMemo(() => cards.reduce((acc, card) => acc + card.totalPrice, 0), [cards]);
const totalProducts = useMemo(() => cards.reduce((acc, card) => acc + card.totalProducts, 0), [cards]); const totalProducts = useMemo(() => cards.reduce((acc, card) => acc + card.totalProducts, 0), [cards]);
const updateName = () => { const updateName = () => {
@@ -70,19 +75,21 @@ export const CardGroupView: FC<Props> = ({ cards, group }) => {
/> />
))} ))}
</Flex> </Flex>
<Flex {isServicesAndProductsIncluded && (
p={rem(10)} <Flex
direction={"column"} p={rem(10)}
bg={theme.colorScheme === "dark" ? "var(--mantine-color-dark-6)" : "var(--mantine-color-gray-2)"} direction={"column"}
style={{ borderRadius: "0.5rem" }} bg={theme.colorScheme === "dark" ? "var(--mantine-color-dark-6)" : "var(--mantine-color-gray-2)"}
> style={{ borderRadius: "0.5rem" }}
<Text >
c={"gray.6"} <Text
size={"xs"}>Сумма: {totalPrice.toLocaleString("ru-RU")} руб.</Text> c={"gray.6"}
<Text size={"xs"}>Сумма: {totalPrice.toLocaleString("ru-RU")} руб.</Text>
c={"gray.6"} <Text
size={"xs"}>Всего товаров: {totalProducts.toLocaleString("ru-RU")} шт.</Text> c={"gray.6"}
</Flex> size={"xs"}>Всего товаров: {totalProducts.toLocaleString("ru-RU")} шт.</Text>
</Flex>
)}
</Flex> </Flex>
); );
}; };

View File

@@ -9,7 +9,7 @@ import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { IconCheck, IconLayoutGridRemove, IconTrash } from "@tabler/icons-react"; import { IconCheck, IconLayoutGridRemove, IconTrash } from "@tabler/icons-react";
import { useContextMenu } from "mantine-contextmenu"; import { useContextMenu } from "mantine-contextmenu";
import useCardSummaryState from "./useCardSummaryState.tsx"; import useCardSummaryState from "./useCardSummaryState.tsx";
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts"; import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx"; import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
type Props = { type Props = {

View File

@@ -9,7 +9,7 @@ import { groupBy, has, uniq } from "lodash";
import { CardGroupView } from "../CardGroupView/CardGroupView.tsx"; import { CardGroupView } from "../CardGroupView/CardGroupView.tsx";
import CreateDealsFromFileButton from "../CreateCardsFromFileButton/CreateDealsFromFileButton.tsx"; import CreateDealsFromFileButton from "../CreateCardsFromFileButton/CreateDealsFromFileButton.tsx";
import DragState from "../../../../pages/CardsPage/enums/DragState.ts"; import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts"; import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx"; import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
type Props = { type Props = {

View File

@@ -7,7 +7,7 @@ import { CardService, StatusSchema } from "../../../../client";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { dateWithoutTimezone } from "../../../../shared/lib/date.ts"; import { dateWithoutTimezone } from "../../../../shared/lib/date.ts";
import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx"; import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx";
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts"; import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx"; import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
type Props = { type Props = {

View File

@@ -9,7 +9,7 @@ import ShippingWarehouseAutocomplete
from "../../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx"; from "../../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
import BaseMarketplaceSelect from "../../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx"; import BaseMarketplaceSelect from "../../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx"; import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx";
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts"; import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx"; import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
type Props = { type Props = {

View File

@@ -9,7 +9,7 @@ import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
import { useContextMenu } from "mantine-contextmenu"; import { useContextMenu } from "mantine-contextmenu";
import { IconEdit, IconTrash } from "@tabler/icons-react"; import { IconEdit, IconTrash } from "@tabler/icons-react";
import useStatus from "./hooks/useStatus.tsx"; import useStatus from "./hooks/useStatus.tsx";
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts"; import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
type Props = { type Props = {

View File

@@ -7,7 +7,7 @@ type Props = Omit<
ObjectSelectProps<UserSchema | null>, ObjectSelectProps<UserSchema | null>,
"data" | "getValueFn" | "getLabelFn" "data" | "getValueFn" | "getLabelFn"
>; >;
const UserSelect: FC<Props> = props => { const ManagerSelect: FC<Props> = props => {
const { objects: managers } = useManagersList(); const { objects: managers } = useManagersList();
return ( return (
<ObjectSelect <ObjectSelect
@@ -21,4 +21,4 @@ const UserSelect: FC<Props> = props => {
/> />
); );
}; };
export default UserSelect; export default ManagerSelect;

View File

@@ -9,8 +9,9 @@ type Props = {
withLabel?: boolean; withLabel?: boolean;
error?: string; error?: string;
inputContainer?: (children: ReactNode) => ReactNode; inputContainer?: (children: ReactNode) => ReactNode;
disabled?: boolean;
}; };
const ClientSelect: FC<Props> = ({ value, onChange, error, inputContainer, withLabel = false }) => { const ClientSelect: FC<Props> = ({ value, onChange, error, inputContainer, withLabel = false, disabled = false }) => {
const { clients } = useClientsList(); const { clients } = useClientsList();
const options = clients.map(client => ({ const options = clients.map(client => ({
label: client.name, label: client.name,
@@ -37,6 +38,7 @@ const ClientSelect: FC<Props> = ({ value, onChange, error, inputContainer, withL
label={withLabel && "Клиент"} label={withLabel && "Клиент"}
error={error} error={error}
inputContainer={inputContainer} inputContainer={inputContainer}
disabled={disabled}
/> />
); );
}; };

View File

@@ -13,7 +13,7 @@ function usePollingEffect(
): void { ): void {
const { interval = 3000, isActive = true, onCleanUp = () => {} } = options; const { interval = 3000, isActive = true, onCleanUp = () => {} } = options;
const timeoutIdRef = useRef<number | null>(null); const timeoutIdRef = useRef<NodeJS.Timeout | number | null>(null);
useEffect(() => { useEffect(() => {
if (!isActive) { if (!isActive) {

View File

@@ -24,6 +24,7 @@ import { modals } from "./modals/modals.ts";
import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx"; import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx";
import { ContextMenuProvider } from "mantine-contextmenu"; import { ContextMenuProvider } from "mantine-contextmenu";
import { ProjectsContextProvider } from "./contexts/ProjectsContext.tsx"; import { ProjectsContextProvider } from "./contexts/ProjectsContext.tsx";
import { ModulesContextProvider } from "./modules/context/ModulesContext.tsx";
// Configuring router // Configuring router
const router = createRouter({ routeTree }); const router = createRouter({ routeTree });
@@ -56,8 +57,10 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
<DatesProvider settings={{ locale: "ru" }}> <DatesProvider settings={{ locale: "ru" }}>
<TasksProvider> <TasksProvider>
<ProjectsContextProvider> <ProjectsContextProvider>
<RouterProvider router={router} /> <ModulesContextProvider>
<Notifications /> <RouterProvider router={router} />
<Notifications />
</ModulesContextProvider>
</ProjectsContextProvider> </ProjectsContextProvider>
</TasksProvider> </TasksProvider>
</DatesProvider> </DatesProvider>

View File

@@ -3,13 +3,13 @@ import CreateServiceCategoryModal from "../pages/ServicesPage/modals/CreateServi
import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx"; import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx";
import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx"; import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx";
import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx"; import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx";
import AddCardServiceModal from "../pages/CardsPage/modals/AddCardServiceModal.tsx"; import AddCardServiceModal from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/AddCardServiceModal.tsx";
import AddCardProductModal from "../pages/CardsPage/modals/AddCardProductModal.tsx"; import AddCardProductModal from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/AddCardProductModal.tsx";
import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx"; import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx";
import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx"; import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx";
import BarcodeTemplateFormModal import BarcodeTemplateFormModal
from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx"; from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx";
import ProductServiceFormModal from "../pages/CardsPage/modals/ProductServiceFormModal.tsx"; import ProductServiceFormModal from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/ProductServiceFormModal.tsx";
import UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx"; import UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx";
import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx"; import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx";
import EmployeeTableModal from "./EmployeeTableModal/EmployeeTableModal.tsx"; import EmployeeTableModal from "./EmployeeTableModal/EmployeeTableModal.tsx";
@@ -24,11 +24,11 @@ import MarketplaceFormModal from "../pages/MarketplacesPage/modals/MarketplaceFo
import ScanningModal from "./ScanningModal/ScanningModal.tsx"; import ScanningModal from "./ScanningModal/ScanningModal.tsx";
import TransactionFormModal from "../pages/AdminPage/tabs/Transactions/modals/TransactionFormModal.tsx"; import TransactionFormModal from "../pages/AdminPage/tabs/Transactions/modals/TransactionFormModal.tsx";
import TransactionTagsModal from "../pages/AdminPage/tabs/Transactions/modals/TransactionTagsModal.tsx"; import TransactionTagsModal from "../pages/AdminPage/tabs/Transactions/modals/TransactionTagsModal.tsx";
import ShippingProductModal from "../pages/CardsPage/tabs/ShippingTab/modals/ShippingProductModal.tsx"; import ShippingProductModal from "../modules/cardModules/cardEditorTabs/ShippingTab/modals/ShippingProductModal.tsx";
import DepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/DepartmentModal.tsx"; import DepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/DepartmentModal.tsx";
import AddUserToDepartmentModal import AddUserToDepartmentModal
from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.tsx"; from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.tsx";
import AssignEmployeeModal from "../pages/CardsPage/tabs/EmployeesTab/modals/AssignEmployeeModal.tsx"; import AssignEmployeeModal from "../modules/cardModules/cardEditorTabs/EmployeesTab/modals/AssignEmployeeModal.tsx";
import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx"; import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx";
import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx"; import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx";
import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx"; import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx";

View File

@@ -0,0 +1,147 @@
import { Button, Fieldset, Group, rem, Stack, Textarea, TextInput } from "@mantine/core";
import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { useForm } from "@mantine/form";
import { CardService, ClientSchema, ClientService } from "../../../../client";
import { notifications } from "../../../../shared/lib/notifications.ts";
import ClientSelect from "../../../../components/Selects/ClientSelect/ClientSelect.tsx";
import { useEffect, useState } from "react";
import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
import { isEqual } from "lodash";
const ClientTab = () => {
const { selectedCard: card, refetchCard } = useCardPageContext();
const [initialValues, setInitialValues] = useState<Partial<ClientSchema>>(card?.client ?? {});
const [client, setClient] = useState<ClientSchema | undefined>(card?.client ?? undefined);
const form = useForm<Partial<ClientSchema>>(
{
initialValues,
},
);
useEffect(() => {
const data = card?.client ?? {};
setInitialValues(data);
form.setValues(data);
}, [card]);
const isEditorDisabled = () => client?.id !== card?.client?.id;
const handleSubmitClientInfo = (values: ClientSchema) => {
ClientService.updateClient({
requestBody: {
data: values,
},
})
.then(({ ok, message }) => {
if (!ok) {
notifications.error({ message });
return;
}
refetchCard();
})
.catch(err => console.log(err));
};
const handleSelectClient = () => {
if (!(card && client)) return;
CardService.updateCardClient({
requestBody: {
cardId: card?.id,
clientId: client?.id,
},
})
.then(({ ok, message }) => {
if (!ok) {
notifications.error({ message });
return;
}
refetchCard();
})
.catch(err => console.log(err));
};
const clientDataEditor = (
<Fieldset legend={"Данные клиента"} flex={1}>
<form
onSubmit={
form.onSubmit(values => handleSubmitClientInfo(values as ClientSchema))
}>
<Stack gap={rem(10)}>
<TextInput
disabled
placeholder={"Название"}
label={"Название"}
value={card?.client?.name}
/>
<TextInput
disabled={isEditorDisabled()}
placeholder={"Введите телефон"}
label={"Телефон клиента"}
{...form.getInputProps("details.phoneNumber")}
/>
<TextInput
disabled={isEditorDisabled()}
placeholder={"Введите email"}
label={"Email"}
{...form.getInputProps("details.email")}
/>
<TextInput
disabled={isEditorDisabled()}
placeholder={"Введите телеграм"}
label={"Телеграм"}
{...form.getInputProps("details.telegram")}
/>
<TextInput
disabled={isEditorDisabled()}
placeholder={"Введите ИНН"}
label={"ИНН"}
{...form.getInputProps("details.inn")}
/>
<Textarea
disabled={isEditorDisabled()}
placeholder={"Введите комментарий"}
label={"Комментарий"}
{...form.getInputProps("comment")}
/>
<Group>
<Button
variant={"default"}
disabled={isEditorDisabled() || isEqual(initialValues, form.values)}
type="submit"
>
Сохранить
</Button>
</Group>
</Stack>
</form>
</Fieldset>
);
return (
<Stack flex={1}>
<Fieldset legend={"Выбор клиента"} flex={1}>
<Stack gap={rem(10)}>
<ClientSelect
value={client}
onChange={setClient}
withLabel
disabled={!isEqual(initialValues, form.values)}
/>
<Group>
<InlineButton
onClick={handleSelectClient}
disabled={!isEditorDisabled()}
>
Сохранить
</InlineButton>
</Group>
</Stack>
</Fieldset>
{clientDataEditor}
</Stack>
);
};
export default ClientTab;

View File

@@ -1,4 +1,4 @@
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx"; import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
import useEmployeeTableColumns from "../hooks/useEmployeesTableColumns.tsx"; import useEmployeeTableColumns from "../hooks/useEmployeesTableColumns.tsx";
import { ActionIcon, Flex, Tooltip } from "@mantine/core"; import { ActionIcon, Flex, Tooltip } from "@mantine/core";

View File

@@ -1,6 +1,6 @@
import { notifications } from "../../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../shared/lib/notifications.ts";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { CardEmployeesSchema, CardService } from "../../../../../client"; import { CardEmployeesSchema, CardService } from "../../../../../client";
const useEmployeesTab = () => { const useEmployeesTab = () => {

View File

@@ -0,0 +1,70 @@
import { Button, Fieldset, Group, rem, Stack } from "@mantine/core";
import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { useForm } from "@mantine/form";
import { CardSchema, CardService, type UserSchema } from "../../../../client";
import { notifications } from "../../../../shared/lib/notifications.ts";
import ManagerSelect from "../../../../components/ManagerSelect/ManagerSelect.tsx";
import { useEffect, useState } from "react";
import { isEqual } from "lodash";
type ManagerForm = {
manager?: (UserSchema | null);
}
const ManagerTab = () => {
const { selectedCard: card, refetchCard } = useCardPageContext();
const [initialValues, setInitialValues] = useState<ManagerForm>(card as CardSchema);
const form = useForm<ManagerForm>({
initialValues,
});
useEffect(() => {
const data = card ?? {};
setInitialValues(data);
form.setValues(data);
}, [card]);
const onSubmit = async (values: ManagerForm) => {
if (!card) return;
return CardService.updateCardManager({
requestBody: {
cardId: card.id,
managerId: values.manager?.id ?? null,
},
})
.then(({ ok, message }) => {
if (!ok) {
notifications.error({ message });
return;
}
refetchCard();
})
.catch(err => console.log(err));
};
return (
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
<Fieldset flex={1}>
<Stack gap={rem(10)}>
<ManagerSelect
placeholder={"Укажите менеджера"}
label={"Менеджер"}
{...form.getInputProps("manager")}
/>
<Group>
<Button
type={"submit"}
variant={"default"}
disabled={isEqual(initialValues, form.values)}
>
Сохранить
</Button>
</Group>
</Stack>
</Fieldset>
</form>
);
};
export default ManagerTab;

View File

@@ -9,7 +9,7 @@
} }
.products-list { .products-list {
width: 60%; width: 52%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: rem(10); gap: rem(10);

View File

@@ -1,16 +1,8 @@
import { FC } from "react"; import { FC } from "react";
import styles from "./ProductAndServiceTab.module.css"; import styles from "./ProductAndServiceTab.module.css";
import ProductView from "./components/ProductView/ProductView.tsx"; import ProductView from "./components/ProductView/ProductView.tsx";
import { import { Button, Checkbox, Divider, Flex, Group, rem, ScrollArea, Stack, Text, Title } from "@mantine/core";
Button, import CardServicesTable from "./components/CardServicesTable/CardServicesTable.tsx";
Divider,
Flex,
rem,
ScrollArea,
Text,
Title,
} from "@mantine/core";
import CardServicesTable from "./components/DealServicesTable/CardServicesTable.tsx";
import useCardProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx"; import useCardProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import { import {
@@ -22,17 +14,19 @@ import {
ProductService, ProductService,
} from "../../../../client"; } from "../../../../client";
import { notifications } from "../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../shared/lib/notifications.ts";
import { CreateProductRequest } from "../../../ProductsPage/types.ts"; import { CreateProductRequest } from "../../../../pages/ProductsPage/types.ts";
import classNames from "classnames"; import classNames from "classnames";
import GeneralDataForm from "./components/GeneralDataForm/GeneralDataForm.tsx";
import PrintDealBarcodesButton from "./components/PrintDealBarcodesButton/PrintDealBarcodesButton.tsx";
import PaymentLinkButton from "./components/PaymentLinkButton/PaymentLinkButton.tsx";
const ProductAndServiceTab: FC = () => { const ProductAndServiceTab: FC = () => {
const { cardState, cardServicesState, cardProductsState } = const { cardState, cardServicesState, cardProductsState } = useCardProductAndServiceTabState();
useCardProductAndServiceTabState();
const isLocked = Boolean(cardState.card?.billRequest || cardState.card?.group?.billRequest); const isLocked = Boolean(cardState.card?.billRequest || cardState.card?.group?.billRequest);
const onAddProductClick = () => { const onAddProductClick = () => {
if (!cardProductsState.onCreate || !cardState.card || !cardState.card.clientId) return; if (!cardProductsState.onCreate || !cardState.card || !cardState.card.clientId) return;
const productIds = cardState.card.products.map( const productIds = cardState.card.products.map(
product => product.product.id product => product.product.id,
); );
modals.openContextModal({ modals.openContextModal({
modal: "addCardProduct", modal: "addCardProduct",
@@ -51,26 +45,26 @@ const ProductAndServiceTab: FC = () => {
acc + acc +
row.services.reduce( row.services.reduce(
(acc2, row2) => acc2 + row2.price * row.quantity, (acc2, row2) => acc2 + row2.price * row.quantity,
0 0,
), ),
0 0,
); );
const cardServicesPrice = cardState.card.services.reduce( const cardServicesPrice = cardState.card.services.reduce(
(acc, row) => acc + row.price * row.quantity, (acc, row) => acc + row.price * row.quantity,
0 0,
); );
return cardServicesPrice + productServicesPrice; return cardServicesPrice + productServicesPrice;
}; };
const onCopyServices = ( const onCopyServices = (
sourceProduct: CardProductSchema, sourceProduct: CardProductSchema,
destinationProducts: CardProductSchema[] destinationProducts: CardProductSchema[],
) => { ) => {
if (!cardState.card) return; if (!cardState.card) return;
CardService.copyProductServices({ CardService.copyProductServices({
requestBody: { requestBody: {
cardId: cardState.card.id, cardId: cardState.card.id,
destinationProductIds: destinationProducts.map( destinationProductIds: destinationProducts.map(
product => product.product.id product => product.product.id,
), ),
sourceProductId: sourceProduct.product.id, sourceProductId: sourceProduct.product.id,
}, },
@@ -147,7 +141,7 @@ const ProductAndServiceTab: FC = () => {
notifications.guess(ok, { message }); notifications.guess(ok, { message });
if (!ok) return; if (!ok) return;
await cardState.refetch(); await cardState.refetch();
} },
); );
}; };
@@ -212,11 +206,12 @@ const ProductAndServiceTab: FC = () => {
}, },
}); });
}; };
return ( return (
<div <div
className={classNames( className={classNames(
styles["container"], styles["container"],
cardState.card?.billRequest && styles["container-disabled"] cardState.card?.billRequest && styles["container-disabled"],
)}> )}>
<div className={styles["products-list"]}> <div className={styles["products-list"]}>
<ScrollArea offsetScrollbars> <ScrollArea offsetScrollbars>
@@ -235,6 +230,27 @@ const ProductAndServiceTab: FC = () => {
</div> </div>
<div className={styles["card-container"]}> <div className={styles["card-container"]}>
{cardState.card && (
<Group
className={styles["card-container-wrapper"]}
justify={"space-between"}
wrap={"nowrap"}
mr={"xs"}
>
<Group wrap={"nowrap"}>
<PrintDealBarcodesButton card={cardState.card} />
<Checkbox
label={"Оплачен"}
checked={cardState.card.billRequest?.paid || cardState.card.group?.billRequest?.paid || false}
disabled
/>
</Group>
<PaymentLinkButton card={cardState.card} />
</Group>
)}
<Stack className={styles["card-container-wrapper"]} mr={"xs"}>
<GeneralDataForm />
</Stack>
<ScrollArea offsetScrollbars> <ScrollArea offsetScrollbars>
<Flex <Flex
direction={"column"} direction={"column"}

View File

@@ -5,7 +5,7 @@ import { ActionIcon, Button, Flex, Modal, NumberInput, rem, Text, Title, Tooltip
import { IconTrash, IconUsersGroup } from "@tabler/icons-react"; import { IconTrash, IconUsersGroup } from "@tabler/icons-react";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import { isNumber } from "lodash"; import { isNumber } from "lodash";
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx"; import SimpleUsersTable from "../../../../../../pages/CardsPage/components/SimpleUsersTable/SimpleUsersTable.tsx";
import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../../../../redux/store.ts"; import { RootState } from "../../../../../../redux/store.ts";

View File

@@ -0,0 +1,107 @@
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";
type GeneralDataFormType = {
shippingWarehouse?: ShippingWarehouseSchema | null | string;
isServicesProfitAccounted: boolean;
}
const GeneralDataForm = () => {
const { selectedCard: card, refetchCard } = useCardPageContext();
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={"Введите склад отгрузки"}
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);
}}
/>
<Checkbox
label={"Учет выручки в статистике"}
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
/>
<Button
type={"submit"}
variant={"default"}
disabled={isEqual(initialValues, form.values)}
>
Сохранить
</Button>
</Stack>
</form>
);
};
export default GeneralDataForm;

View File

@@ -1,7 +1,7 @@
import { CardSchema } from "../../../../../client"; import { CardSchema } from "../../../../../../client";
import ButtonCopy from "../../../../../components/ButtonCopy/ButtonCopy.tsx"; import ButtonCopy from "../../../../../../components/ButtonCopy/ButtonCopy.tsx";
import { ButtonCopyControlled } from "../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx"; import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
import { getCurrentDateTimeForFilename } from "../../../../../shared/lib/date.ts"; import { getCurrentDateTimeForFilename } from "../../../../../../shared/lib/date.ts";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
type Props = { type Props = {

View File

@@ -1,8 +1,8 @@
import { ActionIcon, Tooltip } from "@mantine/core"; import { ActionIcon, Group, Tooltip } from "@mantine/core";
import styles from "../../../ui/CardsPage.module.css"; import styles from "../../../../../../pages/CardsPage/ui/CardsPage.module.css";
import { CardSchema, CardService } from "../../../../../client"; import { CardSchema, CardService } from "../../../../../../client";
import { base64ToBlob } from "../../../../../shared/lib/utils.ts"; import { base64ToBlob } from "../../../../../../shared/lib/utils.ts";
import { notifications } from "../../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../../shared/lib/notifications.ts";
import { IconBarcode, IconPrinter } from "@tabler/icons-react"; import { IconBarcode, IconPrinter } from "@tabler/icons-react";
@@ -12,7 +12,7 @@ type Props = {
const PrintDealBarcodesButton = ({ card }: Props) => { const PrintDealBarcodesButton = ({ card }: Props) => {
return ( return (
<> <Group wrap={"nowrap"}>
<Tooltip <Tooltip
className={styles["print-deals-button"]} className={styles["print-deals-button"]}
label={"Распечатать штрихкоды сделки"} label={"Распечатать штрихкоды сделки"}
@@ -56,7 +56,7 @@ const PrintDealBarcodesButton = ({ card }: Props) => {
<IconPrinter /> <IconPrinter />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</> </Group>
); );
}; };

View File

@@ -7,7 +7,7 @@ import { MRT_TableOptions } from "mantine-react-table";
import { ActionIcon, Button, Flex, Modal, rem, Tooltip } from "@mantine/core"; import { ActionIcon, Button, Flex, Modal, rem, Tooltip } from "@mantine/core";
import { IconEdit, IconTrash, IconUsersGroup } from "@tabler/icons-react"; import { IconEdit, IconTrash, IconUsersGroup } from "@tabler/icons-react";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx"; import SimpleUsersTable from "../../../../../../pages/CardsPage/components/SimpleUsersTable/SimpleUsersTable.tsx";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../../../../redux/store.ts"; import { RootState } from "../../../../../../redux/store.ts";
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx"; import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";

View File

@@ -1,6 +1,6 @@
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx"; import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
import { CardService, CardServiceSchema, CardProductSchema } from "../../../../../client"; import { CardService, CardServiceSchema, CardProductSchema } from "../../../../../client";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { notifications } from "../../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../shared/lib/notifications.ts";
const useCardState = () => { const useCardState = () => {

View File

@@ -1,11 +1,11 @@
import { ContextModalProps } from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
import BaseFormModal, { import BaseFormModal, {
CreateEditFormProps, CreateEditFormProps,
} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; } from "../../../../../pages/ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import { CardProductSchema, CardProductServiceSchema } from "../../../client"; import { CardProductSchema, CardProductServiceSchema } from "../../../../../client";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { NumberInput } from "@mantine/core"; import { NumberInput } from "@mantine/core";
import ProductSelect from "../../../components/ProductSelect/ProductSelect.tsx"; import ProductSelect from "../../../../../components/ProductSelect/ProductSelect.tsx";
import { omit } from "lodash"; import { omit } from "lodash";
type RestProps = { type RestProps = {

View File

@@ -1,12 +1,12 @@
import { ContextModalProps } from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; import BaseFormModal, { CreateEditFormProps } from "../../../../../pages/ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import { CardServiceSchema } from "../../../client"; import { CardServiceSchema } from "../../../../../client";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter } from "@mantine/core"; import { ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter } from "@mantine/core";
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx"; import ServiceWithPriceInput from "../../../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
import { ServiceType } from "../../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../../../../shared/enums/ServiceType.ts";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store.ts"; import { RootState } from "../../../../../redux/store.ts";
type RestProps = { type RestProps = {
serviceIds?: number[]; serviceIds?: number[];

View File

@@ -1,18 +1,18 @@
import BaseFormModal, { import BaseFormModal, {
CreateEditFormProps, CreateEditFormProps,
} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; } from "../../../../../pages/ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import { import {
CardProductServiceSchema, CardProductServiceSchema,
ServiceSchema, ServiceSchema,
} from "../../../client"; } from "../../../../../client";
import { ContextModalProps } from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
import { useForm, UseFormReturnType } from "@mantine/form"; import { useForm, UseFormReturnType } from "@mantine/form";
import { isNil, isNumber } from "lodash"; import { isNil, isNumber } from "lodash";
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx"; import ServiceWithPriceInput from "../../../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
import { Checkbox, Flex, rem } from "@mantine/core"; import { Checkbox, Flex, rem } from "@mantine/core";
import { ServiceType } from "../../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../../../../shared/enums/ServiceType.ts";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store.ts"; import { RootState } from "../../../../../redux/store.ts";
type RestProps = { type RestProps = {
quantity: number; quantity: number;

View File

@@ -8,7 +8,7 @@ import classes from "../ShippingTab.module.css";
import "mantine-datatable/styles.css"; import "mantine-datatable/styles.css";
import { useState } from "react"; import { useState } from "react";
import ShippingProductsTable from "./ShippingProductsTable.tsx"; import ShippingProductsTable from "./ShippingProductsTable.tsx";
import { Group, rem, Text } from "@mantine/core"; import { Flex, rem, Text } from "@mantine/core";
import { ShippingProductParentData } from "../types/ShippingProductData.tsx"; import { ShippingProductParentData } from "../types/ShippingProductData.tsx";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import InlineShippingButton from "./InlineShippingButton.tsx"; import InlineShippingButton from "./InlineShippingButton.tsx";
@@ -63,15 +63,15 @@ const BoxesTable = ({ items, onCreateShippingProduct }: Props) => {
const getBoxActions = (box: BoxSchema) => { const getBoxActions = (box: BoxSchema) => {
return ( return (
<Group wrap={"nowrap"} gap={rem(10)}> <Flex wrap={"nowrap"} direction={"row-reverse"} gap={rem(10)}>
<InlineShippingButton onClick={() => onDeleteBoxClick(box)}>
<IconTrash />
</InlineShippingButton>
<InlineShippingButton onClick={() => onCreateShippingProduct({ boxId: box.id })}> <InlineShippingButton onClick={() => onCreateShippingProduct({ boxId: box.id })}>
<IconPlus /> <IconPlus />
Товар Товар
</InlineShippingButton> </InlineShippingButton>
<InlineShippingButton onClick={() => onDeleteBoxClick(box)}> </Flex>
<IconTrash />
</InlineShippingButton>
</Group>
); );
}; };

View File

@@ -1,4 +1,4 @@
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { Flex, rem, Stack } from "@mantine/core"; import { Flex, rem, Stack } from "@mantine/core";
import { BoxSchema, PalletSchema, ShippingProductSchema } from "../../../../../client"; import { BoxSchema, PalletSchema, ShippingProductSchema } from "../../../../../client";
import ShippingProductsTable from "./ShippingProductsTable.tsx"; import ShippingProductsTable from "./ShippingProductsTable.tsx";

View File

@@ -5,7 +5,7 @@ import { IconEdit, IconTrash } from "@tabler/icons-react";
import { notifications } from "../../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../shared/lib/notifications.ts";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import useUpdateCard from "./useUpdateCard.tsx"; import useUpdateCard from "./useUpdateCard.tsx";
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
const useShippingTableColumns = () => { const useShippingTableColumns = () => {
const { update } = useUpdateCard(); const { update } = useUpdateCard();

View File

@@ -1,4 +1,4 @@
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { import {
CreateBoxInCardSchema, CreateBoxInCardSchema,
CreateBoxInPalletSchema, PalletSchema, CreateBoxInPalletSchema, PalletSchema,

View File

@@ -1,4 +1,4 @@
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
const useShippingQrs = () => { const useShippingQrs = () => {

View File

@@ -1,4 +1,4 @@
import { useCardPageContext } from "../../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
import { CardService } from "../../../../../client"; import { CardService } from "../../../../../client";
const useUpdateCard = () => { const useUpdateCard = () => {

View File

@@ -0,0 +1,20 @@
import { ModuleNames } from "./modules.tsx";
import ClientTab from "./cardModules/cardEditorTabs/ClientTab/ClientTab.tsx";
import ModulesType from "./types.tsx";
import ProductAndServiceTab from "./cardModules/cardEditorTabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
import EmployeesTab from "./cardModules/cardEditorTabs/EmployeesTab/EmployeesTab.tsx";
import ShippingTab from "./cardModules/cardEditorTabs/ShippingTab/ShippingTab.tsx";
import ManagerTab from "./cardModules/cardEditorTabs/ManagersTab/ManagersTab.tsx";
const connectModules = (modules: ModulesType) => {
modules[ModuleNames.CLIENTS].tab = <ClientTab />;
modules[ModuleNames.SERVICES_AND_PRODUCTS].tab = <ProductAndServiceTab />;
modules[ModuleNames.EMPLOYEES].tab = <EmployeesTab />;
modules[ModuleNames.SHIPMENT].tab = <ShippingTab />;
modules[ModuleNames.MANAGERS].tab = <ManagerTab />;
return modules;
};
export default connectModules;

View File

@@ -0,0 +1,54 @@
import React, { createContext, FC, useContext, useEffect, useState } from "react";
import { useProjectsContext } from "../../contexts/ProjectsContext.tsx";
import { MODULES } from "../modules.tsx";
import { Module } from "../types.tsx";
type ModulesContextState = {
modules: Module[];
};
const ModulesContext = createContext<ModulesContextState | undefined>(
undefined,
);
const useModulesContextState = () => {
const { selectedProject } = useProjectsContext();
const [modules, setModules] = useState<Module[]>([]);
useEffect(() => {
const modules = selectedProject?.modules ?? [];
const projectModules = modules.map(module => {
return MODULES[module.key];
}) ?? [];
setModules(projectModules);
}, [selectedProject?.id]);
return {
modules,
};
};
type CardPageContextProviderProps = {
children: React.ReactNode;
};
export const ModulesContextProvider: FC<CardPageContextProviderProps> = ({ children }) => {
const state = useModulesContextState();
return (
<ModulesContext.Provider value={state}>
{children}
</ModulesContext.Provider>
);
};
export const useModulesContext = () => {
const context = useContext(ModulesContext);
if (!context) {
throw new Error(
"useModulesContext must be used within a ModulesContextProvider",
);
}
return context;
};

21
src/modules/modules.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { IconUser, IconBox, IconCubeSend, IconUsersGroup, IconUserCog } from "@tabler/icons-react"
import ModulesType from "./types.tsx";
import connectModules from "./connectModules.tsx";
export enum ModuleNames {
CLIENTS = "clients",
SERVICES_AND_PRODUCTS = "servicesAndProducts",
SHIPMENT = "shipment",
EMPLOYEES = "employees",
MANAGERS = "managers"
}
const modules: ModulesType = {
[ModuleNames.CLIENTS]: { info: { label: "Клиенты", key: "clients", icon: <IconUser /> } },
[ModuleNames.SERVICES_AND_PRODUCTS]: { info: { label: "Товары и услуги", key: "servicesAndProducts", icon: <IconBox /> } },
[ModuleNames.SHIPMENT]: { info: { label: "Отгрузка", key: "shipment", icon: <IconCubeSend /> } },
[ModuleNames.EMPLOYEES]: { info: { label: "Сотрудники", key: "employees", icon: <IconUsersGroup /> } },
[ModuleNames.MANAGERS]: { info: { label: "Менеджер", key: "managers", icon: <IconUserCog /> } }
};
export const MODULES = connectModules(modules);

View File

@@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var axios_1 = require("axios");
var fs = require("fs");
// endregion
var OUTPUT_FILE = "./src/modules/modules.tsx";
function camelToConstCase(camelStr) {
return camelStr
.replace(/([a-z])([A-Z])/g, "$1_$2")
.toUpperCase();
}
var writeToFile = function (data) {
try {
fs.writeFileSync(OUTPUT_FILE, data.trim());
console.log("File successfully generated.");
}
catch (error) {
console.error(error);
}
};
var getImports = function (modules) {
var prefix = "import { ";
var postfix = " } from \"@tabler/icons-react\"\n" +
"import ModulesType from \"./types.tsx\";\n" +
"import connectModules from \"./connectModules.tsx\";";
var filteredModules = modules.filter(function (module) { return module.iconName; });
var icons = filteredModules.map(function (module) { return module.iconName; }).join(", ");
return prefix + icons + postfix;
};
var getModuleNames = function (modules) {
return modules.map(function (module) {
return "".concat(camelToConstCase(module.key), " = \"").concat(module.key, "\"");
}).join(",\n\t");
};
var getModules = function (modules) {
return modules.map(function (module) {
var iconStr = "null";
if (module.iconName) {
iconStr = "<" + module.iconName + " />";
}
return "[ModuleNames.".concat(camelToConstCase(module.key), "]: { info: { label: \"").concat(module.label, "\", key: \"").concat(module.key, "\", icon: ").concat(iconStr, " } }");
}).join(",\n\t");
};
var generateRows = function (modules) {
var imports = getImports(modules);
var moduleNames = "\n\nexport enum ModuleNames {\n\t".concat(getModuleNames(modules), "\n}\n");
var modulesStr = "\nconst modules: ModulesType = {\n\t".concat(getModules(modules), "\n};\n");
var connectModules = "\nexport const MODULES = connectModules(modules);";
var result = imports + moduleNames + modulesStr + connectModules;
writeToFile(result);
};
var modulesFileGen = function () {
console.log("Start file generation...");
axios_1.default
.get("http://127.0.0.1:8000/project/modules")
.then(function (response) {
generateRows(response.data.modules);
})
.catch(function (err) { return console.log(err); });
};
modulesFileGen();

View File

@@ -0,0 +1,86 @@
import axios, { AxiosResponse } from "axios";
import * as fs from 'fs';
// region Types
type Module = {
id: number;
key: string;
label: string;
iconName?: string;
isDeleted: boolean;
}
type ModulesResponse = {
modules: Module[];
}
// endregion
const OUTPUT_FILE = "./src/modules/modules.tsx";
function camelToConstCase(camelStr: string): string {
return camelStr
.replace(/([a-z])([A-Z])/g, "$1_$2")
.toUpperCase();
}
const writeToFile = (data: string) => {
try {
fs.writeFileSync(OUTPUT_FILE, data.trim());
console.log("File successfully generated.");
} catch (error) {
console.error(error);
}
};
const getImports = (modules: Module[]): string => {
const prefix = "import { ";
const postfix = " } from \"@tabler/icons-react\"\n" +
"import ModulesType from \"./types.tsx\";\n" +
"import connectModules from \"./connectModules.tsx\";";
const filteredModules = modules.filter(module => module.iconName);
const icons = filteredModules.map((module: Module) => module.iconName).join(", ");
return prefix + icons + postfix;
};
const getModuleNames = (modules: Module[]) => {
return modules.map(module => {
return `${camelToConstCase(module.key)} = "${module.key}"`;
}).join(",\n\t");
};
const getModules = (modules: Module[]) => {
return modules.map(module => {
let iconStr = "null";
if (module.iconName) {
iconStr = "<" + module.iconName + " />";
}
return `[ModuleNames.${camelToConstCase(module.key)}]: { info: { label: "${module.label}", key: "${module.key}", icon: ${iconStr} } }`;
}).join(",\n\t");
};
const generateRows = (modules: Module[]) => {
const imports = getImports(modules);
const moduleNames = `\n\nexport enum ModuleNames {\n\t${getModuleNames(modules)}\n}\n`;
const modulesStr = `\nconst modules: ModulesType = {\n\t${getModules(modules)}\n};\n`;
const connectModules = "\nexport const MODULES = connectModules(modules);";
const result: string = imports + moduleNames + modulesStr + connectModules;
writeToFile(result);
};
const modulesFileGen = () => {
console.log("Start file generation...");
axios
.get("http://127.0.0.1:8000/project/modules")
.then((response: AxiosResponse<ModulesResponse>) => {
generateRows(response.data.modules);
})
.catch(err => console.log(err));
};
modulesFileGen();

16
src/modules/types.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { ReactNode } from "react";
export type Module = {
info: {
label: string;
key: string;
icon: ReactNode;
},
tab?: ReactNode;
}
type Modules = {
[key: string]: Module;
}
export default Modules;

View File

@@ -1,4 +1,4 @@
import { ProjectSchema } from "../../../client"; import { ProjectSchema } from "../../client";
export enum Modules { export enum Modules {
SERVICES_AND_PRODUCTS = "servicesAndProducts", SERVICES_AND_PRODUCTS = "servicesAndProducts",
@@ -6,7 +6,6 @@ export enum Modules {
EMPLOYEES = "employees", EMPLOYEES = "employees",
CLIENTS = "clients", CLIENTS = "clients",
MANAGERS = "managers", MANAGERS = "managers",
MEGA_MODULE = "hui",
} }
const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => { const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => {

View File

@@ -1,6 +1,6 @@
import { useParams } from "@tanstack/react-router"; import { useParams } from "@tanstack/react-router";
import { CardPageContextProvider, useCardPageContext } from "../../CardsPage/contexts/CardPageContext.tsx"; import { CardPageContextProvider, useCardPageContext } from "../../CardsPage/contexts/CardPageContext.tsx";
import ProductAndServiceTab from "../../CardsPage/tabs/ProductAndServiceTab/ProductAndServiceTab.tsx"; import ProductAndServiceTab from "../../../modules/cardModules/cardEditorTabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
import React, { FC, useEffect } from "react"; import React, { FC, useEffect } from "react";
import { CardService } from "../../../client"; import { CardService } from "../../../client";

View File

@@ -1,15 +1,12 @@
import { Box, Drawer, rem, Tabs } from "@mantine/core"; import { Box, Drawer, rem, Tabs } from "@mantine/core";
import { FC, ReactNode, useEffect } from "react"; import { FC, ReactNode, useEffect } from "react";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
import { IconBox, IconCalendarUser, IconCubeSend, IconSettings, IconUser, IconUsersGroup } from "@tabler/icons-react"; import { IconCalendarUser, IconSettings } from "@tabler/icons-react";
import CardStatusChangeTable from "../../components/CardStatusChangeTable/CardStatusChangeTable.tsx"; import CardStatusChangeTable from "./tabs/CardStatusChangeTable/CardStatusChangeTable.tsx";
import GeneralTab from "../../tabs/GeneralTab/GeneralTab.tsx"; import GeneralTab from "./tabs/GeneralTab/GeneralTab.tsx";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import ShippingTab from "../../tabs/ShippingTab/ShippingTab.tsx"; import { useModulesContext } from "../../../../modules/context/ModulesContext.tsx";
import EmployeesTab from "../../tabs/EmployeesTab/EmployeesTab.tsx";
import ClientTab from "../../tabs/ClientTab/ClientTab.tsx";
const useCardStatusChangeState = () => { const useCardStatusChangeState = () => {
const { selectedCard } = useCardPageContext(); const { selectedCard } = useCardPageContext();
@@ -36,8 +33,7 @@ const CardEditDrawer: FC = () => {
const { isVisible, onClose } = useCardEditDrawerState(); const { isVisible, onClose } = useCardEditDrawerState();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { selectedCard } = useCardPageContext(); const { modules } = useModulesContext();
const modules = new Set<string>(selectedCard?.board.project.modules.map(module => module.key));
useEffect(() => { useEffect(() => {
if (isVisible) return; if (isVisible) return;
@@ -62,23 +58,27 @@ const CardEditDrawer: FC = () => {
); );
}; };
const getTab = ( const getTabs = () => {
key: string, const moduleTabs = modules.map(module => (
icon: ReactNode, <Tabs.Tab
label: string, value={module.info.key}
enablingModules: string[] | null = null, // Show if at least one of modules is in project leftSection={module.info.icon}
) => { >
if (!enablingModules) { {module.info.label}
enablingModules = [key]; </Tabs.Tab>
} ));
if (!enablingModules.some(key => modules.has(key))) return;
return ( return (
<Tabs.Tab <>{moduleTabs}</>
value={key} );
leftSection={icon}> };
{label}
</Tabs.Tab> const getTabPanels = () => {
const moduleTabPanels = modules.map(
module => getTabPanel(module.info.key, module.tab),
);
return (
<>{moduleTabPanels}</>
); );
}; };
@@ -115,18 +115,12 @@ const CardEditDrawer: FC = () => {
leftSection={<IconCalendarUser />}> leftSection={<IconCalendarUser />}>
История История
</Tabs.Tab> </Tabs.Tab>
{getTab("clients", <IconUser />, "Клиент", ["servicesAndProducts", "clients"])} {getTabs()}
{getTab("servicesAndProducts", <IconBox />, "Товары и услуги")}
{getTab("shipment", <IconCubeSend />, "Отгрузка")}
{getTab("employees", <IconUsersGroup />, "Исполнители")}
</Tabs.List> </Tabs.List>
{getTabPanel("general", <GeneralTab />)} {getTabPanel("general", <GeneralTab />)}
{getTabPanel("clients", <ClientTab />)}
{getTabPanel("history", <CardEditDrawerStatusChangeTable />)} {getTabPanel("history", <CardEditDrawerStatusChangeTable />)}
{getTabPanel("servicesAndProducts", <ProductAndServiceTab />)} {getTabPanels()}
{getTabPanel("shipment", <ShippingTab />)}
{getTabPanel("employees", <EmployeesTab />)}
</Tabs> </Tabs>
</Drawer> </Drawer>
); );

View File

@@ -1,6 +1,6 @@
import { CardStatusHistorySchema } from "../../../../client"; import { CardStatusHistorySchema } from "../../../../../../client";
import { useDealStatusChangeTableColumns } from "./columns.tsx"; import { useDealStatusChangeTableColumns } from "./columns.tsx";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx"; import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
import { FC } from "react"; import { FC } from "react";
type Props = { type Props = {

View File

@@ -1,6 +1,6 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table"; import { MRT_ColumnDef } from "mantine-react-table";
import { CardStatusHistorySchema } from "../../../../client"; import { CardStatusHistorySchema } from "../../../../../../client";
import { Spoiler, Text } from "@mantine/core"; import { Spoiler, Text } from "@mantine/core";
export const useDealStatusChangeTableColumns = () => { export const useDealStatusChangeTableColumns = () => {

View File

@@ -1,5 +1,5 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx"; import { useCardPageContext } from "../../../../contexts/CardPageContext.tsx";
import { import {
Button, Button,
Checkbox, Checkbox,
@@ -14,31 +14,17 @@ import {
TextInput, TextInput,
} from "@mantine/core"; } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { import { CardSchema, CardService, ClientService, ProjectSchema, StatusSchema } from "../../../../../../client";
CardSchema,
CardService,
ClientService,
ProjectSchema,
ShippingWarehouseSchema,
StatusSchema,
} from "../../../../client";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
import { notifications } from "../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../../shared/lib/notifications.ts";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import ShippingWarehouseAutocomplete import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
from "../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
import { ButtonCopyControlled } from "../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
import { useClipboard } from "@mantine/hooks"; import { useClipboard } from "@mantine/hooks";
import ManagerSelect from "../../../../components/ManagerSelect/ManagerSelect.tsx"; import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
import ProjectSelect from "../../../../components/ProjectSelect/ProjectSelect.tsx"; import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
import BoardSelect from "../../../../components/BoardSelect/BoardSelect.tsx"; import CardStatusSelect from "../../../../../../components/DealStatusSelect/CardStatusSelect.tsx";
import DealStatusSelect from "../../../../components/DealStatusSelect/DealStatusSelect.tsx"; import CardAttributeFields from "../../../../../../components/CardAttributeFields/CardAttributeFields.tsx";
import CardAttributeFields from "../../../../components/CardAttributeFields/CardAttributeFields.tsx"; import getAttributesFromCard from "../../../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
import getAttributesFromCard from "../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
import isModuleInProject, { Modules } from "../../utils/isModuleInProject.ts";
import PaymentLinkButton from "./components/PaymentLinkButton.tsx";
import PrintDealBarcodesButton from "./components/PrintDealBarcodesButton.tsx";
import ClientSelect from "../../../../components/Selects/ClientSelect/ClientSelect.tsx";
type Props = { type Props = {
card: CardSchema; card: CardSchema;
@@ -56,10 +42,6 @@ const Content: FC<Props> = ({ card }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [project, setProject] = useState<ProjectSchema | null>(card.board.project); const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, card.board.project);
const isManagerIncluded = isModuleInProject(Modules.MANAGERS, card.board.project);
const isClientIncluded = isModuleInProject(Modules.CLIENTS, card.board.project);
const getInitialValues = (card: CardSchema): CardGeneralFormType => { const getInitialValues = (card: CardSchema): CardGeneralFormType => {
return { return {
...card, ...card,
@@ -99,7 +81,6 @@ const Content: FC<Props> = ({ card }) => {
statusId: values.status.id, statusId: values.status.id,
boardId: values.board.id, boardId: values.board.id,
clientId: values.client?.id ?? null, clientId: values.client?.id ?? null,
shippingWarehouse: values.shippingWarehouse?.toString(),
attributes, attributes,
}, },
}, },
@@ -107,7 +88,6 @@ const Content: FC<Props> = ({ card }) => {
notifications.guess(ok, { message }); notifications.guess(ok, { message });
if (!ok) return; if (!ok) return;
CardService.getCardById({ cardId: card.id }).then(data => { CardService.getCardById({ cardId: card.id }).then(data => {
console.log(data);
setSelectedCard(data); setSelectedCard(data);
initialValues = getInitialValues(data); initialValues = getInitialValues(data);
form.setValues(initialValues); form.setValues(initialValues);
@@ -131,21 +111,7 @@ const Content: FC<Props> = ({ card }) => {
await updateClientInfo(values); await updateClientInfo(values);
} }
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse) await updateCardInfo(values);
? values.shippingWarehouse.name
: values.shippingWarehouse;
await updateCardInfo(
{
...values,
shippingWarehouse,
},
);
};
const isShippingWarehouse = (
value: ShippingWarehouseSchema | string | null | undefined,
): value is ShippingWarehouseSchema => {
return !!value && !["string"].includes(typeof value);
}; };
const onCopyGuestUrlClick = () => { const onCopyGuestUrlClick = () => {
@@ -196,7 +162,7 @@ const Content: FC<Props> = ({ card }) => {
{...form.getInputProps("board")} {...form.getInputProps("board")}
label={"Доска"} label={"Доска"}
/> />
<DealStatusSelect <CardStatusSelect
board={form.values.board} board={form.values.board}
{...form.getInputProps("status")} {...form.getInputProps("status")}
label={"Статус"} label={"Статус"}
@@ -211,49 +177,6 @@ const Content: FC<Props> = ({ card }) => {
placeholder={"Введите коментарий"} placeholder={"Введите коментарий"}
{...form.getInputProps("comment")} {...form.getInputProps("comment")}
/> />
{isClientIncluded && (
<ClientSelect
{...form.getInputProps("client")}
withLabel
/>
)}
{isServicesAndProductsIncluded && (
<ShippingWarehouseAutocomplete
placeholder={"Введите склад отгрузки"}
label={"Склад отгрузки"}
value={
isShippingWarehouse(
form.values.shippingWarehouse,
)
? form.values.shippingWarehouse
: undefined
}
onChange={event => {
if (isShippingWarehouse(event)) {
form.getInputProps(
"shippingWarehouse",
).onChange(event.name);
return;
}
form.getInputProps(
"shippingWarehouse",
).onChange(event);
}}
/>
)}
{isManagerIncluded && (
<ManagerSelect
placeholder={"Укажите менеджера"}
label={"Менеджер"}
{...form.getInputProps("manager")}
/>
)}
{isServicesAndProductsIncluded && (
<Checkbox
label={"Учет выручки с услуг в статистике"}
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
/>
)}
{project && ( {project && (
<CardAttributeFields <CardAttributeFields
project={project} project={project}
@@ -273,36 +196,16 @@ const Content: FC<Props> = ({ card }) => {
align={"center"} align={"center"}
gap={rem(10)} gap={rem(10)}
justify={"center"}> justify={"center"}>
<Flex <ButtonCopyControlled
gap={rem(10)} onCopyClick={onCopyGuestUrlClick}
align={"center"} onCopiedLabel={
justify={"space-between"}> "Ссылка скопирована в буфер обмена"
{isServicesAndProductsIncluded && ( }
<PrintDealBarcodesButton card={card} /> copied={clipboard.copied}
)} >
<Flex gap={rem(10)}> Ссылка на редактирование
{isServicesAndProductsIncluded && ( </ButtonCopyControlled>
<PaymentLinkButton card={card} />
)}
<ButtonCopyControlled
onCopyClick={onCopyGuestUrlClick}
onCopiedLabel={
"Ссылка скопирована в буфер обмена"
}
copied={clipboard.copied}
>
Ссылка на редактирование
</ButtonCopyControlled>
</Flex>
</Flex>
<Flex gap={rem(10)}> <Flex gap={rem(10)}>
{isServicesAndProductsIncluded && (
<Checkbox
label={"Оплачен"}
checked={card.billRequest?.paid || card.group?.billRequest?.paid || false}
disabled
/>
)}
<Checkbox <Checkbox
label={"Завершена"} label={"Завершена"}
{...form.getInputProps("isCompleted", { type: "checkbox" })} {...form.getInputProps("isCompleted", { type: "checkbox" })}

View File

@@ -2,7 +2,7 @@ import { FC } from "react";
import { CardProductSchema, ProductSchema } from "../../../../../../client"; import { CardProductSchema, ProductSchema } from "../../../../../../client";
import { Image, rem, Text, Title } from "@mantine/core"; import { Image, rem, Text, Title } from "@mantine/core";
import { isNil } from "lodash"; import { isNil } from "lodash";
import { ProductFieldNames } from "../../../../tabs/ProductAndServiceTab/components/ProductView/ProductView.tsx"; import { ProductFieldNames } from "../../../../../../modules/cardModules/cardEditorTabs/ProductAndServiceTab/components/ProductView/ProductView.tsx";
import ProductServicesTable from "../tables/ProductServicesTable/ProductServicesTable.tsx"; import ProductServicesTable from "../tables/ProductServicesTable/ProductServicesTable.tsx";
import styles from "./ProductPreview.module.css"; import styles from "./ProductPreview.module.css";

View File

@@ -2,7 +2,7 @@ import { useForm } from "@mantine/form";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { BaseMarketplaceSchema } from "../../../../../client"; import { BaseMarketplaceSchema } from "../../../../../client";
import { useCardSummariesFull } from "../../../hooks/useCardSummaries.tsx"; import { useCardSummariesFull } from "../../../hooks/useCardSummaries.tsx";
import isModuleInProject, { Modules } from "../../../utils/isModuleInProject.ts"; import isModuleInProject, { Modules } from "../../../../../modules/utils/isModuleInProject.ts";
type State = { type State = {
idOrName: string | null; idOrName: string | null;

View File

@@ -3,7 +3,7 @@ import { Flex, Modal, NumberInput, rem } from "@mantine/core";
import { UseFormReturnType } from "@mantine/form"; import { UseFormReturnType } from "@mantine/form";
import { CardsPageState } from "../hooks/useCardsPageState.tsx"; import { CardsPageState } from "../hooks/useCardsPageState.tsx";
import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx"; import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx";
import DealStatusSelect from "../../../components/DealStatusSelect/DealStatusSelect.tsx"; import CardStatusSelect from "../../../components/DealStatusSelect/CardStatusSelect.tsx";
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx"; import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx"; import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
@@ -25,7 +25,7 @@ const CardsTableFiltersModal = ({ form, projects }: Props) => {
<IconFilter /> <IconFilter />
Фильтры Фильтры
</InlineButton> </InlineButton>
<Modal title={"Фильтры для сделок"} opened={opened} onClose={close}> <Modal title={"Фильтры"} opened={opened} onClose={close}>
<Flex <Flex
direction={"column"} direction={"column"}
gap={rem(10)} gap={rem(10)}
@@ -49,7 +49,7 @@ const CardsTableFiltersModal = ({ form, projects }: Props) => {
{...form.getInputProps("board")} {...form.getInputProps("board")}
clearable clearable
/> />
<DealStatusSelect <CardStatusSelect
board={form.values.board} board={form.values.board}
{...form.getInputProps("status")} {...form.getInputProps("status")}
clearable clearable

View File

@@ -1,103 +0,0 @@
import { Button, Fieldset, Flex, rem, Stack, Textarea, TextInput } from "@mantine/core";
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
import { useForm } from "@mantine/form";
import { CardGeneralFormType } from "../GeneralTab/GeneralTab.tsx";
import { CardSchema, CardService, ClientService } from "../../../../client";
import { isEqual } from "lodash";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { useQueryClient } from "@tanstack/react-query";
const ClientTab = () => {
const { selectedCard: card, setSelectedCard } = useCardPageContext();
const initialValues: CardGeneralFormType = card as CardSchema;
const queryClient = useQueryClient();
if (!card?.client) return;
const form = useForm<CardGeneralFormType>(
{
initialValues: initialValues,
validate: {
name: (value: string) => value.length > 0 ? null : "Название сделки не может быть пустым",
},
},
);
const hasChanges = !isEqual(form.values, initialValues);
const updateClientInfo = async (values: CardGeneralFormType) => {
if (!values.client) return;
return ClientService.updateClient({
requestBody: {
data: values.client,
},
}).then(({ ok, message }) => notifications.guess(ok, { message }));
};
const update = async () => {
return CardService.getCardById({ cardId: form.values.id }).then(data => {
setSelectedCard(data);
form.setInitialValues(data);
queryClient.invalidateQueries({
queryKey: ["getCardSummaries"],
});
});
};
const handleSave = () => {
updateClientInfo(form.values).then(async () => {
await update();
});
};
const handleCancel = () => {
form.setInitialValues(initialValues);
};
return (
<Flex direction={"column"} flex={1} gap={rem(10)}>
<Flex flex={1}>
<Fieldset legend={"Клиент"} flex={1}>
<Stack gap={rem(10)}>
<TextInput
disabled
placeholder={"Название"}
label={"Название"}
value={card?.client.name}
/>
<TextInput
placeholder={"Введите телефон"}
label={"Телефон клиента"}
{...form.getInputProps("client.details.phoneNumber")}
/>
<TextInput
placeholder={"Введите email"}
label={"Email"}
{...form.getInputProps("client.details.email")}
/>
<TextInput
placeholder={"Введите телеграм"}
label={"Телеграм"}
{...form.getInputProps("client.details.telegram")}
/>
<TextInput
placeholder={"Введите ИНН"}
label={"ИНН"}
{...form.getInputProps("client.details.inn")}
/>
<Textarea
placeholder={"Введите комментарий"}
label={"Комментарий"}
{...form.getInputProps("client.comment")}
/>
</Stack>
</Fieldset>
</Flex>
<Flex
gap={rem(10)}
justify={"flex-end"}
display={!hasChanges ? "none" : "flex"}
>
<Button onClick={handleCancel} variant={"default"}>Отмена</Button>
<Button onClick={handleSave} variant={"default"}>Сохранить</Button>
</Flex>
</Flex>
);
};
export default ClientTab;

View File

@@ -13,7 +13,7 @@ import {
import { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx"; import { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx";
import BaseMarketplaceSelect import BaseMarketplaceSelect
from "../../../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx"; from "../../../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import DealStatusSelect from "../../../../../../components/DealStatusSelect/DealStatusSelect.tsx"; import CardStatusSelect from "../../../../../../components/DealStatusSelect/CardStatusSelect.tsx";
import { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; import { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
import ManagerSelect from "../../../../../../components/ManagerSelect/ManagerSelect.tsx"; import ManagerSelect from "../../../../../../components/ManagerSelect/ManagerSelect.tsx";
import TransactionTagSelect from "../../../../components/ExpenseTagSelect/TransactionTagSelect.tsx"; import TransactionTagSelect from "../../../../components/ExpenseTagSelect/TransactionTagSelect.tsx";
@@ -107,7 +107,7 @@ export const Filters = (props: FiltersProps) => {
/> />
} }
{props.statusSelectProps && {props.statusSelectProps &&
<DealStatusSelect <CardStatusSelect
board={props.boardSelectProps?.value ?? null} board={props.boardSelectProps?.value ?? null}
{...props.statusSelectProps} {...props.statusSelectProps}
clearable clearable

View File

@@ -1,12 +1,12 @@
/* prettier-ignore-start */
/* eslint-disable */ /* eslint-disable */
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// This file is auto-generated by TanStack Router // This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
@@ -36,16 +36,19 @@ const IndexLazyImport = createFileRoute('/')()
// Create/Update Routes // Create/Update Routes
const TestLazyRoute = TestLazyImport.update({ const TestLazyRoute = TestLazyImport.update({
id: '/test',
path: '/test', path: '/test',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/test.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/test.lazy').then((d) => d.Route))
const StatisticsLazyRoute = StatisticsLazyImport.update({ const StatisticsLazyRoute = StatisticsLazyImport.update({
id: '/statistics',
path: '/statistics', path: '/statistics',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/statistics.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/statistics.lazy').then((d) => d.Route))
const ShippingwarehousesLazyRoute = ShippingwarehousesLazyImport.update({ const ShippingwarehousesLazyRoute = ShippingwarehousesLazyImport.update({
id: '/shipping_warehouses',
path: '/shipping_warehouses', path: '/shipping_warehouses',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => } as any).lazy(() =>
@@ -53,66 +56,79 @@ const ShippingwarehousesLazyRoute = ShippingwarehousesLazyImport.update({
) )
const ServicesLazyRoute = ServicesLazyImport.update({ const ServicesLazyRoute = ServicesLazyImport.update({
id: '/services',
path: '/services', path: '/services',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/services.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/services.lazy').then((d) => d.Route))
const ResiduesLazyRoute = ResiduesLazyImport.update({ const ResiduesLazyRoute = ResiduesLazyImport.update({
id: '/residues',
path: '/residues', path: '/residues',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/residues.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/residues.lazy').then((d) => d.Route))
const ReceiptLazyRoute = ReceiptLazyImport.update({ const ReceiptLazyRoute = ReceiptLazyImport.update({
id: '/receipt',
path: '/receipt', path: '/receipt',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/receipt.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/receipt.lazy').then((d) => d.Route))
const ProductsLazyRoute = ProductsLazyImport.update({ const ProductsLazyRoute = ProductsLazyImport.update({
id: '/products',
path: '/products', path: '/products',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/products.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/products.lazy').then((d) => d.Route))
const MarketplacesLazyRoute = MarketplacesLazyImport.update({ const MarketplacesLazyRoute = MarketplacesLazyImport.update({
id: '/marketplaces',
path: '/marketplaces', path: '/marketplaces',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/marketplaces.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/marketplaces.lazy').then((d) => d.Route))
const LoginLazyRoute = LoginLazyImport.update({ const LoginLazyRoute = LoginLazyImport.update({
id: '/login',
path: '/login', path: '/login',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/login.lazy').then((d) => d.Route))
const LeadsLazyRoute = LeadsLazyImport.update({ const LeadsLazyRoute = LeadsLazyImport.update({
id: '/leads',
path: '/leads', path: '/leads',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/leads.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/leads.lazy').then((d) => d.Route))
const ClientsLazyRoute = ClientsLazyImport.update({ const ClientsLazyRoute = ClientsLazyImport.update({
id: '/clients',
path: '/clients', path: '/clients',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/clients.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/clients.lazy').then((d) => d.Route))
const BarcodeLazyRoute = BarcodeLazyImport.update({ const BarcodeLazyRoute = BarcodeLazyImport.update({
id: '/barcode',
path: '/barcode', path: '/barcode',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/barcode.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/barcode.lazy').then((d) => d.Route))
const AdminLazyRoute = AdminLazyImport.update({ const AdminLazyRoute = AdminLazyImport.update({
id: '/admin',
path: '/admin', path: '/admin',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/admin.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/admin.lazy').then((d) => d.Route))
const IndexLazyRoute = IndexLazyImport.update({ const IndexLazyRoute = IndexLazyImport.update({
id: '/',
path: '/', path: '/',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route)) } as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route))
const LeadsDealIdRoute = LeadsDealIdImport.update({ const LeadsDealIdRoute = LeadsDealIdImport.update({
id: '/$dealId',
path: '/$dealId', path: '/$dealId',
getParentRoute: () => LeadsLazyRoute, getParentRoute: () => LeadsLazyRoute,
} as any) } as any)
const DealsDealIdRoute = DealsDealIdImport.update({ const DealsDealIdRoute = DealsDealIdImport.update({
id: '/deals/$dealId',
path: '/deals/$dealId', path: '/deals/$dealId',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
@@ -406,8 +422,6 @@ export const routeTree = rootRoute
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>()
/* prettier-ignore-end */
/* ROUTE_MANIFEST_START /* ROUTE_MANIFEST_START
{ {
"routes": { "routes": {

View File

@@ -1,6 +1,6 @@
import { ProductSchema } from "../client"; import { ProductSchema } from "../client";
import { isNil } from "lodash"; import { isNil } from "lodash";
import { ProductFieldNames } from "../pages/CardsPage/tabs/ProductAndServiceTab/components/ProductView/ProductView.tsx"; import { ProductFieldNames } from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/components/ProductView/ProductView.tsx";
import UseObjectState from "./UseObjectState.ts"; import UseObjectState from "./UseObjectState.ts";
import { CRUDTableProps } from "./CRUDTable.tsx"; import { CRUDTableProps } from "./CRUDTable.tsx";
import { MRT_RowData } from "mantine-react-table"; import { MRT_RowData } from "mantine-react-table";