addded legalEntities table and move form
This commit is contained in:
parent
81548eb4ae
commit
8e8e46ecbc
@ -1,29 +1,40 @@
|
||||
import { clearToken, getToken } from '../auth/token';
|
||||
import { Config } from '../conf/config';
|
||||
import { clearToken, getToken } from "../auth/token";
|
||||
import { Config } from "../conf/config";
|
||||
|
||||
// Хвостовые слэши срезаются: API_URL='/' иначе дал бы `//api/v1` (адрес без хоста)
|
||||
export const API_BASE = `${Config.API_URL.replace(/\/+$/, '')}/api/v1`;
|
||||
export const API_BASE = `${Config.API_URL.replace(/\/+$/, "")}/api/v1`;
|
||||
|
||||
/** Событие `window`: сервер ответил 401, токен сброшен — интерфейс должен вернуться к входу. */
|
||||
export const UNAUTHORIZED_EVENT = 'armka:unauthorized';
|
||||
export const UNAUTHORIZED_EVENT = "armka:unauthorized";
|
||||
|
||||
const FALLBACK_MESSAGE = 'Не удалось выполнить запрос';
|
||||
const FALLBACK_MESSAGE = "Не удалось выполнить запрос";
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public code: string | undefined,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
constructor(
|
||||
public status: number,
|
||||
public code: string | undefined,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
||||
type ErrorInfo = { code: string | undefined; message: string };
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function buildQuery(params?: object): string {
|
||||
if (!params) return "";
|
||||
const search = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value === undefined || value === null) continue;
|
||||
search.append(key, String(value));
|
||||
}
|
||||
const qs = search.toString();
|
||||
return qs ? `?${qs}` : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,88 +43,102 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
* массив (422 валидации pydantic) → `msg` первого элемента.
|
||||
*/
|
||||
function parseErrorBody(body: unknown): ErrorInfo {
|
||||
const detail = isRecord(body) ? body.detail : undefined;
|
||||
if (typeof detail === 'string') {
|
||||
return { code: undefined, message: detail };
|
||||
}
|
||||
if (isRecord(detail)) {
|
||||
const code = typeof detail.code === 'string' ? detail.code : undefined;
|
||||
const message = typeof detail.message === 'string' ? detail.message : FALLBACK_MESSAGE;
|
||||
return { code, message };
|
||||
}
|
||||
if (Array.isArray(detail)) {
|
||||
const first: unknown = detail[0];
|
||||
if (isRecord(first) && typeof first.msg === 'string') {
|
||||
return { code: undefined, message: first.msg };
|
||||
}
|
||||
}
|
||||
return { code: undefined, message: FALLBACK_MESSAGE };
|
||||
const detail = isRecord(body) ? body.detail : undefined;
|
||||
if (typeof detail === "string") {
|
||||
return { code: undefined, message: detail };
|
||||
}
|
||||
if (isRecord(detail)) {
|
||||
const code = typeof detail.code === "string" ? detail.code : undefined;
|
||||
const message =
|
||||
typeof detail.message === "string" ? detail.message : FALLBACK_MESSAGE;
|
||||
return { code, message };
|
||||
}
|
||||
if (Array.isArray(detail)) {
|
||||
const first: unknown = detail[0];
|
||||
if (isRecord(first) && typeof first.msg === "string") {
|
||||
return { code: undefined, message: first.msg };
|
||||
}
|
||||
}
|
||||
return { code: undefined, message: FALLBACK_MESSAGE };
|
||||
}
|
||||
|
||||
async function readErrorInfo(response: Response): Promise<ErrorInfo> {
|
||||
try {
|
||||
return parseErrorBody(await response.json());
|
||||
} catch {
|
||||
// тело не JSON (например, ответ прокси) — показываем общий текст
|
||||
return { code: undefined, message: FALLBACK_MESSAGE };
|
||||
}
|
||||
try {
|
||||
return parseErrorBody(await response.json());
|
||||
} catch {
|
||||
// тело не JSON (например, ответ прокси) — показываем общий текст
|
||||
return { code: undefined, message: FALLBACK_MESSAGE };
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
const headers = new Headers(init.headers);
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
// JSON-заголовок только для строкового тела; FormData и др. отдаём браузеру как есть (он сам задаёт boundary)
|
||||
if (typeof init.body === 'string' && !headers.has('Content-Type')) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
export async function apiFetch<T>(
|
||||
path: string,
|
||||
init: RequestInit = {},
|
||||
): Promise<T> {
|
||||
const headers = new Headers(init.headers);
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
headers.set("Authorization", `Bearer ${token}`);
|
||||
}
|
||||
// JSON-заголовок только для строкового тела; FormData и др. отдаём браузеру как есть (он сам задаёт boundary)
|
||||
if (typeof init.body === "string" && !headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${API_BASE}${path}`, { ...init, headers });
|
||||
} catch (e: unknown) {
|
||||
// отмену запроса (AbortController) вызывающий код обрабатывает сам; остальное — сеть/CORS/DNS: текст браузера не показываем
|
||||
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||
throw e;
|
||||
}
|
||||
throw new ApiError(0, 'NETWORK', 'Нет связи с сервером');
|
||||
}
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${API_BASE}${path}`, { ...init, headers });
|
||||
} catch (e: unknown) {
|
||||
// отмену запроса (AbortController) вызывающий код обрабатывает сам; остальное — сеть/CORS/DNS: текст браузера не показываем
|
||||
if (e instanceof DOMException && e.name === "AbortError") {
|
||||
throw e;
|
||||
}
|
||||
throw new ApiError(0, "NETWORK", "Нет связи с сервером");
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
clearToken();
|
||||
window.dispatchEvent(new Event(UNAUTHORIZED_EVENT));
|
||||
const info = await readErrorInfo(response);
|
||||
throw new ApiError(401, info.code, info.message);
|
||||
}
|
||||
if (!response.ok) {
|
||||
const info = await readErrorInfo(response);
|
||||
throw new ApiError(response.status, info.code, info.message);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
// 2xx с пустым или не-JSON телом (страница прокси, 200 без содержимого) не должен давать «сырой» SyntaxError
|
||||
const text = await response.text();
|
||||
if (text === '') {
|
||||
return undefined as T;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text) as T;
|
||||
} catch {
|
||||
throw new ApiError(response.status, 'BAD_RESPONSE', 'Некорректный ответ сервера');
|
||||
}
|
||||
if (response.status === 401) {
|
||||
clearToken();
|
||||
window.dispatchEvent(new Event(UNAUTHORIZED_EVENT));
|
||||
const info = await readErrorInfo(response);
|
||||
throw new ApiError(401, info.code, info.message);
|
||||
}
|
||||
if (!response.ok) {
|
||||
const info = await readErrorInfo(response);
|
||||
throw new ApiError(response.status, info.code, info.message);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
// 2xx с пустым или не-JSON телом (страница прокси, 200 без содержимого) не должен давать «сырой» SyntaxError
|
||||
const text = await response.text();
|
||||
if (text === "") {
|
||||
return undefined as T;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text) as T;
|
||||
} catch {
|
||||
throw new ApiError(
|
||||
response.status,
|
||||
"BAD_RESPONSE",
|
||||
"Некорректный ответ сервера",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function withJsonBody(method: string, body: unknown): RequestInit {
|
||||
return body === undefined ? { method } : { method, body: JSON.stringify(body) };
|
||||
return body === undefined
|
||||
? { method }
|
||||
: { method, body: JSON.stringify(body) };
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => apiFetch<T>(path),
|
||||
post: <T>(path: string, body?: unknown) => apiFetch<T>(path, withJsonBody('POST', body)),
|
||||
patch: <T>(path: string, body?: unknown) => apiFetch<T>(path, withJsonBody('PATCH', body)),
|
||||
put: <T>(path: string, body?: unknown) => apiFetch<T>(path, withJsonBody('PUT', body)),
|
||||
del: <T>(path: string) => apiFetch<T>(path, { method: 'DELETE' }),
|
||||
get: <T>(path: string, params?:object) =>
|
||||
apiFetch<T>(`${path}${buildQuery(params)}`),
|
||||
post: <T>(path: string, body?: unknown) =>
|
||||
apiFetch<T>(path, withJsonBody("POST", body)),
|
||||
patch: <T>(path: string, body?: unknown) =>
|
||||
apiFetch<T>(path, withJsonBody("PATCH", body)),
|
||||
put: <T>(path: string, body?: unknown) =>
|
||||
apiFetch<T>(path, withJsonBody("PUT", body)),
|
||||
del: <T>(path: string) => apiFetch<T>(path, { method: "DELETE" }),
|
||||
};
|
||||
|
||||
@ -3,10 +3,17 @@ import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import { ConfigProvider } from "antd";
|
||||
import locale from "antd/locale/ru_RU";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import "dayjs/locale/ru";
|
||||
|
||||
dayjs.locale("ru");
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
// <StrictMode>
|
||||
<ConfigProvider
|
||||
locale={locale}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: "rgba(46, 166, 75, 0.8)",
|
||||
|
||||
@ -1,3 +1,31 @@
|
||||
import PageTitle from "../components/PageTitle";
|
||||
import { CatalogueJurTable } from "../widgets/CatalogueJurTable/CatalogueJurTable";
|
||||
|
||||
export function CatalogueJur() {
|
||||
return <h3>ЮЛ вне ГК</h3>;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 12,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<PageTitle
|
||||
header="Юридические лица"
|
||||
location="Справочники"
|
||||
/>
|
||||
<main
|
||||
style={{
|
||||
flex: "1 1 auto",
|
||||
minHeight: 0,
|
||||
overflow: "auto",
|
||||
backgroundColor: "white",
|
||||
borderRadius: 12,
|
||||
}}
|
||||
>
|
||||
<CatalogueJurTable />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
24
src/services/api/directories/catalogue_jur.ts
Normal file
24
src/services/api/directories/catalogue_jur.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { api } from "../../../api/client";
|
||||
import {
|
||||
GetCatalogueJurPropsI,
|
||||
} from "../../../store/slices/catalogueJurSlice/types";
|
||||
import { ICatalogueJur } from "../../../types/catalogueJur/catalogueJur";
|
||||
|
||||
export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) {
|
||||
// #TODO поменять на outside_group=true
|
||||
const res = await api.get<ICatalogueJur[]>(
|
||||
"/directories/legal-entities",
|
||||
params,
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function fetchOneCatalogueJur(
|
||||
catalogue_jur: string,
|
||||
): Promise<ICatalogueJur> {
|
||||
const res = await api.get<ICatalogueJur>(
|
||||
`/directories/legal-entities/${catalogue_jur}`,
|
||||
);
|
||||
return res;
|
||||
}
|
||||
36
src/store/slices/catalogueJurSlice/catalogueJurSlice.ts
Normal file
36
src/store/slices/catalogueJurSlice/catalogueJurSlice.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Config } from "../../../conf/config";
|
||||
import {CatalogueJurSliceType } from "./types";
|
||||
import { getCatalogueJurThunk } from "./thunks";
|
||||
|
||||
const initialState:CatalogueJurSliceType = {
|
||||
raw_rows: [],
|
||||
total_groups: 0,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const catalogueJurSlice = createSlice({
|
||||
name: "catalogue_jur",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(getCatalogueJurThunk.pending, (state, _) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(getCatalogueJurThunk.fulfilled, (state, action) => {
|
||||
state.raw_rows = action.payload.items;
|
||||
state.total_groups = action.payload.total;
|
||||
state.loading = false;
|
||||
})
|
||||
.addCase(getCatalogueJurThunk.rejected, (state, action) => {
|
||||
state.error = action.payload ?? "Неизвестная ошибка";
|
||||
state.loading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default catalogueJurSlice.reducer;
|
||||
18
src/store/slices/catalogueJurSlice/thunks.ts
Normal file
18
src/store/slices/catalogueJurSlice/thunks.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { fetchCatalogueJur } from "../../../services/api/directories/catalogue_jur";
|
||||
import { GetCatalogueJurPropsI } from "./types";
|
||||
|
||||
|
||||
|
||||
export const getCatalogueJurThunk = createAsyncThunk(
|
||||
"catalogue_Jur/getAll",
|
||||
async (args:GetCatalogueJurPropsI, thunkApi) => {
|
||||
try {
|
||||
return await fetchCatalogueJur(args);
|
||||
} catch (e) {
|
||||
return thunkApi.rejectWithValue(
|
||||
e instanceof Error ? e.message : "Ошибка",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
14
src/store/slices/catalogueJurSlice/types.ts
Normal file
14
src/store/slices/catalogueJurSlice/types.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { ICatalogueJur } from "../../../types/catalogueJur/catalogueJur";
|
||||
|
||||
export interface CatalogueJurSliceType{
|
||||
total_groups: number;
|
||||
raw_rows: ICatalogueJur[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
|
||||
export interface GetCatalogueJurPropsI{
|
||||
closed?:boolean
|
||||
outside_group?:boolean
|
||||
}
|
||||
@ -2,6 +2,7 @@ import { configureStore } from "@reduxjs/toolkit";
|
||||
import filesReducer from "./slices/fileSlice";
|
||||
import comGroupsReducer from "./slices/companyGroupsSlice/companyGroupsSlice";
|
||||
import consolidationsReducer from "./slices/consolidations/consolidationsSlice";
|
||||
import catalogueJurReducer from "./slices/catalogueJurSlice/catalogueJurSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export const store = configureStore({
|
||||
@ -9,6 +10,7 @@ export const store = configureStore({
|
||||
files: filesReducer,
|
||||
company_groups: comGroupsReducer,
|
||||
consolidations: consolidationsReducer,
|
||||
catalogue_jur:catalogueJurReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
24
src/types/catalogueJur/catalogueJur.ts
Normal file
24
src/types/catalogueJur/catalogueJur.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export interface ICatalogueJur {
|
||||
id: string;
|
||||
inn: string;
|
||||
kpp: string | null;
|
||||
crm_id: string | null;
|
||||
entity_type: string;
|
||||
name: string;
|
||||
company_group_id: string | null;
|
||||
company_group_name: string | null;
|
||||
regional_office_id: string | null;
|
||||
close_date: Date;
|
||||
valid_from: Date;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
outside_group: boolean;
|
||||
closed: boolean | string;
|
||||
memberships?: unknown[];
|
||||
aliases?: unknown[];
|
||||
regional_office: {
|
||||
id: string;
|
||||
name: string;
|
||||
is_head_office: boolean;
|
||||
};
|
||||
}
|
||||
177
src/widgets/CatalogueJurTable/CatalogueJurTable.tsx
Normal file
177
src/widgets/CatalogueJurTable/CatalogueJurTable.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button, Checkbox, Dropdown, Input, Space } from "antd";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import {
|
||||
DataTable,
|
||||
ColumnSettings,
|
||||
useVisibleColumns,
|
||||
useFilteredRows,
|
||||
} from "../../components/GenericTable";
|
||||
import { catalogueJurConfig } from "./config";
|
||||
import {
|
||||
EditOutlined,
|
||||
EllipsisOutlined,
|
||||
HistoryOutlined,
|
||||
LogoutOutlined,
|
||||
SearchOutlined,
|
||||
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 { getCatalogueJurThunk } from "../../store/slices/catalogueJurSlice/thunks";
|
||||
import { MoveCatalogueJurForm } from "../MoveCatalogueJurForm/MoveCatalogueJurForm";
|
||||
|
||||
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 [jurId, setJurId] = useState("");
|
||||
const { visibleKeys, setVisibleKeys } = useVisibleColumns(catalogueJurConfig);
|
||||
const {
|
||||
rawQuery,
|
||||
setRawQuery,
|
||||
columnFilters,
|
||||
setColumnFilters,
|
||||
sort,
|
||||
setSort,
|
||||
resetAll,
|
||||
hasActiveFilters,
|
||||
} = useTableFilters<ICatalogueJur>();
|
||||
// single filtered array — title count and table share it
|
||||
const { rows, totalCount, displayedCount } = useFilteredRows(
|
||||
rawRows,
|
||||
catalogueJurConfig,
|
||||
rawQuery,
|
||||
columnFilters,
|
||||
sort,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getCatalogueJurThunk({ closed: isGettingArchive, outside_group: false }),
|
||||
);
|
||||
}, [isGettingArchive]);
|
||||
|
||||
const extraColumns: ColumnsType<ICatalogueJur> = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: "actions",
|
||||
title: "",
|
||||
width: 30,
|
||||
render: (_: unknown, row: ICatalogueJur) => {
|
||||
const items = [
|
||||
{
|
||||
key: "edit",
|
||||
icon: <EditOutlined />,
|
||||
label: "Изменить ЮЛ",
|
||||
},
|
||||
{
|
||||
key: "history",
|
||||
icon: <HistoryOutlined />,
|
||||
label: "История изменений",
|
||||
onClick: () => navigate("/logging"),
|
||||
},
|
||||
{
|
||||
key: "move",
|
||||
icon: <SwapOutlined />,
|
||||
label: "Перевести в другую ГК",
|
||||
onClick: () => setJurId(row.id),
|
||||
},
|
||||
{
|
||||
key: "withdraw",
|
||||
icon: <LogoutOutlined />,
|
||||
label: "Вывести из состава ГК",
|
||||
danger: true,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Dropdown menu={{ items }} trigger={["click"]}>
|
||||
<Button type="default">
|
||||
<EllipsisOutlined />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[navigate],
|
||||
);
|
||||
console.log(jurId, "test");
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||
<Space
|
||||
style={{
|
||||
padding: "16px 12px",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: "1px",
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder="Поиск по ГК, CRM ID или РФ"
|
||||
value={rawQuery}
|
||||
onChange={(e) => setRawQuery(e.target.value)}
|
||||
style={{ maxWidth: 320 }}
|
||||
/>
|
||||
<Space>
|
||||
{hasActiveFilters && (
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => {
|
||||
resetAll();
|
||||
}}
|
||||
>
|
||||
Сбросить фильтры
|
||||
</Button>
|
||||
)}
|
||||
<ColumnSettings
|
||||
defs={catalogueJurConfig.columns}
|
||||
visibleKeys={visibleKeys}
|
||||
onChange={setVisibleKeys}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={isGettingArchive}
|
||||
onChange={(e) => setIsGettingArchive(e.target.checked)}
|
||||
>
|
||||
Архив
|
||||
</Checkbox>
|
||||
</Space>
|
||||
</Space>
|
||||
|
||||
<DataTable
|
||||
sortState={sort}
|
||||
onSortChange={setSort}
|
||||
config={catalogueJurConfig}
|
||||
rows={rows}
|
||||
allRows={rawRows}
|
||||
loading={loading}
|
||||
visibleKeys={visibleKeys}
|
||||
globalQuery={rawQuery}
|
||||
columnFilters={columnFilters}
|
||||
onColumnFiltersChange={setColumnFilters}
|
||||
extraColumns={extraColumns}
|
||||
/>
|
||||
{!!jurId && (
|
||||
<MoveCatalogueJurForm
|
||||
jurId={jurId}
|
||||
open={!!jurId}
|
||||
onCancel={() => {
|
||||
setJurId("");
|
||||
}}
|
||||
onOk={async (values) => {
|
||||
// await api.create(values)
|
||||
console.log(values, 'test2')
|
||||
setJurId("");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
src/widgets/CatalogueJurTable/config.tsx
Normal file
49
src/widgets/CatalogueJurTable/config.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { Tag } from "antd";
|
||||
import type { TableConfig } from "../../components/GenericTable";
|
||||
import { ICatalogueJur } from "../../types/catalogueJur/catalogueJur";
|
||||
|
||||
export const catalogueJurConfig: TableConfig<ICatalogueJur> = {
|
||||
rowKey: "id",
|
||||
searchableKeys: ["closed", "inn", "kpp", "name", "entity_type"],
|
||||
columns: [
|
||||
{
|
||||
key: "closed",
|
||||
title: "Статус",
|
||||
features: { sort: true },
|
||||
render: (_:unknown, row) =>{
|
||||
// #TODO поменять логику если запись не активна
|
||||
return <Tag variant="outlined" color={'success'}>{!row.closed ? 'Активно' : 'Не активно'}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "inn",
|
||||
title: "ИНН",
|
||||
features: { search: true },
|
||||
},
|
||||
{
|
||||
key: "kpp",
|
||||
title: "КПП",
|
||||
features: { search: true },
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
title: "Организация",
|
||||
features: { search: true, multiselect: true, sort: true },
|
||||
},
|
||||
{
|
||||
key: "entity_type",
|
||||
title: "Тип",
|
||||
features: { multiselect: true, sort: true },
|
||||
render: (val) =>{
|
||||
return <Tag variant="outlined" color={val === 'ЮЛ' ? 'purple' : 'orange'}>{val as string}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "regional_office",
|
||||
title: "Основной РФ",
|
||||
render: (val:any) =>{
|
||||
return val?.name
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
131
src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx
Normal file
131
src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
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 { 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";
|
||||
|
||||
type FormValues = {
|
||||
name: string;
|
||||
company_group_name: string;
|
||||
company_group_id: string;
|
||||
effective_date: string;
|
||||
old_company_group_id: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onOk: (values: FormValues) => void | Promise<void>;
|
||||
jurId: string;
|
||||
};
|
||||
|
||||
export const MoveCatalogueJurForm: React.FC<Props> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onOk,
|
||||
jurId,
|
||||
}) => {
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { raw_rows, loading } = useAppSelector((state) => state.company_groups);
|
||||
const [initialDataLoading, setInitialDataLoading] = useState(false);
|
||||
async function getInitialData() {
|
||||
setInitialDataLoading(true);
|
||||
const data = await fetchOneCatalogueJur(jurId);
|
||||
form.setFieldsValue({
|
||||
name: data?.name,
|
||||
company_group_name: data?.company_group_name ?? "",
|
||||
old_company_group_id: data?.company_group_id ?? "",
|
||||
company_group_id: "",
|
||||
effective_date: "",
|
||||
});
|
||||
setInitialDataLoading(false);
|
||||
}
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
await onOk(values);
|
||||
form.resetFields();
|
||||
} catch {
|
||||
// validation error - keep modal open
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
};
|
||||
useEffect(() => {
|
||||
if (open && raw_rows.length === 0 && !loading) {
|
||||
dispatch(getCompanyGroupsThunk());
|
||||
}
|
||||
}, [open, raw_rows.length, loading, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
getInitialData();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Перевести ЮЛ в другую ГК"
|
||||
open={open}
|
||||
onCancel={handleCancel}
|
||||
onOk={handleOk}
|
||||
okText="+ Создать"
|
||||
cancelText="Отмена"
|
||||
destroyOnHidden
|
||||
loading={initialDataLoading}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<FormItem name="name" label="ЮЛ">
|
||||
<Input disabled />
|
||||
</FormItem>
|
||||
<FormItem name="company_group_name" label="Текущая ГК">
|
||||
<Input disabled />
|
||||
</FormItem>
|
||||
<Form.Item
|
||||
name="company_group_id"
|
||||
label="Новая ГК"
|
||||
rules={[{ required: true, message: "Укажите группу" }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="Выбрать"
|
||||
loading={loading}
|
||||
showSearch
|
||||
allowClear
|
||||
options={raw_rows
|
||||
.filter(
|
||||
(row) => row.id !== form.getFieldValue("old_company_group_id"),
|
||||
)
|
||||
.map((g) => ({ value: g.id, label: g.name }))}
|
||||
notFoundContent={loading ? "Загрузка" : "Нет вариантов"}
|
||||
/>
|
||||
</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>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user