diff --git a/src/client/index.ts b/src/client/index.ts index 27d15ba..dd07988 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -9,10 +9,16 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { AuthLoginRequest } from './models/AuthLoginRequest'; export type { AuthLoginResponse } from './models/AuthLoginResponse'; +export type { ClientCreateRequest } from './models/ClientCreateRequest'; +export type { ClientCreateResponse } from './models/ClientCreateResponse'; +export type { ClientDeleteRequest } from './models/ClientDeleteRequest'; +export type { ClientDeleteResponse } from './models/ClientDeleteResponse'; export type { ClientDetailsSchema } from './models/ClientDetailsSchema'; export type { ClientGetAllResponse } from './models/ClientGetAllResponse'; export type { ClientSchema } from './models/ClientSchema'; export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest'; +export type { ClientUpdateRequest } from './models/ClientUpdateRequest'; +export type { ClientUpdateResponse } from './models/ClientUpdateResponse'; export type { DealAddServicesRequest } from './models/DealAddServicesRequest'; export type { DealAddServicesResponse } from './models/DealAddServicesResponse'; export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest'; diff --git a/src/client/models/ClientCreateRequest.ts b/src/client/models/ClientCreateRequest.ts new file mode 100644 index 0000000..4b188d2 --- /dev/null +++ b/src/client/models/ClientCreateRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ClientSchema } from './ClientSchema'; +export type ClientCreateRequest = { + data: ClientSchema; +}; + diff --git a/src/client/models/ClientCreateResponse.ts b/src/client/models/ClientCreateResponse.ts new file mode 100644 index 0000000..134225d --- /dev/null +++ b/src/client/models/ClientCreateResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ClientCreateResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/ClientDeleteRequest.ts b/src/client/models/ClientDeleteRequest.ts new file mode 100644 index 0000000..3e2f16f --- /dev/null +++ b/src/client/models/ClientDeleteRequest.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ClientDeleteRequest = { + clientId: number; +}; + diff --git a/src/client/models/ClientDeleteResponse.ts b/src/client/models/ClientDeleteResponse.ts new file mode 100644 index 0000000..a77f135 --- /dev/null +++ b/src/client/models/ClientDeleteResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ClientDeleteResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/ClientUpdateRequest.ts b/src/client/models/ClientUpdateRequest.ts new file mode 100644 index 0000000..9940aef --- /dev/null +++ b/src/client/models/ClientUpdateRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ClientSchema } from './ClientSchema'; +export type ClientUpdateRequest = { + data: ClientSchema; +}; + diff --git a/src/client/models/ClientUpdateResponse.ts b/src/client/models/ClientUpdateResponse.ts new file mode 100644 index 0000000..47dab67 --- /dev/null +++ b/src/client/models/ClientUpdateResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ClientUpdateResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/services/ClientService.ts b/src/client/services/ClientService.ts index bf483e5..1cce5f3 100644 --- a/src/client/services/ClientService.ts +++ b/src/client/services/ClientService.ts @@ -2,8 +2,14 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { ClientCreateRequest } from '../models/ClientCreateRequest'; +import type { ClientCreateResponse } from '../models/ClientCreateResponse'; +import type { ClientDeleteRequest } from '../models/ClientDeleteRequest'; +import type { ClientDeleteResponse } from '../models/ClientDeleteResponse'; import type { ClientGetAllResponse } from '../models/ClientGetAllResponse'; import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest'; +import type { ClientUpdateRequest } from '../models/ClientUpdateRequest'; +import type { ClientUpdateResponse } from '../models/ClientUpdateResponse'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; @@ -60,4 +66,64 @@ export class ClientService { url: '/client/get-all', }); } + /** + * Create Client + * @returns ClientCreateResponse Successful Response + * @throws ApiError + */ + public static createClient({ + requestBody, + }: { + requestBody: ClientCreateRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/client/create', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Update Client + * @returns ClientUpdateResponse Successful Response + * @throws ApiError + */ + public static updateClient({ + requestBody, + }: { + requestBody: ClientUpdateRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/client/update', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Client + * @returns ClientDeleteResponse Successful Response + * @throws ApiError + */ + public static deleteClient({ + requestBody, + }: { + requestBody: ClientDeleteRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/client/delete', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } } diff --git a/src/components/Navbar/Navbar.module.css b/src/components/Navbar/Navbar.module.css index 3e8f9c8..80963dd 100644 --- a/src/components/Navbar/Navbar.module.css +++ b/src/components/Navbar/Navbar.module.css @@ -1,19 +1,21 @@ .navbar { - width: rem(80px); height: 100%; padding: var(--mantine-spacing-md); display: flex; flex-direction: column; border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); + align-items: center; } .navbarMain { flex: 1; margin-top: rem(50px); + width: 100%; } .link { - width: rem(50px); + //width: rem(50px); + width: 100%; height: rem(50px); border-radius: var(--mantine-radius-md); display: flex; diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index fcf3fc8..b4f33f4 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -94,7 +94,7 @@ export function Navbar() { - + diff --git a/src/modals/modals.ts b/src/modals/modals.ts index ce62dc1..a00eba8 100644 --- a/src/modals/modals.ts +++ b/src/modals/modals.ts @@ -2,10 +2,12 @@ import EnterDeadlineModal from "./EnterDeadlineModal/EnterDeadlineModal.tsx"; import CreateServiceCategoryModal from "../pages/ServicesPage/modals/CreateServiceCategoryModal.tsx"; import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx"; import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx"; +import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx"; export const modals = { enterDeadline: EnterDeadlineModal, createServiceCategory: CreateServiceCategoryModal, createService: CreateServiceModal, - createProduct: createProductModal + createProduct: createProductModal, + productFormModal: ProductFormModal, } diff --git a/src/pages/ClientsPage/ClientsPage.module.css b/src/pages/ClientsPage/ClientsPage.module.css index 652a476..61ac571 100644 --- a/src/pages/ClientsPage/ClientsPage.module.css +++ b/src/pages/ClientsPage/ClientsPage.module.css @@ -1,4 +1,12 @@ .container { - /*background: rebeccapurple;*/ + display: flex; + flex-direction: column; flex: 1; + gap: rem(10); +} + +.top-panel { + padding: rem(5); + gap: rem(10); + display: flex; } \ No newline at end of file diff --git a/src/pages/ClientsPage/ClientsPage.tsx b/src/pages/ClientsPage/ClientsPage.tsx index 0e69579..09cadd1 100644 --- a/src/pages/ClientsPage/ClientsPage.tsx +++ b/src/pages/ClientsPage/ClientsPage.tsx @@ -2,17 +2,82 @@ import {FC} from "react"; import ClientsTable from "./components/ClientsTable/ClientsTable.tsx"; import useClientsList from "./hooks/useClientsList.tsx"; import PageBlock from "../../components/PageBlock/PageBlock.tsx"; +import styles from './ClientsPage.module.css'; +import {Button} from "@mantine/core"; +import {modals} from "@mantine/modals"; +import {ClientSchema, ClientService} from "../../client"; +import {notifications} from "../../shared/lib/notifications.ts"; const ClientsPage: FC = () => { - const {clients} = useClientsList(); + const {clients, refetch} = useClientsList(); + const onCreate = (client: ClientSchema) => { + ClientService + .createClient({ + requestBody: { + data: client + } + }) + .then(async ({ok, message}) => { + notifications.guess(ok, {message}); + if (ok) + await refetch() + }) + } + const onChange = (client: ClientSchema) => { + ClientService + .updateClient({ + requestBody: { + data: client + } + }) + .then(async ({ok, message}) => { + notifications.guess(ok, {message}); + if (ok) + await refetch() + }) + } + const onDelete = (client: ClientSchema) => { + ClientService + .deleteClient({ + requestBody: { + clientId: client.id + } + }) + .then(async ({ok, message}) => { + notifications.guess(ok, {message}); + if (ok) + await refetch() + }) + } + const onCreateClick = () => { + modals.openContextModal({ + modal: 'productFormModal', + title: "Создание клиента", + innerProps: { + onCreate + } + }) + } return ( - <> +
- - +
+ +
- - + + + +
) } export default ClientsPage; \ No newline at end of file diff --git a/src/pages/ClientsPage/components/ClientsTable/ClientsTable.tsx b/src/pages/ClientsPage/components/ClientsTable/ClientsTable.tsx index 8ff0275..78dad55 100644 --- a/src/pages/ClientsPage/components/ClientsTable/ClientsTable.tsx +++ b/src/pages/ClientsPage/components/ClientsTable/ClientsTable.tsx @@ -3,25 +3,57 @@ import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx"; import {useClientsTableColumns} from "./columns.tsx"; import {MRT_TableOptions} from "mantine-react-table"; import {ClientSchema} from "../../../../client"; +import {ActionIcon, Flex, Tooltip} from "@mantine/core"; +import {IconEdit, IconTrash} from "@tabler/icons-react"; +import {CRUDTableProps} from "../../../../types/CRUDTable.tsx"; +import {modals} from "@mantine/modals"; -type Props = { - data: ClientSchema[]; -} -const ClientsTable: FC = ({data}) => { +const ClientsTable: FC> = ({ + items, + onDelete, + onChange + }) => { const columns = useClientsTableColumns(); + const onEditClick = (client: ClientSchema) => { + if (!onChange) return; + modals.openContextModal({ + modal: "productFormModal", + title: 'Создание клиента', + withCloseButton: false, + innerProps: { + onChange: (newClient) => onChange(newClient), + element: client, + } + }) + } return ( <> ( + + + onEditClick(row.original)} + variant={"default"}> + + + + + { + if (onDelete) onDelete(row.original); + }} variant={"default"}> + + + + + ) } as MRT_TableOptions} /> diff --git a/src/pages/ClientsPage/modals/BaseFormModal/BaseFormModal.tsx b/src/pages/ClientsPage/modals/BaseFormModal/BaseFormModal.tsx new file mode 100644 index 0000000..bc3898a --- /dev/null +++ b/src/pages/ClientsPage/modals/BaseFormModal/BaseFormModal.tsx @@ -0,0 +1,69 @@ +import {UseFormReturnType} from "@mantine/form"; +import {Button, Flex, rem} from "@mantine/core"; +import {FC} from "react"; + +type CreateProps = { + onCreate(values: T): void; +} +type EditProps = { + onChange(values: T): void; + element: T; +} + +export type CreateEditFormProps = CreateProps | EditProps; + +type BaseProps = { + form: UseFormReturnType + onClose: () => void; + closeOnSubmit?: boolean; + children: React.JSX.Element; +} +type Props = BaseProps & (CreateProps | EditProps); + +const BaseFormModal = (props: Props) => { + const {closeOnSubmit = false} = props; + const isEditing = 'onChange' in props; + + const onSubmit = (values: T) => { + if (isEditing) { + props.onChange(values); + } else { + props.onCreate(values); + } + + if (closeOnSubmit) props.onClose(); + } + return ( +
onSubmit(values))}> + + {props.children} + + + + + +
+ ) +} +type BodyProps = { + children: React.JSX.Element; +} +const Body: FC = ({children}) => { + return ( + + {children} + + ) +} +BaseFormModal.Body = Body; +export default BaseFormModal; \ No newline at end of file diff --git a/src/pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx b/src/pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx new file mode 100644 index 0000000..9843226 --- /dev/null +++ b/src/pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx @@ -0,0 +1,105 @@ +import {ContextModalProps} from "@mantine/modals"; +import {Fieldset, NumberInput, TextInput} from "@mantine/core"; +import {useForm} from "@mantine/form"; +import {ClientSchema} from "../../../../client"; +import {getDigitsCount} from "../../../../shared/lib/utils.ts"; +import BaseFormModal, {CreateEditFormProps} from "../BaseFormModal/BaseFormModal.tsx"; + + +type Props = CreateEditFormProps; + +const ClientFormModal = ({ + context, + id, + innerProps, + }: ContextModalProps) => { + const isEditing = 'onChange' in innerProps; + + const initialValues: ClientSchema = isEditing ? { + id: innerProps.element.id, + name: innerProps.element.name, + details: { + address: innerProps.element.details?.address, + phoneNumber: innerProps.element.details?.phoneNumber, + email: innerProps.element.details?.email, + inn: innerProps.element.details?.inn + } + } : { + id: -1, + name: '', + details: { + address: '', + phoneNumber: '', + email: '', + inn: undefined + } + } + const form = useForm({ + initialValues: initialValues, + validate: { + name: (name: string) => name.trim() !== '' ? null : "Необходимо ввести название клиента", + details: { + address: (address: string | undefined | null) => (address && address.trim() !== '') ? null : "Необходимо ввести адрес", + phoneNumber: (phoneNumber: string | undefined | null) => (phoneNumber && phoneNumber.trim() !== '') ? null : "Необходимо ввести номер телефона", + email: (email: string | undefined | null) => (email && email.trim() !== '') ? null : "Необходимо ввести почту", + inn: (inn: number | undefined | null) => (inn && getDigitsCount(inn) >= 10) ? null : "ИНН должен содержать не менее 10 цифр", + } + } + }) + + + const onClose = () => { + context.closeContextModal(id); + } + return ( + + + <> +
+ +
+
+ + + + +
+ + +
+
+ ) +} + +export default ClientFormModal; \ No newline at end of file diff --git a/src/pages/PageWrapper/PageWrapper.tsx b/src/pages/PageWrapper/PageWrapper.tsx index bd30426..9c360a1 100644 --- a/src/pages/PageWrapper/PageWrapper.tsx +++ b/src/pages/PageWrapper/PageWrapper.tsx @@ -1,5 +1,5 @@ import {FC, ReactNode} from "react"; -import {AppShell, rem} from "@mantine/core"; +import {AppShell} from "@mantine/core"; import {Navbar} from "../../components/Navbar/Navbar.tsx"; import {useSelector} from "react-redux"; import {RootState} from "../../redux/store.ts"; @@ -13,7 +13,7 @@ const PageWrapper: FC = ({children}) => { return ( diff --git a/src/shared/lib/utils.ts b/src/shared/lib/utils.ts index 4a938c8..aab9bb5 100644 --- a/src/shared/lib/utils.ts +++ b/src/shared/lib/utils.ts @@ -4,4 +4,9 @@ export const dateWithoutTimezone = (date: Date) => { .toISOString() .slice(0, -1); return withoutTimezone; +}; + +export const getDigitsCount = (num: number): number => { + if (num === 0) return 1; + return Math.floor(Math.log10(Math.abs(num))) + 1; }; \ No newline at end of file