diff --git a/web/public/images/Project.svg b/web/public/images/Project.svg new file mode 100644 index 0000000..e69de29 diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 78a9586..442a2cd 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -24,9 +24,11 @@ import { import { useRealtime } from './contexts/RealtimeContext'; import { toast } from 'react-toastify'; import { CircularProgress } from '@mui/material'; -import { additionVspRowTable } from './constants/addingRowConfig'; +import { additionExpenseRowTable, additionVspRowTable } from './constants/addingRowConfig'; import { SelectVspModal } from './Modals/SelectVspModal'; +import { SelectExpenseItemModal } from './Modals/SelectExpenseItemModal'; import { getRowId } from './utils/rowUtils'; +import { FORM_TYPE_OPTIONS } from '../../constants/constants'; import { debounce } from '@mui/material'; const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { @@ -40,6 +42,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { const [editingCell, setEditingCell] = useState(null); const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false); + const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false); const isVspAdditionRow = useMemo(() => { if (!formType || !sheetName) return false; @@ -47,6 +50,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { return additionVspRowTable[formType].includes(sheetName); }, [formType, sheetName]); + const isAdditionExpenseItem = useMemo(() => { + if (!formType || !sheetName) return false; + if (!additionExpenseRowTable[formType]) return false; + return additionExpenseRowTable[formType].includes(sheetName); + }, [formType, sheetName]); + const handleGlobalFilterChange = useCallback( debounce((value) => { startTransition(() => { @@ -66,6 +75,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { addRow: contextAddRow, deleteRow: contextDeleteRow, startEditing: contextStartEditing, + addProgram: contextAddProgram, + addProject: contextAddProject, } = useRealtime(); const { @@ -113,6 +124,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { setColumnsConfig({ columns: [], colors: {} }); } }; + loadConfig(); }, [formType, sheetName]); @@ -363,11 +375,19 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { const handleAddVspRow = useCallback((vsp_id) => { contextAddRow({ vsp_id: vsp_id }); - }, [rowSelection, table]); + }, []); + + const handleAddExpenseItemRow = useCallback((expense_item) => { + contextAddRow({ expense_item_id: expense_item.id }); + }, []); const handleOpenModalSelectVsp = useCallback(() => { setIsOpenModalSelectVsp(true); - }, [rowSelection, table]) + }, []) + + const handleOpenModalSelectExpenseItem = useCallback(() => { + setIsOpenModalSelectExpenseItem(true); + }, []) const handleDeleteRow = useCallback(() => { const allRows = table.getRowModel().rows; @@ -383,6 +403,17 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { setRowSelection({}); }, [rowSelection, table]) + const addRow = useCallback(() => { + + if (isVspAdditionRow) { + return handleOpenModalSelectVsp(); + } + if (isAdditionExpenseItem) { + return handleOpenModalSelectExpenseItem(); + } + return handleAddRow(); + }, [isVspAdditionRow, isAdditionExpenseItem, handleOpenModalSelectVsp, handleAddRow]); + useEffect(() => { return () => { setData([]); @@ -409,7 +440,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { onGlobalFilterChange={handleGlobalFilterChange} sizeMult={sizeMult} onChangeShowColumnFilters={setShowColumnFilters} - onAddRow={isVspAdditionRow ? handleOpenModalSelectVsp : handleAddRow} + onAddRow={addRow} onDeleteRow={handleDeleteRow} isLoadingData={isLoadingData} formId={formId} @@ -479,6 +510,14 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { onSelect={handleAddVspRow} title="Выбор ВСП" /> + setIsOpenModalSelectExpenseItem(false)} + formId={formId} + onSelect={handleAddExpenseItemRow} + title="Добавление строки" + sheet={sheetName} + /> ); }; diff --git a/web/src/components/RealtimeTable/contexts/RealtimeContext.js b/web/src/components/RealtimeTable/contexts/RealtimeContext.js index 58182a8..dac68d9 100644 --- a/web/src/components/RealtimeTable/contexts/RealtimeContext.js +++ b/web/src/components/RealtimeTable/contexts/RealtimeContext.js @@ -88,19 +88,33 @@ export const RealtimeProvider = ({ } break; + case "program_added": + console.log("Program added:", data); + if (data.result && onProgramAddRef.current) { + onProgramAddRef.current(data); + } + break; + + case "project_added": + console.log("Project added:", data); + if (data.result && onProjectAddRef.current) { + onProjectAddRef.current(data); + } + break; + default: console.log("Unknown event:", data.event); } }, []); - const { isConnectedRef, isConnected, sendMessage, disconnect, lastError } = useWebSocket( - wsUrl, - handleMessage, - ); + const { isConnectedRef, isConnected, sendMessage, disconnect, lastError } = + useWebSocket(wsUrl, handleMessage); const onCellUpdateRef = useRef(null); const onRowAddRef = useRef(null); const onRowDeleteRef = useRef(null); + const onProgramAddRef = useRef(null); + const onProjectAddRef = useRef(null); const onErrorRef = useRef(null); const onAuthSuccessRef = useRef(null); const onCellEditStartRef = useRef(null); @@ -283,6 +297,42 @@ export const RealtimeProvider = ({ [isConnected, sendMessage, sheetName, formId, direction], ); + const addProject = useCallback( + async (rowData) => { + if (!isConnectedRef) { + onErrorRef.current?.("Нет подключения или авторизации"); + return false; + } + + const message = { + event: "row_added", + data: rowData, + }; + addCommonField(message); + + return sendMessage(message); + }, + [isConnected, sendMessage, sheetName, formId, direction], + ); + + const addProgram = useCallback( + async (rowData) => { + if (!isConnectedRef) { + onErrorRef.current?.("Нет подключения или авторизации"); + return false; + } + + const message = { + event: "row_added", + data: rowData, + }; + addCommonField(message); + + return sendMessage(message); + }, + [isConnected, sendMessage, sheetName, formId, direction], + ); + const deleteRow = useCallback( async (rowId) => { if (!isConnectedRef) { @@ -322,6 +372,20 @@ export const RealtimeProvider = ({ }; }, []); + const subscribeToProgramAdds = useCallback((callback) => { + onProgramAddRef.current = callback; + return () => { + onProgramAddRef.current = null; + }; + }, []); + + const subscribeToProjectAdds = useCallback((callback) => { + onProjectAddRef.current = callback; + return () => { + onProjectAddRef.current = null; + }; + }, []); + const subscribeToErrors = useCallback((callback) => { onErrorRef.current = callback; return () => { @@ -366,9 +430,13 @@ export const RealtimeProvider = ({ endEditing, updateCell, addRow, + addProgram, + addProject, deleteRow, subscribeToRowAdds, subscribeToRowDeletes, + subscribeToProgramAdds, + subscribeToProjectAdds, subscribeToErrors, subscribeToAuthSuccess, subscribeToCellEditStart, diff --git a/web/src/components/RealtimeTable/hooks/useRealtimeData.js b/web/src/components/RealtimeTable/hooks/useRealtimeData.js index 77ad80a..583b4fd 100644 --- a/web/src/components/RealtimeTable/hooks/useRealtimeData.js +++ b/web/src/components/RealtimeTable/hooks/useRealtimeData.js @@ -19,6 +19,8 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => { error: wsError, subscribeToCellUpdates, subscribeToRowAdds, + subscribeToProgramAdds, + subscribeToProjectAdds, subscribeToRowDeletes, subscribeToErrors, subscribeToCellEditStart, @@ -206,6 +208,58 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => { }); }); + const unsubscribeProjectAdds = subscribeToProjectAdds((data) => { + const updates = data.result.data; + const expense_item_id = data.data.expense_item_id || null; + const newLineId = data.result.new_line_id; + const [updatedCells, newRow] = updates.reduce( + (res, row) => { + if (row[3]?.line_id === newLineId || isVspNewRow(row)) { + res[1] = row; + } else { + res[0].push(row); + } + return res; + }, + [[], null], + ); + if (newRow === null) { + toast.error("Ошибка получения данных"); + return; + } + setData((prevData) => { + const updateData = updateCells(prevData, updatedCells); + const newData = insertCell(updateData, newRow, expense_item_id); + return newData; + }); + }); + + const unsubscribeProgramAdds = subscribeToProgramAdds((data) => { + const updates = data.result.data; + const expense_item_id = data.data.expense_item_id || null; + const newLineId = data.result.new_line_id; + const [updatedCells, newRow] = updates.reduce( + (res, row) => { + if (row[3]?.line_id === newLineId || isVspNewRow(row)) { + res[1] = row; + } else { + res[0].push(row); + } + return res; + }, + [[], null], + ); + if (newRow === null) { + toast.error("Ошибка получения данных"); + return; + } + setData((prevData) => { + const updateData = updateCells(prevData, updatedCells); + const newData = insertCell(updateData, newRow, expense_item_id); + return newData; + }); + }); + const unsubscribeRowDeletes = subscribeToRowDeletes((data) => { const updatedCells = data.result; const lineIdForDelete = data.data.row_id; @@ -255,6 +309,8 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => { return () => { unsubscribeCellUpdates(); unsubscribeRowAdds(); + unsubscribeProgramAdds(); + unsubscribeProjectAdds(); unsubscribeRowDeletes(); unsubscribeErrors(); unsubscribeCellEditStart(); @@ -268,6 +324,8 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => { subscribeToErrors, subscribeToCellEditStart, subscribeToCellEditEnd, + subscribeToProgramAdds, + subscribeToProjectAdds, formatedToSubRows, ]);