feat: clients page

This commit is contained in:
2025-10-04 18:18:17 +04:00
parent f641e9ef8c
commit e9bfd39ab4
20 changed files with 1141 additions and 3 deletions

View File

@ -0,0 +1,23 @@
import { FC } from "react";
import { Group, TextInput } from "@mantine/core";
import { useClientsContext } from "@/app/clients/contexts/ClientsContext";
import useClientsActions from "@/app/clients/hooks/useClientsActions";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
const ClientDesktopHeader: FC = () => {
const { search, setSearch } = useClientsContext();
const { onCreateClick } = useClientsActions();
return (
<Group gap={"xs"}>
<InlineButton onClick={onCreateClick}>Создать клиента</InlineButton>
<TextInput
placeholder={"Поиск"}
value={search}
onChange={e => setSearch(e.target.value)}
/>
</Group>
);
};
export default ClientDesktopHeader;

View File

@ -0,0 +1,31 @@
import { FC } from "react";
import { Flex, TextInput } from "@mantine/core";
import { useClientsContext } from "@/app/clients/contexts/ClientsContext";
import useClientsActions from "@/app/clients/hooks/useClientsActions";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
const ClientMobileHeader: FC = () => {
const { search, setSearch } = useClientsContext();
const { onCreateClick } = useClientsActions();
return (
<Flex
gap={"xs"}
px={"xs"}
pt={"xs"}>
<InlineButton
w={"100%"}
onClick={onCreateClick}>
Создать клиента
</InlineButton>
<TextInput
w={"100%"}
placeholder={"Поиск"}
value={search}
onChange={e => setSearch(e.target.value)}
/>
</Flex>
);
};
export default ClientMobileHeader;

View File

@ -0,0 +1,32 @@
"use client";
import { FC } from "react";
import { useClientsTableColumns } from "@/app/clients/components/shared/ClientsTable/columns";
import { useClientsContext } from "@/app/clients/contexts/ClientsContext";
import useClientsActions from "@/app/clients/hooks/useClientsActions";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import useIsMobile from "@/hooks/utils/useIsMobile";
const ClientsTable: FC = () => {
const isMobile = useIsMobile();
const { clientsCrud, clients } = useClientsContext();
const { onUpdateClick } = useClientsActions();
const columns = useClientsTableColumns({
onDelete: clientsCrud.onDelete,
onChange: onUpdateClick,
});
return (
<BaseTable
withTableBorder
records={clients}
columns={columns}
verticalSpacing={"xs"}
mx={isMobile ? "xs" : 0}
groups={undefined}
/>
);
};
export default ClientsTable;

View File

@ -0,0 +1,58 @@
import { useMemo } from "react";
import { DataTableColumn } from "mantine-datatable";
import { Center } from "@mantine/core";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import { ClientSchema } from "@/lib/client";
type Props = {
onChange: (client: ClientSchema) => void;
onDelete: (client: ClientSchema) => void;
};
export const useClientsTableColumns = ({ onChange, onDelete }: Props) => {
return useMemo(
() =>
[
{
accessor: "actions",
title: <Center>Действия</Center>,
width: "0%",
render: client => (
<UpdateDeleteTableActions
onDelete={() => onDelete(client)}
onChange={() => onChange(client)}
/>
),
},
{
accessor: "name",
title: "Имя",
},
{
accessor: "details.telegram",
title: "Телеграм",
},
{
accessor: "details.email",
title: "Почта",
},
{
accessor: "details.phoneNumber",
title: "Телефон",
},
{
accessor: "details.inn",
title: "ИНН",
},
{
accessor: "companyName",
title: "Название компании",
},
{
accessor: "comment",
title: "Комментарий",
},
] as DataTableColumn<ClientSchema>[],
[onChange, onDelete]
);
};

View File

@ -0,0 +1,37 @@
"use client";
import { FC } from "react";
import { Stack } from "@mantine/core";
import ClientDesktopHeader from "@/app/clients/components/desktop/ClientDesktopHeader/ClientDesktopHeader";
import ClientsTable from "@/app/clients/components/shared/ClientsTable/ClientsTable";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
import useIsMobile from "@/hooks/utils/useIsMobile";
import ClientMobileHeader from "@/app/clients/components/mobile/ClientMobileHeader/ClientMobileHeader";
const PageBody: FC = () => {
const isMobile = useIsMobile();
return (
<Stack h={"100%"}>
{!isMobile && (
<PageBlock>
<ClientDesktopHeader />
</PageBlock>
)}
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
<Stack
gap={"xs"}
h={"100%"}>
{isMobile && <ClientMobileHeader />}
<div style={{ flex: 1, overflow: "auto" }}>
<ClientsTable />
</div>
</Stack>
</PageBlock>
</Stack>
);
};
export default PageBody;

