From 85f654406aa158212cd662bc0425630b3aec827a Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 8 Oct 2026 05:00:11 +0300 Subject: [PATCH] feat: add UL/IP auto-check in consolidation form, stub consolidation view, fix sidebar markup --- src/App.tsx | 5 + src/layouts/ConsolidationView.tsx | 97 ++++++++++ src/layouts/index.ts | 1 + .../api/directories/company_groups.ts | 17 +- src/widgets/ConsoleTable/ConsoleTable.tsx | 2 +- src/widgets/FilesDrawer/FilesTable.tsx | 165 ++++++++++++++++++ src/widgets/FilesDrawer/styles.module.css | 8 + .../NewConsoleModal/NewConsoleModal.tsx | 54 +++--- src/widgets/Sidebar/styles.module.css | 2 +- 9 files changed, 314 insertions(+), 37 deletions(-) create mode 100644 src/layouts/ConsolidationView.tsx create mode 100644 src/widgets/FilesDrawer/FilesTable.tsx create mode 100644 src/widgets/FilesDrawer/styles.module.css diff --git a/src/App.tsx b/src/App.tsx index 4244953..418b7c9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import { CatalogueGroup, CatalogueJur, ConsolidationPage, + ConsolidationView, DocStorage, GroupsLayout, Main, @@ -41,6 +42,10 @@ function App() { } /> } /> } /> + } + /> diff --git a/src/layouts/ConsolidationView.tsx b/src/layouts/ConsolidationView.tsx new file mode 100644 index 0000000..97ea209 --- /dev/null +++ b/src/layouts/ConsolidationView.tsx @@ -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 ( +
+ {!loading && versionData !== null && ( + <> +
+ +
+ Статус отчета + + + +
+
+
+ + )} +
+ ); +} diff --git a/src/layouts/index.ts b/src/layouts/index.ts index 25eb9e0..cd337a3 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -1,3 +1,4 @@ +export { ConsolidationView } from "./ConsolidationView"; export { Main } from "./Main"; export { CatalogueJur } from "./CatalogueJur"; export { ConsolidationPage } from "./ConsolidationsList"; diff --git a/src/services/api/directories/company_groups.ts b/src/services/api/directories/company_groups.ts index 6cf1837..aefbd34 100644 --- a/src/services/api/directories/company_groups.ts +++ b/src/services/api/directories/company_groups.ts @@ -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; } diff --git a/src/widgets/ConsoleTable/ConsoleTable.tsx b/src/widgets/ConsoleTable/ConsoleTable.tsx index ff911da..fae440e 100644 --- a/src/widgets/ConsoleTable/ConsoleTable.tsx +++ b/src/widgets/ConsoleTable/ConsoleTable.tsx @@ -169,7 +169,7 @@ export function ConsoleTableWidget() { diff --git a/src/widgets/FilesDrawer/FilesTable.tsx b/src/widgets/FilesDrawer/FilesTable.tsx new file mode 100644 index 0000000..74305f5 --- /dev/null +++ b/src/widgets/FilesDrawer/FilesTable.tsx @@ -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 = + { + MATCH: { color: "#52c41a", icon: }, // green, 'v' + MISMATCH: { color: "#ff4d4f", icon: }, // red, '*' + UNKNOWN: { color: "#1677ff", icon: }, // blue, '?' + }; + +const OVERALL_COLOR: Record = { + 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 ( + + {icon} + {label} + + ); +} + +const columns: ColumnsType = [ + { + title: "Name", + dataIndex: "name", + key: "name", + }, + { + title: "Overall", + dataIndex: "overall_status", + key: "overall_status", + render: (value: OverallStatus) => ( + + ), + }, + { + title: "Statuses", + key: "simple_statuses", + // No dataIndex — we read the whole record from the second arg. + render: (_, record) => ( +
+ + +
+ ), + }, +]; + +// 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 ( + + columns={columns} + dataSource={mockData} + pagination={false} + rowKey="name" + rowHoverable={false} + rowClassName={(record) => + rowNeedsHighlight(record) ? styles.row_highlight : "" + } + /> + ); +} diff --git a/src/widgets/FilesDrawer/styles.module.css b/src/widgets/FilesDrawer/styles.module.css new file mode 100644 index 0000000..d575e0c --- /dev/null +++ b/src/widgets/FilesDrawer/styles.module.css @@ -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; +} \ No newline at end of file diff --git a/src/widgets/NewConsoleModal/NewConsoleModal.tsx b/src/widgets/NewConsoleModal/NewConsoleModal.tsx index 6dd76b2..040f827 100644 --- a/src/widgets/NewConsoleModal/NewConsoleModal.tsx +++ b/src/widgets/NewConsoleModal/NewConsoleModal.tsx @@ -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 = ({ 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 = ({ 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 = ({ 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 = ({ open, onCancel, onOk }) => { destroyOnHidden width={640} > -
+ = ({ open, onCancel, onOk }) => { .includes(input.toLowerCase()) } options={subgroups.map((s) => ({ value: s.id, label: s.name }))} - // onChange={handleSubGroupChange} + onChange={handleSubGroupChange} /> @@ -289,8 +295,8 @@ export const MyModal: React.FC = ({ open, onCancel, onOk }) => { image={Empty.PRESENTED_IMAGE_SIMPLE} description={ legEntities.length === 0 - ? "No students" - : "No matches" + ? "Нет строк" + : "Нет совпадений" } /> ) : ( diff --git a/src/widgets/Sidebar/styles.module.css b/src/widgets/Sidebar/styles.module.css index 85fee50..f28dbc6 100644 --- a/src/widgets/Sidebar/styles.module.css +++ b/src/widgets/Sidebar/styles.module.css @@ -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%; -- 2.47.2