From 99e7a445cf8a87fa4f497ab051b1a28ecc4ee77b Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Fri, 29 May 2026 11:23:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/api/form_sheet.js | 5 +- web/src/app/Routes.jsx | 2 +- .../RealtimeTable/RealtimeTable.jsx | 22 ++++++-- .../SettingPanel/SettingPanel.jsx | 11 ++-- .../constants/FORM_1/{AHR_P.js => AHR.js} | 0 .../RealtimeTable/contexts/RealtimeContext.js | 20 +++----- .../RealtimeTable/hooks/useRealtimeData.js | 33 +++++++++--- .../RealtimeTable/utils/cellUtils.js | 51 +++++++++++++++---- .../components/common/TableCard/TableCard.jsx | 5 +- .../components/common/TableList/TableList.jsx | 2 +- web/src/pages/NewFormTablePage.jsx | 5 +- web/src/pages/TaskPage/TaskPage.jsx | 34 +++++++------ 12 files changed, 130 insertions(+), 60 deletions(-) rename web/src/components/RealtimeTable/constants/FORM_1/{AHR_P.js => AHR.js} (100%) diff --git a/web/src/api/form_sheet.js b/web/src/api/form_sheet.js index 0c1771b..9213a35 100644 --- a/web/src/api/form_sheet.js +++ b/web/src/api/form_sheet.js @@ -1,10 +1,9 @@ import api from './client'; export const FormsSheetApi = { - get(formId, sheetName) { + get(formId, sheetName, params) { return api - .get(`/form/${formId}/sheet/${sheetName}`) - // .get(`/forms/${form}/sheet/${sheetName}`) + .get(`/form/${formId}/sheet/${sheetName}`,params) .then((r) => r.data); }, }; diff --git a/web/src/app/Routes.jsx b/web/src/app/Routes.jsx index 74414ab..4c26684 100644 --- a/web/src/app/Routes.jsx +++ b/web/src/app/Routes.jsx @@ -50,7 +50,7 @@ export const AppRoutes = () => { } /> } /> } /> - } /> + } /> diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 063dbd3..1cdf94b 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -21,10 +21,9 @@ import { getTablePaperStyles, } from './constants/tableConfig'; import { useRealtime } from './contexts/RealtimeContext'; -import { setNewValueToData, updateExistingStructure } from './utils/cellUtils'; -const RealtimeTable = ({ formType, formId, sheetName }) => { - const { data, setData } = useRealtimeData(formId, sheetName); +const RealtimeTable = ({ formType, formId, sheetName, direction }) => { + const { data, setData } = useRealtimeData(formId, sheetName, direction); const [isLoading, setIsLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [showColumnFilters, setShowColumnFilters] = useState(false); @@ -39,7 +38,8 @@ const RealtimeTable = ({ formType, formId, sheetName }) => { subscribeToErrors, subscribeToAuthSuccess, error: wsError, - updateCell: contextUpdateCell + updateCell: contextUpdateCell, + addRow: contextAddRow, } = useRealtime(); const { @@ -196,6 +196,19 @@ const RealtimeTable = ({ formType, formId, sheetName }) => { const table = useMaterialReactTable(tableConfig); + const handleAddRow = useCallback(() => { + const allRows = table.getRowModel().rows; + + // Фильтруем выделенные строки + const selectedRows = allRows.filter(row => rowSelection[row.id]); + if (selectedRows.length === 0) { + toasr.error('Выделите строку для вставки'); + return; + } + const row = selectedRows[0]; + const expense_item_id = row.original.data.header.expense_item_id; + contextAddRow({expense_item_id:expense_item_id}) + }, [rowSelection, table]) if (!data) { return
Loading...
; } @@ -213,6 +226,7 @@ const RealtimeTable = ({ formType, formId, sheetName }) => { onGlobalFilterChange={setGlobalFilter} sizeMult={sizeMult} onChangeShowColumnFilters={setShowColumnFilters} + onAddRow={handleAddRow} />
diff --git a/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx b/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx index 7b4b12a..225e5fe 100644 --- a/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx +++ b/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx @@ -29,6 +29,7 @@ const SettingPanel = ({ onGlobalFilterChange, sizeMult, onChangeShowColumnFilters, + onAddRow, }) => { const [isPinned, setIsPinned] = useState(false); const [columnTree, setColumnTree] = useState(); @@ -54,7 +55,7 @@ const SettingPanel = ({ ); }, [columnVisibility]); - const { addRow, addColorForCells, addFormulaForCells, deleteFormula } = {}; + const { addColorForCells, addFormulaForCells, deleteFormula } = {}; const handleChangeShowColumnFilters = () => { const show = table.getState().showColumnFilters; @@ -62,14 +63,14 @@ const SettingPanel = ({ onChangeShowColumnFilters(!show); }; - const handleClickAddFormula = () => {}; + const handleClickAddFormula = () => { }; - const handleClickDeleteFormula = () => {}; + const handleClickDeleteFormula = () => { }; return ( <> - + @@ -92,7 +93,7 @@ const SettingPanel = ({ - + diff --git a/web/src/components/RealtimeTable/constants/FORM_1/AHR_P.js b/web/src/components/RealtimeTable/constants/FORM_1/AHR.js similarity index 100% rename from web/src/components/RealtimeTable/constants/FORM_1/AHR_P.js rename to web/src/components/RealtimeTable/constants/FORM_1/AHR.js diff --git a/web/src/components/RealtimeTable/contexts/RealtimeContext.js b/web/src/components/RealtimeTable/contexts/RealtimeContext.js index 500ee91..b7bd552 100644 --- a/web/src/components/RealtimeTable/contexts/RealtimeContext.js +++ b/web/src/components/RealtimeTable/contexts/RealtimeContext.js @@ -33,15 +33,14 @@ export const RealtimeProvider = ({ const handleMessage = useCallback((data) => { // Обработка ошибок if (data.error) { - toast.error("Server error:", data.error) + toast.error("Server error:", data.error); onErrorRef.current?.(data.error); return; } - // Обработка событий switch (data.event) { case "cell_updated": - console.log(onCellUpdateRef.current) + console.log(onCellUpdateRef.current); if (data.result && onCellUpdateRef.current) { const updatedCells = Array.isArray(data.result) ? data.result @@ -52,7 +51,7 @@ export const RealtimeProvider = ({ case "row_added": if (data.result && onRowAddRef.current) { - onRowAddRef.current(data.result); + onRowAddRef.current(data); } break; @@ -145,7 +144,6 @@ export const RealtimeProvider = ({ return false; } - const lineId = row.original?.data?.line_id || null; const colId = columnId.slice(5); const message = { @@ -156,7 +154,6 @@ export const RealtimeProvider = ({ value: value, }, }; - console.log(message); const sent = sendMessage(message); if (!sent) { @@ -165,10 +162,7 @@ export const RealtimeProvider = ({ return true; }, - [ - isConnected, - sendMessage, - ], + [isConnected, sendMessage], ); const addRow = useCallback( @@ -181,8 +175,10 @@ export const RealtimeProvider = ({ const message = { event: "row_added", data: rowData, + sheet: sheetName, + form_id: formId, + direction: direction, }; - return sendMessage(message); }, [isConnected, sendMessage], @@ -190,7 +186,7 @@ export const RealtimeProvider = ({ const deleteRow = useCallback( async (rowId) => { - if (!isConnected ) { + if (!isConnected) { onErrorRef.current?.("Нет подключения или авторизации"); return false; } diff --git a/web/src/components/RealtimeTable/hooks/useRealtimeData.js b/web/src/components/RealtimeTable/hooks/useRealtimeData.js index 857f434..efa64df 100644 --- a/web/src/components/RealtimeTable/hooks/useRealtimeData.js +++ b/web/src/components/RealtimeTable/hooks/useRealtimeData.js @@ -1,9 +1,9 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { FormsSheetApi } from "../../../api/form_sheet"; import { useRealtime } from "../contexts/RealtimeContext"; -import { updateExistingStructure } from "../utils/cellUtils"; +import { insertCell, updateCells } from "../utils/cellUtils"; -const useRealtimeData = (formId, sheetName) => { +const useRealtimeData = (formId, sheetName, direction) => { const [data, setData] = useState([]); const [isConnected, setIsConnected] = useState(false); const [error, setError] = useState(null); @@ -71,7 +71,14 @@ const useRealtimeData = (formId, sheetName) => { const loadInitialData = async () => { try { setIsLoading(true); - const res = await FormsSheetApi.get(formId, sheetName); + let res; + if (direction !== null) { + res = await FormsSheetApi.get(formId, sheetName, { + params: { direction: direction }, + }); + } else { + res = await FormsSheetApi.get(formId, sheetName); + } const data = res.result; if (!data || data.length === 0) { @@ -119,8 +126,22 @@ const useRealtimeData = (formId, sheetName) => { if (!updatedCells || updatedCells.length === 0) return; setData((prevData) => { - const newData = updateExistingStructure(prevData, updatedCells) - return newData + const newData = updateCells(prevData, updatedCells); + return newData; + }); + }); + + const unsubscribeRowAdds = subscribeToRowAdds((data) => { + const updates = data.result; + const expense_item_id = data.data.expense_item_id; + const updatedCells = updates.slice(0, -1); + const newRow = updates.at(-1); + setData((prevData) => { + const updateData = updateCells(prevData, updatedCells); + + const newData = insertCell(updateData, newRow, expense_item_id); + console.log(newData) + return newData; }); }); @@ -150,7 +171,7 @@ const useRealtimeData = (formId, sheetName) => { return () => { unsubscribeCellUpdates(); - // unsubscribeRowAdds(); + unsubscribeRowAdds(); unsubscribeRowDeletes(); unsubscribeErrors(); }; diff --git a/web/src/components/RealtimeTable/utils/cellUtils.js b/web/src/components/RealtimeTable/utils/cellUtils.js index 64b2e3d..1f948e5 100644 --- a/web/src/components/RealtimeTable/utils/cellUtils.js +++ b/web/src/components/RealtimeTable/utils/cellUtils.js @@ -42,10 +42,10 @@ export function setNewValueToData(data, colId, rowId, value) { return true; } -export function updateExistingStructure(existingRows, updateItems) { - const updatedRows = JSON.parse(JSON.stringify(existingRows)); +export function updateCells(data, newRows) { + const updatedRows = JSON.parse(JSON.stringify(data)); - updateItems.forEach((update) => { + newRows.forEach((update) => { const [rowType, depth, sortOrder, newData] = update; // Рекурсивная функция для поиска и обновления строки @@ -53,20 +53,20 @@ export function updateExistingStructure(existingRows, updateItems) { for (let i = 0; i < rows.length; i++) { const row = rows[i]; - // Проверяем совпадение по всем ключам if ( row.row_type === rowType && row.depth === depth && row.sort_order === sortOrder ) { - // Обновляем только те поля, которые пришли в newData if (newData && typeof newData === "object") { Object.keys(newData).forEach((key) => { if (row.data && row.data[key]) { - // Если поле существует в data, обновляем его - row.data[key] = {...row.data[key], ...newData[key]}; + if (typeof row.data[key] === "object") { + row.data[key] = { ...row.data[key], ...newData[key] }; + } else { + row.data[key] = newData[key]; + } } else if (row[key]) { - // Если поле на уровне самой строки (не в data) row[key] = newData[key]; } }); @@ -74,7 +74,6 @@ export function updateExistingStructure(existingRows, updateItems) { return true; // Нашли и обновили } - // Рекурсивно ищем в subRows if ( row.subRows && Array.isArray(row.subRows) && @@ -92,3 +91,37 @@ export function updateExistingStructure(existingRows, updateItems) { return updatedRows; } + +export function insertCell(data, newRow, expense_item_id) { + const updatedRows = JSON.parse(JSON.stringify(data)); + const [row_type, depth, sort_order, dataRow] = newRow; + const newRowObject = { + row_type: row_type, + depth: depth, + sort_order: sort_order, + subRows: [], + data: dataRow, + }; + + function findAndUpdate(rows) { + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + + if (row.data.header.expense_item_id === expense_item_id) { + if (!("subRows" in row)) { + row.subRows = []; + } + row.subRows.push(newRowObject); + return true; + } + + if (row.subRows && Array.isArray(row.subRows) && row.subRows.length > 0) { + const found = findAndUpdate(row.subRows); + if (found) return true; + } + } + return false; + } + findAndUpdate(updatedRows); + return updatedRows; +} diff --git a/web/src/components/common/TableCard/TableCard.jsx b/web/src/components/common/TableCard/TableCard.jsx index db19e26..fa12a9d 100644 --- a/web/src/components/common/TableCard/TableCard.jsx +++ b/web/src/components/common/TableCard/TableCard.jsx @@ -22,9 +22,10 @@ const TableCard = ({ table, onNavigate }) => {

{table.name}

- +

{table?.direction}

+ {/*
-
+
*/}
diff --git a/web/src/components/common/TableList/TableList.jsx b/web/src/components/common/TableList/TableList.jsx index 54a992a..28be971 100644 --- a/web/src/components/common/TableList/TableList.jsx +++ b/web/src/components/common/TableList/TableList.jsx @@ -22,7 +22,7 @@ const TablesList = ({ if (!formInfo){ return; } - const path = `${basePath}/form/${formInfo.id}/form-type/${formInfo.form_type_code}/${table.sheet}`; + const path = `${basePath}/form/${formInfo.id}/form-type/${formInfo.form_type_code}/${table.sheet}/${table?.direction}`; return ( { }; export default function NewTablePage() { - const { formId, formType, sheetName } = useParams() + const { formId, formType, sheetName, direction } = useParams() const { user } = useAuth(); return ( @@ -34,8 +34,9 @@ export default function NewTablePage() { formId={formId} sheetName={sheetName} userId={user.id} + direction={direction === 'null' ? null : direction} > - + )} diff --git a/web/src/pages/TaskPage/TaskPage.jsx b/web/src/pages/TaskPage/TaskPage.jsx index 1d84ba1..5b08735 100644 --- a/web/src/pages/TaskPage/TaskPage.jsx +++ b/web/src/pages/TaskPage/TaskPage.jsx @@ -63,7 +63,13 @@ export default function TaskPage() { setIsLoading(true); try { const data = await TasksApi.getSheets(id); - setTables(data.result.sheets.map(table => ({ name: SHEET_NAME[table], sheet: table }))); + // Преобразуем данные с учетом direction + const mappedTables = data.result.all_sheets.map(sheet => ({ + name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name, + sheet: sheet.sheet_name, + direction: sheet.direction + })); + setTables(mappedTables); } catch (error) { toast.error( error?.response?.data?.detail || 'Ошибка получения данных задачи', @@ -78,14 +84,14 @@ export default function TaskPage() { } }, [id]); - useEffect(() => { + useEffect(() => { const getFormInfo = async () => { setIsLoading(true); try { - const data = await TasksApi.getById(id); - setTask(data.result) + const data = await TasksApi.getById(id); + setTask(data.result); } catch (error) { - + // обработка ошибки } finally { setIsLoading(false); } @@ -97,13 +103,14 @@ export default function TaskPage() { }, [id]); const handleCreateProject = (projectData) => { - + // логика создания проекта }; - // Фильтруем таблицы по query - const filteredTables = tables.filter((item) => - item.name.toLowerCase().includes(query.toLowerCase()), - ); + // Фильтруем таблицы по query (учитывая name и direction) + const filteredTables = tables.filter((item) => { + const searchString = `${item.name} ${item.direction || ''}`.toLowerCase(); + return searchString.includes(query.toLowerCase()); + }); if (isLoading) { return ( @@ -124,7 +131,6 @@ export default function TaskPage() { return ( <> - {/* */} - - setModalStageOpen(true)} @@ -179,4 +183,4 @@ export default function TaskPage() { ); -} +} \ No newline at end of file