View File

@ -0,0 +1,39 @@
"use client";
import { Dispatch, SetStateAction } from "react";
import {
ClientsCrud,
useClientsCrud,
} from "@/app/clients/hooks/useClientsCrud";
import useClientsFilter from "@/app/clients/hooks/useClientsFilter";
import useClientsList from "@/app/clients/hooks/useClientsList";
import { ClientSchema } from "@/lib/client";
import makeContext from "@/lib/contextFactory/contextFactory";
type ClientsContextState = {
clients: ClientSchema[];
refetchClients: () => void;
search: string;
setSearch: Dispatch<SetStateAction<string>>;
clientsCrud: ClientsCrud;
};
const useClientsContextState = (): ClientsContextState => {
const clientsList = useClientsList();
const { filteredClients, search, setSearch } =
useClientsFilter(clientsList);
const clientsCrud = useClientsCrud(clientsList);
return {
clients: filteredClients,
refetchClients: clientsList.refetch,
search,
setSearch,
clientsCrud,
};
};
export const [ClientsContextProvider, useClientsContext] =
makeContext<ClientsContextState>(useClientsContextState, "Clients");

View File

@ -0,0 +1,37 @@
import { modals } from "@mantine/modals";
import { useClientsContext } from "@/app/clients/contexts/ClientsContext";
import { ClientSchema } from "@/lib/client";
const useClientsActions = () => {
const { clientsCrud } = useClientsContext();
const onCreateClick = () => {
modals.openContextModal({
modal: "clientEditorModal",
title: "Создание клиента",
innerProps: {
onCreate: clientsCrud.onCreate,
isEditing: false,
},
});
};
const onUpdateClick = (client: ClientSchema) => {
modals.openContextModal({
modal: "clientEditorModal",
title: "Редактирование клиента",
innerProps: {
onChange: updates => clientsCrud.onUpdate(client.id, updates),
entity: client,
isEditing: true,
},
});
};
return {
onCreateClick,
onUpdateClick,
};
};
export default useClientsActions;

View File

@ -0,0 +1,45 @@
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
import {
ClientSchema,
CreateClientSchema,
UpdateClientSchema,
} from "@/lib/client";
import {
createClientMutation,
deleteClientMutation,
updateClientMutation,
} from "@/lib/client/@tanstack/react-query.gen";
type UseClientsProps = {
queryKey: any[];
};
export type ClientsCrud = {
onCreate: (client: CreateClientSchema) => void;
onUpdate: (clientId: number, client: UpdateClientSchema) => void;
onDelete: (client: ClientSchema) => void;
};
export const useClientsCrud = ({ queryKey }: UseClientsProps): ClientsCrud => {
return useCrudOperations<
ClientSchema,
UpdateClientSchema,
CreateClientSchema
>({
key: "getClients",
queryKey,
mutations: {
create: createClientMutation(),
update: updateClientMutation(),
delete: deleteClientMutation(),
},
getUpdateEntity: (old, update) => ({
...old,
details: update.details ?? old.details,
name: update.name ?? old.name,
companyName: update.companyName ?? old.companyName,
comment: update.comment ?? old.comment,
}),
getDeleteConfirmTitle: () => "Удаление клиента",
});
};

View File

@ -0,0 +1,46 @@
import { useEffect, useState } from "react";
import { useDebouncedValue } from "@mantine/hooks";
import { ClientSchema } from "@/lib/client";
type Props = {
clients: ClientSchema[];
};
const useClientsFilter = ({ clients }: Props) => {
const [search, setSearch] = useState<string>("");
const [debouncedSearch] = useDebouncedValue(search, 400);
const [filteredClients, setFilteredClients] = useState<ClientSchema[]>([]);
const filterClients = () => {
if (debouncedSearch.length === 0) {
setFilteredClients(clients);
return;
}
const loweredSearch = debouncedSearch.toLowerCase();
const filtered = clients.filter(
client =>
client.name.toLowerCase().includes(loweredSearch) ||
client.details?.inn?.includes(loweredSearch) ||
client.details?.email?.toLowerCase().includes(loweredSearch) ||
client.details?.telegram
?.toLowerCase()
.includes(loweredSearch) ||
client.details?.phoneNumber?.includes(loweredSearch) ||
client.companyName.toLowerCase().includes(loweredSearch)
);
setFilteredClients(filtered);
};
useEffect(() => {
filterClients();
}, [debouncedSearch, clients]);
return {
search,
setSearch,
filteredClients,
};
};
export default useClientsFilter;

