feat: add UL/IP auto-check in consolidation form, stub consolidation view, fix sidebar markup
This commit is contained in:
parent
8747f6318c
commit
85f654406a
@ -10,6 +10,7 @@ import {
|
||||
CatalogueGroup,
|
||||
CatalogueJur,
|
||||
ConsolidationPage,
|
||||
ConsolidationView,
|
||||
DocStorage,
|
||||
GroupsLayout,
|
||||
Main,
|
||||
@ -41,6 +42,10 @@ function App() {
|
||||
<Route path="/files/:file_id" element={<FileViewer />} />
|
||||
<Route path="/docstorage" element={<DocStorage />} />
|
||||
<Route path="/logging" element={<AuditLogging />} />
|
||||
<Route
|
||||
path="/consolidations/:cons_id"
|
||||
element={<ConsolidationView />}
|
||||
/>
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
|
||||
97
src/layouts/ConsolidationView.tsx
Normal file
97
src/layouts/ConsolidationView.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { useAppSelector, useAppDispatch } from "../store/store";
|
||||
import PageTitle from "../components/PageTitle";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { api } from "../api/client";
|
||||
import { Button, Tag } from "antd";
|
||||
import { TableTag } from "../components/TableTag/TableTag";
|
||||
import { TAG_PRESETS } from "../components/TableTag/presets";
|
||||
|
||||
export function ConsolidationView() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { cons_id } = useParams();
|
||||
// const files = useAppSelector((state) => state.files.currentOpenFiles);
|
||||
const navigate = useNavigate();
|
||||
const [versionData, setVersionData] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
useEffect(() => {
|
||||
async function fetchVersionCard() {
|
||||
if (!cons_id) return;
|
||||
setLoading(true);
|
||||
const res = await api.get(`/consolidations/versions/${cons_id}`);
|
||||
const another_res = await api.get(
|
||||
`/consolidations/versions/${cons_id}/osv`,
|
||||
);
|
||||
setVersionData(res);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchVersionCard();
|
||||
}, [cons_id]);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const desc = useMemo(() => {
|
||||
return "";
|
||||
}, []);
|
||||
const headerContent = useMemo(() => {
|
||||
if (!versionData) return { location: "", header: "" };
|
||||
const year = versionData.year;
|
||||
const quarter = versionData.quarter;
|
||||
const company_name = versionData.company_group.name;
|
||||
let header = `${company_name} - ${year} ${quarter}`;
|
||||
let location = `Отчётность > ${company_name} > ${year} · ${quarter}`;
|
||||
console.log({ header, location });
|
||||
return { header, location };
|
||||
}, [versionData]);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 12,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
{!loading && versionData !== null && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
// alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<PageTitle {...headerContent} description={desc} backable />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "baseline",
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "rgb(102, 112, 133)" }}>Статус отчета</span>
|
||||
<TableTag
|
||||
{...TAG_PRESETS.green}
|
||||
style={{ fontSize: 11 }}
|
||||
text={"Верифицирован"}
|
||||
/>
|
||||
|
||||
<Button type="default" disabled size="small">
|
||||
Изменить статус
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<main
|
||||
style={{
|
||||
flex: "1 1 auto",
|
||||
minHeight: 0,
|
||||
overflow: "auto",
|
||||
backgroundColor: "white",
|
||||
borderRadius: 12,
|
||||
}}
|
||||
></main>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
export { ConsolidationView } from "./ConsolidationView";
|
||||
export { Main } from "./Main";
|
||||
export { CatalogueJur } from "./CatalogueJur";
|
||||
export { ConsolidationPage } from "./ConsolidationsList";
|
||||
|
||||
@ -21,21 +21,16 @@ export async function fetchGSKByComGroup(com_group_id: string) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// export async function fetchGroupMembers(related_group_id: string) {
|
||||
// const res = await api.get(`/directories/related-groups/${related_group_id}`);
|
||||
// const members = res?.members ?? [];
|
||||
// console.log(members);
|
||||
// return members;
|
||||
// }
|
||||
export async function fetchGroupMembers(related_group_id: string) {
|
||||
const res = await api.get(`/directories/related-groups/${related_group_id}`);
|
||||
const members = res?.members ?? [];
|
||||
return members;
|
||||
}
|
||||
|
||||
// legal-entities?company_group_id=<ГК>&closed=false
|
||||
|
||||
export async function fetchGroupMembers(company_group_id: string) {
|
||||
export async function fetchLegEntsByComGroup(company_group_id: string) {
|
||||
const res = await api.get(
|
||||
`/directories/legal-entities?company_group_id=${company_group_id}&closed=false`,
|
||||
);
|
||||
console.log(res);
|
||||
const members = res?.items ?? [];
|
||||
console.log(members);
|
||||
return members;
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ export function ConsoleTableWidget() {
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => navigate(`/cons/${row.con_id}/versions/${row.id}`)}
|
||||
onClick={() => navigate(`/consolidations/${row.id}`)}
|
||||
>
|
||||
К отчетности <ArrowRightOutlined />
|
||||
</Button>
|
||||
|
||||
165
src/widgets/FilesDrawer/FilesTable.tsx
Normal file
165
src/widgets/FilesDrawer/FilesTable.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { Table, Tag } from "antd";
|
||||
import {
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
QuestionOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { TableTag } from "../../components/TableTag/TableTag";
|
||||
import { TAG_PRESETS } from "../../components/TableTag/presets";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
type Status = "MATCH" | "MISMATCH" | "UNKNOWN";
|
||||
type OverallStatus = "OK" | "ERROR" | "CHECK";
|
||||
|
||||
interface Row {
|
||||
name: string;
|
||||
check_status: Status;
|
||||
file_status: Status;
|
||||
overall_status: OverallStatus;
|
||||
}
|
||||
|
||||
// Map a Status to its icon + color. Single source of truth.
|
||||
const STATUS_CONFIG: Record<Status, { color: string; icon: React.ReactNode }> =
|
||||
{
|
||||
MATCH: { color: "#52c41a", icon: <CheckOutlined /> }, // green, 'v'
|
||||
MISMATCH: { color: "#ff4d4f", icon: <CloseOutlined /> }, // red, '*'
|
||||
UNKNOWN: { color: "#1677ff", icon: <QuestionOutlined /> }, // blue, '?'
|
||||
};
|
||||
|
||||
const OVERALL_COLOR: Record<OverallStatus, string> = {
|
||||
OK: TAG_PRESETS.green,
|
||||
ERROR: TAG_PRESETS.red,
|
||||
CHECK: TAG_PRESETS.orange,
|
||||
};
|
||||
|
||||
// One stacked line: icon + label, colored by its own status.
|
||||
function StatusLine({ label, status }: { label: string; status: Status }) {
|
||||
const { color, icon } = STATUS_CONFIG[status];
|
||||
return (
|
||||
<span
|
||||
style={{ color, display: "inline-flex", alignItems: "center", gap: 4 }}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const columns: ColumnsType<Row> = [
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "Overall",
|
||||
dataIndex: "overall_status",
|
||||
key: "overall_status",
|
||||
render: (value: OverallStatus) => (
|
||||
<TableTag {...OVERALL_COLOR[value]} text={value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Statuses",
|
||||
key: "simple_statuses",
|
||||
// No dataIndex — we read the whole record from the second arg.
|
||||
render: (_, record) => (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
<StatusLine label="Check" status={record.check_status} />
|
||||
<StatusLine label="File" status={record.file_status} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// Red background when any sub-status is MISMATCH or UNKNOWN.
|
||||
function rowNeedsHighlight(row: Row): boolean {
|
||||
const flagged: (Status | OverallStatus)[] = ["MISMATCH", "UNKNOWN", "ERROR"];
|
||||
return (
|
||||
flagged.includes(row.check_status) ||
|
||||
flagged.includes(row.file_status) ||
|
||||
flagged.includes(row.overall_status)
|
||||
);
|
||||
}
|
||||
|
||||
const mockData = [
|
||||
{
|
||||
key: "1",
|
||||
name: "document_a.pdf",
|
||||
check_status: "MATCH",
|
||||
file_status: "MATCH",
|
||||
overall_status: "OK",
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
name: "document_b.pdf",
|
||||
check_status: "MISMATCH",
|
||||
file_status: "MATCH",
|
||||
overall_status: "ERROR",
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
name: "document_c.xlsx",
|
||||
check_status: "MISMATCH",
|
||||
file_status: "MISMATCH",
|
||||
overall_status: "ERROR",
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
name: "document_d.docx",
|
||||
check_status: "MATCH",
|
||||
file_status: "MISMATCH",
|
||||
overall_status: "ERROR",
|
||||
},
|
||||
{
|
||||
key: "5",
|
||||
name: "document_e.pdf",
|
||||
check_status: "UNKNOWN",
|
||||
file_status: "MATCH",
|
||||
overall_status: "CHECK",
|
||||
},
|
||||
{
|
||||
key: "6",
|
||||
name: "document_f.pdf",
|
||||
check_status: "MATCH",
|
||||
file_status: "UNKNOWN",
|
||||
overall_status: "CHECK",
|
||||
},
|
||||
{
|
||||
key: "7",
|
||||
name: "document_g.pdf",
|
||||
check_status: "UNKNOWN",
|
||||
file_status: "UNKNOWN",
|
||||
overall_status: "CHECK",
|
||||
},
|
||||
{
|
||||
key: "8",
|
||||
name: "document_h.pdf",
|
||||
check_status: "MISMATCH",
|
||||
file_status: "UNKNOWN",
|
||||
overall_status: "ERROR",
|
||||
},
|
||||
{
|
||||
key: "9",
|
||||
name: "document_i.pdf",
|
||||
check_status: "UNKNOWN",
|
||||
file_status: "MISMATCH",
|
||||
overall_status: "ERROR",
|
||||
},
|
||||
];
|
||||
|
||||
export function FilesTable() {
|
||||
return (
|
||||
<Table<Row>
|
||||
columns={columns}
|
||||
dataSource={mockData}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
rowHoverable={false}
|
||||
rowClassName={(record) =>
|
||||
rowNeedsHighlight(record) ? styles.row_highlight : ""
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
8
src/widgets/FilesDrawer/styles.module.css
Normal file
8
src/widgets/FilesDrawer/styles.module.css
Normal file
@ -0,0 +1,8 @@
|
||||
.row_highlight > td {
|
||||
background-color: #fff1f0; /* soft red; use #ffccc7 for stronger */
|
||||
}
|
||||
|
||||
/* Preserve the highlight on hover instead of Antd's default grey */
|
||||
row_highlight:hover > td {
|
||||
background-color: #ffe0dd !important;
|
||||
}
|
||||
@ -11,8 +11,9 @@ import {
|
||||
Spin,
|
||||
} from "antd";
|
||||
import {
|
||||
fetchGroupMembers,
|
||||
fetchLegEntsByComGroup,
|
||||
fetchGSKByComGroup,
|
||||
fetchGroupMembers,
|
||||
} from "../../services/api/directories/company_groups";
|
||||
import { useAppDispatch, useAppSelector } from "../../store/store";
|
||||
import { getCompanyGroupsThunk } from "../../store/slices/companyGroupsSlice/thunks";
|
||||
@ -46,6 +47,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||
const legEntReqRef = useRef(0);
|
||||
|
||||
const [legEntSearch, setLegEntSearch] = useState("");
|
||||
const gskEntReqRef = useRef(0);
|
||||
|
||||
const company_group_id = Form.useWatch("company_group_id", form);
|
||||
const related_group_id = Form.useWatch("related_group_id", form);
|
||||
@ -105,19 +107,28 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleSubGroupChange = async (value?: string) => {
|
||||
// form.setFieldsValue({ legal_entity_ids: [] });
|
||||
// clearLegEnts();
|
||||
// if (!value) return;
|
||||
// const reqId = ++legEntReqRef.current;
|
||||
// setLoadingEntities(true);
|
||||
// try {
|
||||
// const data = await fetchGroupMembers(value);
|
||||
// if (reqId === legEntReqRef.current) setLegEntities(data);
|
||||
// } finally {
|
||||
// if (reqId === legEntReqRef.current) setLoadingEntities(false);
|
||||
// }
|
||||
// };
|
||||
const handleSubGroupChange = async (value?: string) => {
|
||||
form.setFieldsValue({ legal_entity_ids: [] });
|
||||
if (!value) return;
|
||||
const reqId = ++gskEntReqRef.current;
|
||||
setLoadingEntities(true);
|
||||
try {
|
||||
const data = await fetchGroupMembers(value);
|
||||
|
||||
if (reqId !== gskEntReqRef.current) return;
|
||||
const gskEntitiesIds = new Set(data.map((c) => c.legal_entity_id));
|
||||
const sorted = [...legEntities].sort((a, b) => {
|
||||
const a_gsk = gskEntitiesIds.has(a.id);
|
||||
const b_gsk = gskEntitiesIds.has(b.id);
|
||||
if (a_gsk !== b_gsk) return a_gsk ? -1 : 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
setLegEntities(sorted);
|
||||
form.setFieldsValue({ legal_entity_ids: [...gskEntitiesIds] });
|
||||
} finally {
|
||||
if (reqId === gskEntReqRef.current) setLoadingEntities(false);
|
||||
}
|
||||
};
|
||||
const handleGroupChange = async (value?: string) => {
|
||||
form.setFieldsValue({
|
||||
related_group_id: undefined as any,
|
||||
@ -132,7 +143,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||
setLoadingEntities(true);
|
||||
try {
|
||||
const dataSubgroups = await fetchGSKByComGroup(value);
|
||||
const dataLegEnts = await fetchGroupMembers(value);
|
||||
const dataLegEnts = await fetchLegEntsByComGroup(value);
|
||||
if (reqId === subgroupReqRef.current) setSubgroups(dataSubgroups);
|
||||
if (leReqId === legEntReqRef.current) setLegEntities(dataLegEnts);
|
||||
} finally {
|
||||
@ -181,12 +192,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||
destroyOnHidden
|
||||
width={640}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
preserve={false}
|
||||
initialValues={{ legEntIds: [] }}
|
||||
>
|
||||
<Form form={form} layout="vertical" preserve={false}>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
@ -229,7 +235,7 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||
.includes(input.toLowerCase())
|
||||
}
|
||||
options={subgroups.map((s) => ({ value: s.id, label: s.name }))}
|
||||
// onChange={handleSubGroupChange}
|
||||
onChange={handleSubGroupChange}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@ -289,8 +295,8 @@ export const MyModal: React.FC<Props> = ({ open, onCancel, onOk }) => {
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={
|
||||
legEntities.length === 0
|
||||
? "No students"
|
||||
: "No matches"
|
||||
? "Нет строк"
|
||||
: "Нет совпадений"
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@ -33,7 +33,6 @@
|
||||
|
||||
.section {
|
||||
margin-bottom: 0px;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@ -75,6 +74,7 @@
|
||||
|
||||
.linkBase {
|
||||
display: inline-flex;
|
||||
font-size: 13px;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
width: 100%;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user