added forms for catalogue jur

This commit is contained in:
Grog 2026-10-12 09:45:31 +03:00
parent 8e8e46ecbc
commit 3977daebe5
10 changed files with 607 additions and 52 deletions

View File

@ -1,7 +1,32 @@
import { Button, Space } from "antd";
import PageTitle from "../components/PageTitle";
import { CatalogueJurTable } from "../widgets/CatalogueJurTable/CatalogueJurTable";
import { PlusOutlined } from "@ant-design/icons";
import { useState } from "react";
import { useAppDispatch, useAppSelector } from "../store/store";
import { CatalogueJurForm } from "../widgets/CatalogueJurForm/CatalogueJurForm";
import { getCatalogueJurThunk } from "../store/slices/catalogueJurSlice/thunks";
import { CatalogueJurFormProps } from "../types/catalogueJur/catalogueJur";
import { createCatalogueJur } from "../services/api/directories/catalogue_jur";
export function CatalogueJur() {
const dispatch = useAppDispatch();
const isGettingArchive = useAppSelector(
(state) => state.catalogue_jur.isGettingArchive,
);
const [createFormOpen, setCreateFormOpen] = useState(false);
async function createJurHandler(values: CatalogueJurFormProps) {
try {
await createCatalogueJur(values);
dispatch(
getCatalogueJurThunk({ closed: isGettingArchive, outside_group: true }),
);
setCreateFormOpen(false);
} catch {}
}
return (
<div
style={{
@ -11,10 +36,16 @@ export function CatalogueJur() {
height: "100vh",
}}
>
<PageTitle
header="Юридические лица"
location="Справочники"
/>
<Space style={{ justifyContent: "space-between" }}>
<PageTitle header="Юридические лица" location="Справочники" />
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setCreateFormOpen(true)}
>
Создать ЮЛ
</Button>
</Space>
<main
style={{
flex: "1 1 auto",
@ -26,6 +57,17 @@ export function CatalogueJur() {
>
<CatalogueJurTable />
</main>
{createFormOpen && (
<CatalogueJurForm
open={createFormOpen}
onCancel={() => {
setCreateFormOpen(false);
}}
onOk={(values) => {
createJurHandler(values);
}}
/>
)}
</div>
);
}

View File

@ -2,10 +2,9 @@ import { api } from "../../../api/client";
import {
GetCatalogueJurPropsI,
} from "../../../store/slices/catalogueJurSlice/types";
import { ICatalogueJur } from "../../../types/catalogueJur/catalogueJur";
import { CatalogueJurFormProps, ICatalogueJur, MoveCatalogueJurProps, WithdrawCatalogueJurProps } from "../../../types/catalogueJur/catalogueJur";
export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) {
// #TODO поменять на outside_group=true
const res = await api.get<ICatalogueJur[]>(
"/directories/legal-entities",
params,
@ -22,3 +21,40 @@ export async function fetchOneCatalogueJur(
);
return res;
}
export async function moveCatalogueJur(
entityId: string,
body: MoveCatalogueJurProps,
): Promise<void> {
return api.post<void>(
`/directories/legal-entities/${entityId}/move`,
body,
);
}
export async function withdrawCatalogueJur(
entityId: string,
body: WithdrawCatalogueJurProps,
): Promise<void> {
return api.post<void>(
`/directories/legal-entities/${entityId}/withdraw`,
body,
);
}
export async function createCatalogueJur(
body: CatalogueJurFormProps,
): Promise<void> {
return api.post<void>(
`/directories/legal-entities`,
body,
);
}
export async function editCatalogueJur(
entityId: string,
body: CatalogueJurFormProps,
): Promise<void> {
return api.patch<void>(
`/directories/legal-entities/${entityId}`,
body,
);
}

View File

@ -1,20 +1,23 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { useSelector } from "react-redux";
import { Config } from "../../../conf/config";
import {CatalogueJurSliceType } from "./types";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { CatalogueJurSliceType } from "./types";
import { getCatalogueJurThunk } from "./thunks";
const initialState:CatalogueJurSliceType = {
const initialState: CatalogueJurSliceType = {
raw_rows: [],
total_groups: 0,
loading: false,
error: null,
isGettingArchive: false,
};
const catalogueJurSlice = createSlice({
name: "catalogue_jur",
initialState,
reducers: {},
reducers: {
setIsGettingArchive(state, action: PayloadAction<boolean>) {
state.isGettingArchive = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getCatalogueJurThunk.pending, (state, _) => {
@ -32,5 +35,5 @@ const catalogueJurSlice = createSlice({
});
},
});
export const { setIsGettingArchive } = catalogueJurSlice.actions;
export default catalogueJurSlice.reducer;

View File

@ -5,6 +5,7 @@ export interface CatalogueJurSliceType{
raw_rows: ICatalogueJur[];
loading: boolean;
error: string | null;
isGettingArchive:boolean
}

View File

@ -1,13 +1,13 @@
export interface ICatalogueJur {
id: string;
inn: string;
kpp: string | null;
crm_id: string | null;
entity_type: string;
kpp?: string;
crm_id?: string;
entity_type: "ЮЛ" | "ИП";
name: string;
company_group_id: string | null;
company_group_id?: string;
company_group_name: string | null;
regional_office_id: string | null;
regional_office_id?: string;
close_date: Date;
valid_from: Date;
created_at: Date;
@ -15,10 +15,38 @@ export interface ICatalogueJur {
outside_group: boolean;
closed: boolean | string;
memberships?: unknown[];
aliases?: unknown[];
aliases?: JurAlliase[];
regional_office: {
id: string;
name: string;
is_head_office: boolean;
};
}
export type JurAlliase = {
alias: string;
created_at: Date;
id: string;
normalized_alias: string;
};
export type MoveCatalogueJurProps = {
company_group_id: string;
effective_date: string;
};
export type WithdrawCatalogueJurProps = {
event: "CLOSE" | "REMOVE_FROM_GROUP";
effective_date: string;
comment: string;
};
export type CatalogueJurFormProps = {
inn?: string;
kpp?: string;
crm_id: string;
entity_type: "ЮЛ" | "ИП";
name: string;
aliases?: string[] | {value:string, label:string}[];
company_group_id?: string;
regional_office_id?: string;
};

View File

@ -0,0 +1,217 @@
import React, { useCallback, useEffect, useState } from "react";
import { Modal, Form, Input, Select, Row, Col, InputNumber } from "antd";
import { CatalogueJurFormProps } from "../../types/catalogueJur/catalogueJur";
import { useAppDispatch, useAppSelector } from "../../store/store";
import { getCompanyGroupsThunk } from "../../store/slices/companyGroupsSlice/thunks";
import { api } from "../../api/client";
import { RegionalOffice } from "../../api/types";
import { useAsyncData } from "../Admin/hooks";
import { fetchOneCatalogueJur } from "../../services/api/directories/catalogue_jur";
type Props = {
open: boolean;
onCancel: () => void;
onOk: (values: CatalogueJurFormProps) => void | Promise<void>;
jurId?: string;
};
export const CatalogueJurForm: React.FC<Props> = ({
open,
onCancel,
onOk,
jurId,
}) => {
const [form] = Form.useForm<CatalogueJurFormProps>();
const dispatch = useAppDispatch();
const loadOffices = useCallback(
() => api.get<RegionalOffice[]>("/directories/regional-offices"),
[],
);
const offices = useAsyncData(loadOffices);
const type = Form.useWatch("entity_type", form);
const { raw_rows, loading } = useAppSelector((state) => state.company_groups);
const [initialDataLoading, setInitialDataLoading] = useState(false);
async function getInitialData() {
setInitialDataLoading(true);
if (jurId) {
const data = await fetchOneCatalogueJur(jurId);
form.setFieldsValue({
name: data?.name,
company_group_id: data?.company_group_id,
aliases: data?.aliases?.map((aliase) => aliase.alias),
crm_id: data?.crm_id,
entity_type: data?.entity_type,
inn: data.inn,
kpp: data?.kpp,
regional_office_id: data?.regional_office_id,
});
}
setInitialDataLoading(false);
}
const handleOk = async () => {
try {
const values = await form.validateFields();
await onOk({
...values,
kpp: type === "ИП" ? undefined : values.kpp?.toString(),
inn: jurId ? undefined : values?.inn?.toString(),
company_group_id: jurId ? undefined : values.company_group_id,
});
} catch {
return;
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
useEffect(() => {
if (open && raw_rows.length === 0 && !loading) {
dispatch(getCompanyGroupsThunk());
}
}, [open, raw_rows.length, loading, dispatch]);
useEffect(() => {
if (!!jurId) {
getInitialData();
}
}, [open]);
return (
<Modal
title={
!!jurId ? "Изменить юридическое лицо" : "Добавить юридическое лицо"
}
open={open}
onCancel={handleCancel}
onOk={handleOk}
okText={!!jurId ? "Сохраить" : "Добавить"}
cancelText="Отмена"
destroyOnHidden
width={720}
loading={initialDataLoading}
>
<Form form={form} layout="vertical">
<Row gutter={16}>
<Col span={6}>
<Form.Item
name="inn"
label="ИНН"
rules={[
{ required: true, message: "Укажите ИНН" },
{ pattern: /^\d{10}$/, message: "10 цифр" },
]}
>
<InputNumber
disabled={!!jurId}
style={{ width: "100%" }}
placeholder="0000000000"
maxLength={10}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="kpp"
label="КПП"
rules={[{ pattern: /^\d{9}$/, message: "9 цифр" }]}
>
<InputNumber
placeholder="000000000"
maxLength={9}
disabled={type === "ИП"}
style={{ width: "100%" }}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="crm_id"
label="ID ЮЛ в CRM"
rules={[
{ required: true, message: "Укажите CRM ID" },
{ pattern: /^\d{11}$/, message: "11 цифр" },
]}
>
<Input maxLength={11} />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="entity_type"
label="Тип"
initialValue="ЮЛ"
rules={[{ required: true }]}
>
<Select
options={[
{ value: "ЮЛ", label: "ЮЛ" },
{ value: "ИП", label: "ИП" },
]}
/>
</Form.Item>
</Col>
</Row>
<Form.Item
name="name"
label="Наименование организации"
rules={[{ required: true, message: "Укажите наименование" }]}
>
<Input placeholder="ООО «Название»" />
</Form.Item>
<Form.Item
name="aliases"
label="Альтернативные наименования"
extra="Введите синоним и нажмите Enter. Можно добавить несколько значений."
>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Добавить синоним"
tokenSeparators={[","]}
open={false}
suffixIcon={null}
notFoundContent={null}
/>
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="company_group_id" label="Входит в ГСК">
<Select
disabled={!!jurId}
allowClear
placeholder="— не указан —"
options={raw_rows?.map((g) => ({ value: g.id, label: g.name }))}
showSearch
optionFilterProp="label"
notFoundContent={loading ? "Загрузка" : "Нет вариантов"}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="regional_office_id" label="Входит в ГСЗ">
<Select
allowClear
placeholder="— не указан —"
options={offices?.data?.map((g) => ({
value: g.id,
label: g.name,
}))}
showSearch
optionFilterProp="label"
notFoundContent={loading ? "Загрузка" : "Нет вариантов"}
/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
);
};

View File

@ -18,18 +18,37 @@ import {
SwapOutlined,
} from "@ant-design/icons";
import { useAppDispatch, useAppSelector } from "../../store/store";
import { getCompanyGroupsThunk } from "../../store/slices/companyGroupsSlice/thunks";
import { useTableFilters } from "../../components/GenericTable/useTableFilters";
import { ICatalogueJur } from "../../types/catalogueJur/catalogueJur";
import {
CatalogueJurFormProps,
ICatalogueJur,
MoveCatalogueJurProps,
WithdrawCatalogueJurProps,
} from "../../types/catalogueJur/catalogueJur";
import { getCatalogueJurThunk } from "../../store/slices/catalogueJurSlice/thunks";
import { MoveCatalogueJurForm } from "../MoveCatalogueJurForm/MoveCatalogueJurForm";
import {
editCatalogueJur,
moveCatalogueJur,
withdrawCatalogueJur,
} from "../../services/api/directories/catalogue_jur";
import { WithdrawCatalogueJurForm } from "../WithdrawCatalogueJurForm/WithdrawCatalogueJurForm";
import { setIsGettingArchive } from "../../store/slices/catalogueJurSlice/catalogueJurSlice";
import { CatalogueJurForm } from "../CatalogueJurForm/CatalogueJurForm";
export function CatalogueJurTable() {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const rawRows = useAppSelector((state) => state.catalogue_jur.raw_rows);
const loading = useAppSelector((state) => state.catalogue_jur.loading);
const [isGettingArchive, setIsGettingArchive] = useState(false);
const isGettingArchive = useAppSelector(
(state) => state.catalogue_jur.isGettingArchive,
);
const [moveCatalogueFormOpen, setMoveCatalogueFormOpen] = useState(false);
const [editCatalogueFormOpen, setEditCatalogueFormOpen] = useState(false);
const [withdrawCatalogueFormOpen, setWithdrawCatalogueFormOpen] =
useState(false);
const [jurId, setJurId] = useState("");
const { visibleKeys, setVisibleKeys } = useVisibleColumns(catalogueJurConfig);
const {
@ -43,7 +62,7 @@ export function CatalogueJurTable() {
hasActiveFilters,
} = useTableFilters<ICatalogueJur>();
// single filtered array — title count and table share it
const { rows, totalCount, displayedCount } = useFilteredRows(
const { rows } = useFilteredRows(
rawRows,
catalogueJurConfig,
rawQuery,
@ -51,10 +70,59 @@ export function CatalogueJurTable() {
sort,
);
useEffect(() => {
function getCatalogueJur() {
// #TODO поменять на outside_group=true
dispatch(
getCatalogueJurThunk({ closed: isGettingArchive, outside_group: false }),
getCatalogueJurThunk({ closed: isGettingArchive, outside_group: true }),
);
}
function closeMoveCatalogueJurHandler() {
setJurId("");
setMoveCatalogueFormOpen(false);
}
function closeWithdrawCatalogueJurHandler() {
setJurId("");
setWithdrawCatalogueFormOpen(false);
}
function closeEditCatalogueJurHandler() {
setJurId("");
setEditCatalogueFormOpen(false);
}
async function moveCatalogueJurHandler(values: MoveCatalogueJurProps) {
try {
await moveCatalogueJur(jurId, values);
getCatalogueJur();
closeMoveCatalogueJurHandler();
} catch (e) {
console.error(e);
}
}
async function withdrawCatalogueJurHandler(
values: WithdrawCatalogueJurProps,
) {
try {
await withdrawCatalogueJur(jurId, values);
getCatalogueJur();
closeWithdrawCatalogueJurHandler();
} catch (e) {
console.error(e);
}
}
async function editJurHandler(values: CatalogueJurFormProps) {
try {
await editCatalogueJur(jurId, values);
dispatch(
getCatalogueJurThunk({ closed: isGettingArchive, outside_group: true }),
);
closeEditCatalogueJurHandler()
} catch {}
}
useEffect(() => {
getCatalogueJur();
}, [isGettingArchive]);
const extraColumns: ColumnsType<ICatalogueJur> = useMemo(
@ -69,6 +137,10 @@ export function CatalogueJurTable() {
key: "edit",
icon: <EditOutlined />,
label: "Изменить ЮЛ",
onClick: () => {
setJurId(row.id);
setEditCatalogueFormOpen(true);
},
},
{
key: "history",
@ -80,18 +152,25 @@ export function CatalogueJurTable() {
key: "move",
icon: <SwapOutlined />,
label: "Перевести в другую ГК",
onClick: () => setJurId(row.id),
onClick: () => {
setJurId(row.id);
setMoveCatalogueFormOpen(true);
},
},
{
key: "withdraw",
icon: <LogoutOutlined />,
label: "Вывести из состава ГК",
danger: true,
onClick: () => {
setJurId(row.id);
setWithdrawCatalogueFormOpen(true);
},
},
];
return (
<Dropdown menu={{ items }} trigger={["click"]}>
<Button type="default">
<Button type="default" style={{ width: "32px" }}>
<EllipsisOutlined />
</Button>
</Dropdown>
@ -101,7 +180,7 @@ export function CatalogueJurTable() {
],
[navigate],
);
console.log(jurId, "test");
return (
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<Space
@ -131,17 +210,18 @@ export function CatalogueJurTable() {
Сбросить фильтры
</Button>
)}
<Checkbox
checked={isGettingArchive}
onChange={(e) => dispatch(setIsGettingArchive(e.target.checked))}
>
Архив
</Checkbox>
<ColumnSettings
defs={catalogueJurConfig.columns}
visibleKeys={visibleKeys}
onChange={setVisibleKeys}
/>
<Checkbox
checked={isGettingArchive}
onChange={(e) => setIsGettingArchive(e.target.checked)}
>
Архив
</Checkbox>
</Space>
</Space>
@ -156,19 +236,41 @@ export function CatalogueJurTable() {
globalQuery={rawQuery}
columnFilters={columnFilters}
onColumnFiltersChange={setColumnFilters}
extraColumns={extraColumns}
extraColumns={isGettingArchive ? undefined : extraColumns}
/>
{!!jurId && (
{moveCatalogueFormOpen && (
<MoveCatalogueJurForm
jurId={jurId}
open={!!jurId}
open={moveCatalogueFormOpen}
onCancel={() => {
setJurId("");
closeMoveCatalogueJurHandler();
}}
onOk={async (values) => {
// await api.create(values)
console.log(values, 'test2')
setJurId("");
onOk={(values) => {
moveCatalogueJurHandler(values);
}}
/>
)}
{withdrawCatalogueFormOpen && (
<WithdrawCatalogueJurForm
jurId={jurId}
open={withdrawCatalogueFormOpen}
onCancel={() => {
closeWithdrawCatalogueJurHandler();
}}
onOk={(values) => {
withdrawCatalogueJurHandler(values);
}}
/>
)}
{editCatalogueFormOpen && (
<CatalogueJurForm
jurId={jurId}
open={editCatalogueFormOpen}
onCancel={() => {
closeEditCatalogueJurHandler()
}}
onOk={(values) => {
editJurHandler(values);
}}
/>
)}

View File

@ -4,7 +4,7 @@ import { ICatalogueJur } from "../../types/catalogueJur/catalogueJur";
export const catalogueJurConfig: TableConfig<ICatalogueJur> = {
rowKey: "id",
searchableKeys: ["closed", "inn", "kpp", "name", "entity_type"],
searchableKeys: ["closed", "inn", "kpp", "name", "company_group_name", "entity_type"],
columns: [
{
key: "closed",
@ -30,6 +30,11 @@ export const catalogueJurConfig: TableConfig<ICatalogueJur> = {
title: "Организация",
features: { search: true, multiselect: true, sort: true },
},
{
key: "company_group_name",
title: "ГК",
features: { search: true, multiselect: true, sort: true },
},
{
key: "entity_type",
title: "Тип",

View File

@ -1,12 +1,11 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Modal, Form, Select, Row, Col, Input, DatePicker } from "antd";
import { fetchGSKByComGroup } from "../../services/api/directories/company_groups";
import React, { useEffect, useState } from "react";
import { Modal, Form, Select, Input, DatePicker } from "antd";
import { useAppDispatch, useAppSelector } from "../../store/store";
import { getCompanyGroupsThunk } from "../../store/slices/companyGroupsSlice/thunks";
import FormItem from "antd/es/form/FormItem";
import { fetchOneCatalogueJur } from "../../services/api/directories/catalogue_jur";
import type { Dayjs } from "dayjs";
import dayjs from "dayjs";
import { MoveCatalogueJurProps } from "../../types/catalogueJur/catalogueJur";
type FormValues = {
name: string;
@ -19,7 +18,7 @@ type FormValues = {
type Props = {
open: boolean;
onCancel: () => void;
onOk: (values: FormValues) => void | Promise<void>;
onOk: (values: MoveCatalogueJurProps) => void | Promise<void>;
jurId: string;
};
@ -34,6 +33,7 @@ export const MoveCatalogueJurForm: React.FC<Props> = ({
const { raw_rows, loading } = useAppSelector((state) => state.company_groups);
const [initialDataLoading, setInitialDataLoading] = useState(false);
async function getInitialData() {
setInitialDataLoading(true);
const data = await fetchOneCatalogueJur(jurId);
@ -41,7 +41,7 @@ export const MoveCatalogueJurForm: React.FC<Props> = ({
name: data?.name,
company_group_name: data?.company_group_name ?? "",
old_company_group_id: data?.company_group_id ?? "",
company_group_id: "",
company_group_id: undefined,
effective_date: "",
});
setInitialDataLoading(false);
@ -49,9 +49,8 @@ export const MoveCatalogueJurForm: React.FC<Props> = ({
const handleOk = async () => {
try {
const values = await form.validateFields();
await onOk(values);
form.resetFields();
const { effective_date, company_group_id } = await form.validateFields();
await onOk({ effective_date, company_group_id });
} catch {
// validation error - keep modal open
}
@ -105,6 +104,7 @@ export const MoveCatalogueJurForm: React.FC<Props> = ({
)
.map((g) => ({ value: g.id, label: g.name }))}
notFoundContent={loading ? "Загрузка" : "Нет вариантов"}
optionFilterProp="label"
/>
</Form.Item>
@ -120,6 +120,7 @@ export const MoveCatalogueJurForm: React.FC<Props> = ({
}
>
<DatePicker
minDate={dayjs()}
style={{ width: "100%" }}
format="DD.MM.YYYY"
placeholder="ДД.ММ.ГГГГ"

View File

@ -0,0 +1,120 @@
import React, { useEffect, useState } from "react";
import { Modal, Form, Select, Input, DatePicker } from "antd";
import FormItem from "antd/es/form/FormItem";
import { fetchOneCatalogueJur } from "../../services/api/directories/catalogue_jur";
import dayjs from "dayjs";
import { WithdrawCatalogueJurProps } from "../../types/catalogueJur/catalogueJur";
import TextArea from "antd/es/input/TextArea";
interface FormValues extends WithdrawCatalogueJurProps {
name: string;
}
type Props = {
open: boolean;
onCancel: () => void;
onOk: (values: WithdrawCatalogueJurProps) => void | Promise<void>;
jurId: string;
};
export const WithdrawCatalogueJurForm: React.FC<Props> = ({
open,
onCancel,
onOk,
jurId,
}) => {
const [form] = Form.useForm<FormValues>();
const [initialDataLoading, setInitialDataLoading] = useState(false);
const eventOptions = [
{ value: "CLOSE", label: "Закрытие" },
{ value: "REMOVE_FROM_GROUP", label: "Переход в другую ГК" },
];
async function getInitialData() {
setInitialDataLoading(true);
const data = await fetchOneCatalogueJur(jurId);
form.setFieldsValue({
name: data?.name,
event: undefined,
effective_date: "",
comment: "",
});
setInitialDataLoading(false);
}
const handleOk = async () => {
try {
const {
effective_date,
event,
comment = "",
} = await form.validateFields();
await onOk({ effective_date, event, comment });
} catch {
// validation error - keep modal open
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
useEffect(() => {
getInitialData();
}, [open]);
return (
<Modal
title="Вывести ЮЛ из состава ГК"
open={open}
onCancel={handleCancel}
onOk={handleOk}
okText="Вывести"
cancelText="Отмена"
destroyOnHidden
loading={initialDataLoading}
okButtonProps={{ danger: true }}
>
<Form form={form} layout="vertical">
<FormItem name="name" label="ЮЛ">
<Input disabled />
</FormItem>
<Form.Item
name="event"
label="Событие"
rules={[{ required: true, message: "Укажите событие" }]}
>
<Select
placeholder="Выбрать"
showSearch
allowClear
options={eventOptions}
/>
</Form.Item>
<Form.Item
name="effective_date"
label="Дата закрытия"
rules={[{ required: true, message: "Укажите дату" }]}
getValueProps={(value: string | undefined) => ({
value: value ? dayjs(value) : undefined,
})}
normalize={(value) =>
value ? value.format("YYYY-MM-DD") : undefined
}
>
<DatePicker
style={{ width: "100%" }}
format="DD.MM.YYYY"
placeholder="ДД.ММ.ГГГГ"
/>
</Form.Item>
<FormItem name={"comment"} label="Комментарий">
<TextArea maxLength={6} rows={4} placeholder="Необязательно"/>
</FormItem>
</Form>
</Modal>
);
};