View File

@ -0,0 +1,17 @@
import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
getClientsOptions,
getClientsQueryKey,
} from "@/lib/client/@tanstack/react-query.gen";
const useClientsList = () => {
const { data, refetch } = useQuery(getClientsOptions());
const clients = useMemo(() => data?.items ?? [], [data]);
const queryKey = getClientsQueryKey();
return { clients, refetch, queryKey };
};
export default useClientsList;

View File

@ -0,0 +1,105 @@
"use client";
import { Fieldset, Stack, Textarea, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import isValidInn from "@/app/clients/utils/isValidInn";
import {
ClientSchema,
CreateClientSchema,
UpdateClientSchema,
} from "@/lib/client";
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
type Props = CreateEditFormProps<
ClientSchema,
CreateClientSchema,
UpdateClientSchema
>;
const ClientEditorModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const initialValues = innerProps.isEditing
? innerProps.entity
: ({
name: "",
companyName: "",
details: {
telegram: "",
phoneNumber: "",
email: "",
inn: "",
},
comment: "",
} as CreateClientSchema);
const form = useForm({
initialValues,
validate: {
name: name =>
(!name || name.trim() === "") &&
"Необходимо ввести название клиента",
details: {
inn: inn => inn.length > 0 && !isValidInn(inn) && "Некорректный ИНН",
},
},
});
return (
<BaseFormModal
{...innerProps}
closeOnSubmit
form={form}
onClose={() => context.closeContextModal(id)}>
<Fieldset legend={"Основная информация"}>
<TextInput
required
label={"Название клиента"}
placeholder={"Введите название клиента"}
{...form.getInputProps("name")}
/>
</Fieldset>
<Fieldset legend={"Дополнительная информация"}>
<Stack gap={"xs"}>
<TextInput
label={"Телеграм"}
placeholder={"Введите телеграм"}
{...form.getInputProps("details.telegram")}
/>
<TextInput
label={"Номер телефона"}
placeholder={"Введите номер телефона"}
{...form.getInputProps("details.phoneNumber")}
/>
<TextInput
label={"Почта"}
placeholder={"Введите почту"}
{...form.getInputProps("details.email")}
/>
<TextInput
label={"ИНН"}
placeholder={"Введите ИНН"}
{...form.getInputProps("details.inn")}
/>
<TextInput
label={"Название компании"}
placeholder={"Введите название компании"}
{...form.getInputProps("companyName")}
/>
<Textarea
label={"Комментарий"}
placeholder={"Введите комментарий"}
{...form.getInputProps("comment")}
/>
</Stack>
</Fieldset>
</BaseFormModal>
);
};
export default ClientEditorModal;

22
src/app/clients/page.tsx Normal file
View File

@ -0,0 +1,22 @@
import { Suspense } from "react";
import { Center, Loader } from "@mantine/core";
import { ClientsContextProvider } from "@/app/clients/contexts/ClientsContext";
import PageContainer from "@/components/layout/PageContainer/PageContainer";
import PageBody from "@/app/clients/components/shared/PageBody/PageBody";
export default async function ClientsPage() {
return (
<Suspense
fallback={
<Center h="50vh">
<Loader size="lg" />
</Center>
}>
<PageContainer>
<ClientsContextProvider>
<PageBody />
</ClientsContextProvider>
</PageContainer>
</Suspense>
);
}

View File

@ -0,0 +1,5 @@
const isValidInn = (inn: string | null | undefined) => {
return inn && inn.match(/^(\d{12}|\d{10})$/);
};
export default isValidInn;

View File

