addded legalEntities table and move form
This commit is contained in:
parent
81548eb4ae
commit
8e8e46ecbc
@ -1,13 +1,13 @@
|
|||||||
import { clearToken, getToken } from '../auth/token';
|
import { clearToken, getToken } from "../auth/token";
|
||||||
import { Config } from '../conf/config';
|
import { Config } from "../conf/config";
|
||||||
|
|
||||||
// Хвостовые слэши срезаются: API_URL='/' иначе дал бы `//api/v1` (адрес без хоста)
|
// Хвостовые слэши срезаются: 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, токен сброшен — интерфейс должен вернуться к входу. */
|
/** Событие `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 {
|
export class ApiError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
@ -16,14 +16,25 @@ export class ApiError extends Error {
|
|||||||
message: string,
|
message: string,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'ApiError';
|
this.name = "ApiError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorInfo = { code: string | undefined; message: string };
|
type ErrorInfo = { code: string | undefined; message: string };
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
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}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,17 +44,18 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
*/
|
*/
|
||||||
function parseErrorBody(body: unknown): ErrorInfo {
|
function parseErrorBody(body: unknown): ErrorInfo {
|
||||||
const detail = isRecord(body) ? body.detail : undefined;
|
const detail = isRecord(body) ? body.detail : undefined;
|
||||||
if (typeof detail === 'string') {
|
if (typeof detail === "string") {
|
||||||
return { code: undefined, message: detail };
|
return { code: undefined, message: detail };
|
||||||
}
|
}
|
||||||
if (isRecord(detail)) {
|
if (isRecord(detail)) {
|
||||||
const code = typeof detail.code === 'string' ? detail.code : undefined;
|
const code = typeof detail.code === "string" ? detail.code : undefined;
|
||||||
const message = typeof detail.message === 'string' ? detail.message : FALLBACK_MESSAGE;
|
const message =
|
||||||
|
typeof detail.message === "string" ? detail.message : FALLBACK_MESSAGE;
|
||||||
return { code, message };
|
return { code, message };
|
||||||
}
|
}
|
||||||
if (Array.isArray(detail)) {
|
if (Array.isArray(detail)) {
|
||||||
const first: unknown = detail[0];
|
const first: unknown = detail[0];
|
||||||
if (isRecord(first) && typeof first.msg === 'string') {
|
if (isRecord(first) && typeof first.msg === "string") {
|
||||||
return { code: undefined, message: first.msg };
|
return { code: undefined, message: first.msg };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,15 +71,18 @@ async function readErrorInfo(response: Response): Promise<ErrorInfo> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
|
export async function apiFetch<T>(
|
||||||
|
path: string,
|
||||||
|
init: RequestInit = {},
|
||||||
|
): Promise<T> {
|
||||||
const headers = new Headers(init.headers);
|
const headers = new Headers(init.headers);
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
headers.set('Authorization', `Bearer ${token}`);
|
headers.set("Authorization", `Bearer ${token}`);
|
||||||
}
|
}
|
||||||
// JSON-заголовок только для строкового тела; FormData и др. отдаём браузеру как есть (он сам задаёт boundary)
|
// JSON-заголовок только для строкового тела; FormData и др. отдаём браузеру как есть (он сам задаёт boundary)
|
||||||
if (typeof init.body === 'string' && !headers.has('Content-Type')) {
|
if (typeof init.body === "string" && !headers.has("Content-Type")) {
|
||||||
headers.set('Content-Type', 'application/json');
|
headers.set("Content-Type", "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
@ -75,10 +90,10 @@ export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise
|
|||||||
response = await fetch(`${API_BASE}${path}`, { ...init, headers });
|
response = await fetch(`${API_BASE}${path}`, { ...init, headers });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
// отмену запроса (AbortController) вызывающий код обрабатывает сам; остальное — сеть/CORS/DNS: текст браузера не показываем
|
// отмену запроса (AbortController) вызывающий код обрабатывает сам; остальное — сеть/CORS/DNS: текст браузера не показываем
|
||||||
if (e instanceof DOMException && e.name === 'AbortError') {
|
if (e instanceof DOMException && e.name === "AbortError") {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
throw new ApiError(0, 'NETWORK', 'Нет связи с сервером');
|
throw new ApiError(0, "NETWORK", "Нет связи с сервером");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
@ -96,24 +111,34 @@ export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise
|
|||||||
}
|
}
|
||||||
// 2xx с пустым или не-JSON телом (страница прокси, 200 без содержимого) не должен давать «сырой» SyntaxError
|
// 2xx с пустым или не-JSON телом (страница прокси, 200 без содержимого) не должен давать «сырой» SyntaxError
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
if (text === '') {
|
if (text === "") {
|
||||||
return undefined as T;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(text) as T;
|
return JSON.parse(text) as T;
|
||||||
} catch {
|
} catch {
|
||||||
throw new ApiError(response.status, 'BAD_RESPONSE', 'Некорректный ответ сервера');
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
"BAD_RESPONSE",
|
||||||
|
"Некорректный ответ сервера",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function withJsonBody(method: string, body: unknown): RequestInit {
|
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 = {
|
export const api = {
|
||||||
get: <T>(path: string) => apiFetch<T>(path),
|
get: <T>(path: string, params?:object) =>
|
||||||
post: <T>(path: string, body?: unknown) => apiFetch<T>(path, withJsonBody('POST', body)),
|
apiFetch<T>(`${path}${buildQuery(params)}`),
|
||||||
patch: <T>(path: string, body?: unknown) => apiFetch<T>(path, withJsonBody('PATCH', body)),
|
post: <T>(path: string, body?: unknown) =>
|
||||||
put: <T>(path: string, body?: unknown) => apiFetch<T>(path, withJsonBody('PUT', body)),
|
apiFetch<T>(path, withJsonBody("POST", body)),
|
||||||
del: <T>(path: string) => apiFetch<T>(path, { method: 'DELETE' }),
|
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 "./index.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { ConfigProvider } from "antd";
|
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(
|
createRoot(document.getElementById("root")!).render(
|
||||||
// <StrictMode>
|
// <StrictMode>
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
|
locale={locale}
|
||||||
theme={{
|
theme={{
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: "rgba(46, 166, 75, 0.8)",
|
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() {
|
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 filesReducer from "./slices/fileSlice";
|
||||||
import comGroupsReducer from "./slices/companyGroupsSlice/companyGroupsSlice";
|
import comGroupsReducer from "./slices/companyGroupsSlice/companyGroupsSlice";
|
||||||
import consolidationsReducer from "./slices/consolidations/consolidationsSlice";
|
import consolidationsReducer from "./slices/consolidations/consolidationsSlice";
|
||||||
|
import catalogueJurReducer from "./slices/catalogueJurSlice/catalogueJurSlice";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
@ -9,6 +10,7 @@ export const store = configureStore({
|
|||||||
files: filesReducer,
|
files: filesReducer,
|
||||||
company_groups: comGroupsReducer,
|
company_groups: comGroupsReducer,
|
||||||
consolidations: consolidationsReducer,
|
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