@ -2,6 +2,7 @@ import {
IconColumns,
IconFileBarcode,
IconLayoutKanban,
IconUsers,
} from "@tabler/icons-react";
import { Box, Flex } from "@mantine/core";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
@ -16,6 +17,11 @@ const linksData = [
label: "Сделки",
href: "/deals",
},
{
icon: IconUsers,
label: "Клиенты",
href: "/clients",
},
{
icon: IconColumns,
label: "Услуги",

View File

@ -5,13 +5,13 @@ import { Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
import { HttpValidationError } from "@/lib/client";
import { sortByLexorank } from "@/utils/lexorank/sort";
import {
BaseEntity,
CreateMutationOptions,
DeleteMutationOptions,
UpdateMutationOptions,
} from "./types";
import { sortByLexorank } from "@/utils/lexorank/sort";
type CrudOperations<
TEntity,

View File

@ -13,6 +13,7 @@ import {
addKitToDealProduct,
createBarcodeTemplate,
createBoard,
createClient,
createDeal,
createDealProduct,
createDealProductService,
@ -25,6 +26,7 @@ import {
createStatus,
deleteBarcodeTemplate,
deleteBoard,
deleteClient,
deleteDeal,
deleteDealProduct,
deleteDealProductService,
@ -41,6 +43,7 @@ import {
getBarcodeTemplateSizes,
getBoards,
getBuiltInModules,
getClients,
getDealProducts,
getDeals,
getDealServices,
@ -53,6 +56,7 @@ import {
getStatusHistory,
updateBarcodeTemplate,
updateBoard,
updateClient,
updateDeal,
updateDealProduct,
updateDealProductService,
@ -78,6 +82,9 @@ import type {
CreateBoardData,
CreateBoardError,
CreateBoardResponse2,
CreateClientData,
CreateClientError,
CreateClientResponse2,
CreateDealData,
CreateDealError,
CreateDealProductData,
@ -114,6 +121,9 @@ import type {
DeleteBoardData,
DeleteBoardError,
DeleteBoardResponse2,
DeleteClientData,
DeleteClientError,
DeleteClientResponse2,
DeleteDealData,
DeleteDealError,
DeleteDealProductData,
@ -152,6 +162,7 @@ import type {
GetBarcodeTemplateSizesData,
GetBoardsData,
GetBuiltInModulesData,
GetClientsData,
GetDealProductsData,
GetDealsData,
GetDealsError,
@ -172,6 +183,9 @@ import type {
UpdateBoardData,
UpdateBoardError,
UpdateBoardResponse2,
UpdateClientData,
UpdateClientError,
UpdateClientResponse2,
UpdateDealData,
UpdateDealError,
UpdateDealProductData,
@ -865,6 +879,129 @@ export const getStatusHistoryOptions = (
});
};
export const getClientsQueryKey = (options?: Options<GetClientsData>) =>
createQueryKey("getClients", options);
/**
* Get Clients
*/
export const getClientsOptions = (options?: Options<GetClientsData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getClients({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getClientsQueryKey(options),
});
};
export const createClientQueryKey = (options: Options<CreateClientData>) =>
createQueryKey("createClient", options);
/**
* Create Client
*/
export const createClientOptions = (options: Options<CreateClientData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await createClient({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: createClientQueryKey(options),
});
};
/**
* Create Client
*/
export const createClientMutation = (
options?: Partial<Options<CreateClientData>>
): UseMutationOptions<
CreateClientResponse2,
AxiosError<CreateClientError>,
Options<CreateClientData>
> => {
const mutationOptions: UseMutationOptions<
CreateClientResponse2,
AxiosError<CreateClientError>,
Options<CreateClientData>
> = {
mutationFn: async localOptions => {
const { data } = await createClient({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Delete Product
*/
export const deleteClientMutation = (
options?: Partial<Options<DeleteClientData>>
): UseMutationOptions<
DeleteClientResponse2,
AxiosError<DeleteClientError>,
Options<DeleteClientData>
> => {
const mutationOptions: UseMutationOptions<
DeleteClientResponse2,
AxiosError<DeleteClientError>,
Options<DeleteClientData>
> = {
mutationFn: async localOptions => {
const { data } = await deleteClient({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Update Client
*/
export const updateClientMutation = (
options?: Partial<Options<UpdateClientData>>
): UseMutationOptions<
UpdateClientResponse2,
AxiosError<UpdateClientError>,
Options<UpdateClientData>
> => {
const mutationOptions: UseMutationOptions<
UpdateClientResponse2,
AxiosError<UpdateClientError>,
Options<UpdateClientData>
> = {
mutationFn: async localOptions => {
const { data } = await updateClient({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getBarcodeTemplatesQueryKey = (
options?: Options<GetBarcodeTemplatesData>
) => createQueryKey("getBarcodeTemplates", options);

View File

@ -15,6 +15,9 @@ import type {
CreateBoardData,
CreateBoardErrors,
CreateBoardResponses,
CreateClientData,
CreateClientErrors,
CreateClientResponses,
CreateDealData,
CreateDealErrors,
CreateDealProductData,
@ -51,6 +54,9 @@ import type {
DeleteBoardData,
DeleteBoardErrors,
DeleteBoardResponses,
DeleteClientData,
DeleteClientErrors,
DeleteClientResponses,
DeleteDealData,
DeleteDealErrors,
DeleteDealProductData,
@ -95,6 +101,8 @@ import type {
GetBoardsResponses,
GetBuiltInModulesData,
GetBuiltInModulesResponses,
GetClientsData,
GetClientsResponses,
GetDealProductsData,
GetDealProductsErrors,
GetDealProductsResponses,
@ -127,6 +135,9 @@ import type {
UpdateBoardData,
UpdateBoardErrors,
UpdateBoardResponses,
UpdateClientData,
UpdateClientErrors,
UpdateClientResponses,
UpdateDealData,
UpdateDealErrors,
UpdateDealProductData,
@ -167,6 +178,8 @@ import {
zCreateBarcodeTemplateResponse2,
zCreateBoardData,
zCreateBoardResponse2,
zCreateClientData,
zCreateClientResponse2,
zCreateDealData,
zCreateDealProductData,
zCreateDealProductResponse2,
@ -191,6 +204,8 @@ import {
zDeleteBarcodeTemplateResponse2,
zDeleteBoardData,
zDeleteBoardResponse2,
zDeleteClientData,
zDeleteClientResponse2,
zDeleteDealData,
zDeleteDealProductData,
zDeleteDealProductResponse2,
@ -223,6 +238,8 @@ import {
zGetBoardsResponse2,
zGetBuiltInModulesData,
zGetBuiltInModulesResponse,
zGetClientsData,
zGetClientsResponse2,
zGetDealProductsData,
zGetDealProductsResponse2,
zGetDealsData,
@ -247,6 +264,8 @@ import {
zUpdateBarcodeTemplateResponse2,
zUpdateBoardData,
zUpdateBoardResponse2,
zUpdateClientData,
zUpdateClientResponse2,
zUpdateDealData,
zUpdateDealProductData,
zUpdateDealProductResponse2,
@ -732,6 +751,106 @@ export const getStatusHistory = <ThrowOnError extends boolean = false>(
});
};
/**
* Get Clients
*/
export const getClients = <ThrowOnError extends boolean = false>(
options?: Options<GetClientsData, ThrowOnError>
) => {
return (options?.client ?? _heyApiClient).get<
GetClientsResponses,
unknown,
ThrowOnError
>({
requestValidator: async data => {
return await zGetClientsData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetClientsResponse2.parseAsync(data);
},
url: "/modules/clients/client/",
...options,
});
};
/**
* Create Client
*/
export const createClient = <ThrowOnError extends boolean = false>(
options: Options<CreateClientData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).post<
CreateClientResponses,
CreateClientErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zCreateClientData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zCreateClientResponse2.parseAsync(data);
},
url: "/modules/clients/client/",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Delete Product
*/
export const deleteClient = <ThrowOnError extends boolean = false>(
options: Options<DeleteClientData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).delete<
DeleteClientResponses,
DeleteClientErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zDeleteClientData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zDeleteClientResponse2.parseAsync(data);
},
url: "/modules/clients/client/{pk}",
...options,
});
};
/**
* Update Client
*/
export const updateClient = <ThrowOnError extends boolean = false>(
options: Options<UpdateClientData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).patch<
UpdateClientResponses,
UpdateClientErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zUpdateClientData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUpdateClientResponse2.parseAsync(data);
},
url: "/modules/clients/client/{pk}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Get Barcode Templates
*/

View File

@ -171,6 +171,51 @@ export type BuiltInModuleTabSchema = {
device: string;
};
/**
* ClientDetailsSchema
*/
export type ClientDetailsSchema = {
/**
* Telegram
*/
telegram: string;
/**
* Phonenumber
*/
phoneNumber: string;
/**
* Inn
*/
inn: string;
/**
* Email
*/
email: string;
};
/**
* ClientSchema
*/
export type ClientSchema = {
/**
* Name
*/
name: string;
/**
* Companyname
*/
companyName: string;
/**
* Comment
*/
comment?: string | null;
details: ClientDetailsSchema;
/**
* Id
*/
id: number;
};
/**
* CreateBarcodeTemplateRequest
*/
@ -244,6 +289,42 @@ export type CreateBoardSchema = {
lexorank: string;
};
/**
* CreateClientRequest
*/
export type CreateClientRequest = {
entity: CreateClientSchema;
};
/**
* CreateClientResponse
*/
export type CreateClientResponse = {
/**
* Message
*/
message: string;
};
/**
* CreateClientSchema
*/
export type CreateClientSchema = {
/**
* Name
*/
name: string;
/**
* Companyname
*/
companyName: string;
/**
* Comment
*/
comment?: string | null;
details: ClientDetailsSchema;
};
/**
* CreateDealProductRequest
*/
@ -799,6 +880,16 @@ export type DeleteBoardResponse = {
message: string;
};
/**
* DeleteClientResponse
*/
export type DeleteClientResponse = {
/**
* Message
*/
message: string;
};
/**
* DeleteDealProductResponse
*/
@ -949,6 +1040,16 @@ export type GetBoardsResponse = {
items: Array<BoardSchema>;
};
/**
* GetClientsResponse
*/
export type GetClientsResponse = {
/**
* Items
*/
items: Array<ClientSchema>;
};
/**
* GetDealProductsResponse
*/
@ -1419,6 +1520,42 @@ export type UpdateBoardSchema = {
lexorank?: string | null;
};
/**
* UpdateClientRequest
*/
export type UpdateClientRequest = {
entity: UpdateClientSchema;
};
/**
* UpdateClientResponse
*/
export type UpdateClientResponse = {
/**
* Message
*/
message: string;
};
/**
* UpdateClientSchema
*/
export type UpdateClientSchema = {
/**
* Name
*/
name?: string | null;
/**
* Companyname
*/
companyName?: string | null;
/**
* Comment
*/
comment?: string | null;
details?: ClientDetailsSchema | null;
};
/**
* UpdateDealProductRequest
*/
@ -2357,6 +2494,111 @@ export type GetStatusHistoryResponses = {
export type GetStatusHistoryResponse2 =
GetStatusHistoryResponses[keyof GetStatusHistoryResponses];
export type GetClientsData = {
body?: never;
path?: never;
query?: never;
url: "/modules/clients/client/";
};
export type GetClientsResponses = {
/**
* Successful Response
*/
200: GetClientsResponse;
};
export type GetClientsResponse2 =
GetClientsResponses[keyof GetClientsResponses];
export type CreateClientData = {
body: CreateClientRequest;
path?: never;
query?: never;
url: "/modules/clients/client/";
};
export type CreateClientErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type CreateClientError = CreateClientErrors[keyof CreateClientErrors];
export type CreateClientResponses = {
/**
* Successful Response
*/
200: CreateClientResponse;
};
export type CreateClientResponse2 =
CreateClientResponses[keyof CreateClientResponses];
export type DeleteClientData = {
body?: never;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/modules/clients/client/{pk}";
};
export type DeleteClientErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type DeleteClientError = DeleteClientErrors[keyof DeleteClientErrors];
export type DeleteClientResponses = {
/**
* Successful Response
*/
200: DeleteClientResponse;
};
export type DeleteClientResponse2 =
DeleteClientResponses[keyof DeleteClientResponses];
export type UpdateClientData = {
body: UpdateClientRequest;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/modules/clients/client/{pk}";
};
export type UpdateClientErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UpdateClientError = UpdateClientErrors[keyof UpdateClientErrors];
export type UpdateClientResponses = {
/**
* Successful Response
*/
200: UpdateClientResponse;
};
export type UpdateClientResponse2 =
UpdateClientResponses[keyof UpdateClientResponses];
export type GetBarcodeTemplatesData = {
body?: never;
path?: never;

View File

@ -89,6 +89,27 @@ export const zBuiltInModuleSchemaOutput = z.object({
tabs: z.array(zBuiltInModuleTabSchema),
});
/**
* ClientDetailsSchema
*/
export const zClientDetailsSchema = z.object({
telegram: z.string(),
phoneNumber: z.string(),
inn: z.string(),
email: z.string(),
});
/**
* ClientSchema
*/
export const zClientSchema = z.object({
name: z.string(),
companyName: z.string(),
comment: z.optional(z.union([z.string(), z.null()])),
details: zClientDetailsSchema,
id: z.int(),
});
/**
* CreateBarcodeTemplateSchema
*/
@ -138,6 +159,30 @@ export const zCreateBoardResponse = z.object({
entity: zBoardSchema,
});
/**
* CreateClientSchema
*/
export const zCreateClientSchema = z.object({
name: z.string(),
companyName: z.string(),
comment: z.optional(z.union([z.string(), z.null()])),
details: zClientDetailsSchema,
});
/**
* CreateClientRequest
*/
export const zCreateClientRequest = z.object({
entity: zCreateClientSchema,
});
/**
* CreateClientResponse
*/
export const zCreateClientResponse = z.object({
message: z.string(),
});
/**
* CreateDealProductSchema
*/
@ -563,6 +608,13 @@ export const zDeleteBoardResponse = z.object({
message: z.string(),
});
/**
* DeleteClientResponse
*/
export const zDeleteClientResponse = z.object({
message: z.string(),
});
/**
* DeleteDealProductResponse
*/
@ -668,6 +720,13 @@ export const zGetBoardsResponse = z.object({
items: z.array(zBoardSchema),
});
/**
* GetClientsResponse
*/
export const zGetClientsResponse = z.object({
items: z.array(zClientSchema),
});
/**
* GetDealProductsResponse
*/
@ -851,6 +910,30 @@ export const zUpdateBoardResponse = z.object({
message: z.string(),
});
/**
* UpdateClientSchema
*/
export const zUpdateClientSchema = z.object({
name: z.optional(z.union([z.string(), z.null()])),
companyName: z.optional(z.union([z.string(), z.null()])),
comment: z.optional(z.union([z.string(), z.null()])),
details: z.optional(z.union([zClientDetailsSchema, z.null()])),
});
/**
* UpdateClientRequest
*/
export const zUpdateClientRequest = z.object({
entity: zUpdateClientSchema,
});
/**
* UpdateClientResponse
*/
export const zUpdateClientResponse = z.object({
message: z.string(),
});
/**
* UpdateDealProductSchema
*/
@ -1323,6 +1406,54 @@ export const zGetStatusHistoryData = z.object({
*/
export const zGetStatusHistoryResponse2 = zGetStatusHistoryResponse;
export const zGetClientsData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetClientsResponse2 = zGetClientsResponse;
export const zCreateClientData = z.object({
body: zCreateClientRequest,
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zCreateClientResponse2 = zCreateClientResponse;
export const zDeleteClientData = z.object({
body: z.optional(z.never()),
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zDeleteClientResponse2 = zDeleteClientResponse;
export const zUpdateClientData = z.object({
body: zUpdateClientRequest,
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUpdateClientResponse2 = zUpdateClientResponse;
export const zGetBarcodeTemplatesData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),

View File

@ -1,7 +1,13 @@
import BarcodeTemplateEditorModal from "@/app/barcode-templates/modals/BarcodeTemplateFormModal/BarcodeTemplateEditorModal";
import ClientEditorModal from "@/app/clients/modals/ClientFormModal/ClientFormModal";
import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal";
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
import { ServiceCategoryEditorModal, ServiceEditorModal, ServicesKitEditorModal } from "@/app/services/modals";
import {
ServiceCategoryEditorModal,
ServiceEditorModal,
ServicesKitEditorModal,
} from "@/app/services/modals";
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
import {
DealProductEditorModal,
@ -11,7 +17,6 @@ import {
ProductServiceEditorModal,
ServicesKitSelectModal,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals";
import BarcodeTemplateEditorModal from "@/app/barcode-templates/modals/BarcodeTemplateFormModal/BarcodeTemplateEditorModal";
export const modals = {
enterNameModal: EnterNameModal,
@ -28,4 +33,5 @@ export const modals = {
serviceCategoryEditorModal: ServiceCategoryEditorModal,
serviceEditorModal: ServiceEditorModal,
barcodeTemplateEditorModal: BarcodeTemplateEditorModal,
clientEditorModal: ClientEditorModal,
};