diff --git a/web/src/app/Routes.jsx b/web/src/app/Routes.jsx
index d0945a3..6918c00 100644
--- a/web/src/app/Routes.jsx
+++ b/web/src/app/Routes.jsx
@@ -18,6 +18,7 @@ import NewTablePage from '../pages/NewTablePage.jsx';
import TablesTest from '../pages/TablesTest.jsx';
import NewFormTablePage from '../pages/NewFormTablePage.jsx'
import ProjectsPage from '../pages/ProjectsPage/ProjectsPage.jsx';
+import SummaryPage from '../pages/ProjectPages/SummaryPage.jsx';
export const PrivateRoute = () => {
const { isAuthenticated, loading } = useAuth();
@@ -53,6 +54,7 @@ export const AppRoutes = () => {
} />
} />
} />
+ } />
diff --git a/web/src/pages/ProjectPages/SummaryPage.jsx b/web/src/pages/ProjectPages/SummaryPage.jsx
new file mode 100644
index 0000000..bf76839
--- /dev/null
+++ b/web/src/pages/ProjectPages/SummaryPage.jsx
@@ -0,0 +1,409 @@
+import React, { useMemo, useState } from "react";
+import {
+ useMaterialReactTable,
+ MaterialReactTable,
+} from 'material-react-table';
+import {
+ Box,
+ Paper,
+ Typography,
+} from "@mui/material";
+import { useNavigate } from 'react-router-dom';
+import { firstTableData, secondTableData } from "./mockData";
+import { createFirstColumns, secondColumns } from "./columns";
+import EditModal from "./components/EditModal/EditModal";
+import TableFilters from "./components/TableFilters/TableFilters"; // Импортируем компонент фильтров
+import { handleEditClick, handleNavigateClick, handleSaveEdit } from "./utils/tableHandlers";
+
+const SummaryPage = () => {
+ const navigate = useNavigate();
+
+ const [selectedRow, setSelectedRow] = useState(null);
+ const [editModalOpen, setEditModalOpen] = useState(false);
+ const [selectedRowData, setSelectedRowData] = useState(null);
+ const [tableData, setTableData] = useState(firstTableData);
+
+ // Состояния для фильтров
+ const [selectedBranch, setSelectedBranch] = useState('');
+ const [searchQuery, setSearchQuery] = useState('');
+
+ const getFilteredData = () => {
+ if (!selectedBranch && !searchQuery) {
+ return tableData;
+ }
+
+ return tableData
+ .filter(item => {
+ // Фильтр по филиалу (только для корневых)
+ if (selectedBranch) {
+ return item.org_unit_id === selectedBranch.id;
+ }
+ return true;
+ })
+ .map(item => {
+ if (!searchQuery) {
+ return item;
+ }
+
+ const query = searchQuery.toLowerCase().trim();
+
+ // Проверяем, совпадает ли родитель
+ const parentMatches = item.project.toLowerCase().includes(query);
+
+ // Фильтруем дочерние элементы - оставляем только те, что совпадают с поиском
+ const filteredSubRows = item.subRows?.filter(subItem =>
+ subItem.project.toLowerCase().includes(query)
+ ) || [];
+
+ // Возвращаем элемент с отфильтрованными дочерними элементами
+ return {
+ ...item,
+ subRows: filteredSubRows
+ };
+ })
+ .filter(item => {
+ if (!searchQuery) {
+ return true;
+ }
+
+ const query = searchQuery.toLowerCase().trim();
+ const parentMatches = item.project.toLowerCase().includes(query);
+ const hasMatchingChildren = item.subRows && item.subRows.length > 0;
+
+ return parentMatches || hasMatchingChildren;
+ });
+ };
+ const filteredData = getFilteredData();
+
+ const handleRowClick = (row) => {
+ if (row.depth === 0 && row) {
+ setSelectedRow(row);
+ }
+ };
+
+ const isChildOfSelectedRow = (row) => {
+ if (!selectedRow) return false;
+ const parentRowId = row.parentId;
+ return (parentRowId === selectedRow.id);
+ };
+
+ const onEdit = (row) => {
+ handleEditClick(row, setEditModalOpen, setSelectedRowData);
+ };
+
+ const onNavigate = (row) => {
+ handleNavigateClick(row, navigate);
+ };
+
+ const onSaveEdit = (formData) => {
+ handleSaveEdit(formData, updateTableData);
+ };
+
+ const updateTableData = (updatedData) => {
+ setTableData(prevData =>
+ prevData.map(item =>
+ item.id === updatedData.id ? { ...item, ...updatedData } : item
+ )
+ );
+ };
+
+ const firstColumns = useMemo(() =>
+ createFirstColumns({ onEdit, onNavigate }),
+ [onEdit, onNavigate]
+ );
+
+ const tableHeadCellStyles = {
+ fontWeight: 700,
+ fontSize: "12px",
+ lineHeight: 1.15,
+ textAlign: "left",
+ backgroundColor: "rgb(248, 249, 250)",
+ borderRight: "1px solid #e0e0e0",
+ "&:last-child": {
+ borderRight: "none",
+ },
+ whiteSpace: "nowrap",
+ position: "sticky",
+ top: 0,
+ boxShadow: 'none',
+ padding: "0.5rem 1rem",
+ "& .MuiTableSortLabel-icon": {
+ fontSize: "0.875rem",
+ },
+ "& .MuiTableSortLabel-iconDirectionDesc": {
+ fontSize: "0.875rem",
+ },
+ "& .MuiTableSortLabel-iconDirectionAsc": {
+ fontSize: "0.875rem",
+ },
+ };
+
+ const summaryProjectsTableOptions = {
+ enableColumnActions: false,
+ enableColumnFilters: false,
+ enablePagination: false,
+ enableSorting: true,
+ enableBottomToolbar: false,
+ enableTopToolbar: false,
+ enableExpanding: true,
+ getSubRows: (row) => row.subRows,
+ muiTableHeadCellProps: {
+ sx: tableHeadCellStyles,
+ },
+ muiExpandButtonProps: {
+ sx: {
+ width: "1.5rem",
+ height: "1.5rem",
+ minWidth: "1.5rem",
+ "& .MuiSvgIcon-root": {
+ fontSize: "0.875rem",
+ },
+ },
+ },
+ muiTableBodyRowProps: ({ row }) => {
+ const isSelected = selectedRow && row.id === selectedRow.id;
+ const isChild = isChildOfSelectedRow(row);
+
+ return {
+ sx: {
+ backgroundColor: "#ffffff",
+ "&:hover": {
+ backgroundColor: "rgb(248, 250, 252)",
+ },
+ "&:hover td::after": {
+ backgroundColor: "transparent !important",
+ },
+ ...(row.depth > 0 && {
+ backgroundColor: "rgb(252, 252, 253)",
+ }),
+ ...((isSelected || isChild) && {
+ backgroundColor: "rgb(234, 247, 236)",
+ "&:hover": {
+ backgroundColor: "rgb(225, 242, 229) !important",
+ },
+ }),
+ },
+ onClick: () => handleRowClick(row),
+ };
+ },
+ muiTableBodyCellProps: ({ row, column }) => ({
+ sx: {
+ borderRight: "1px solid #e0e0e0",
+ "&:last-child": {
+ borderRight: "none",
+ },
+ "&:hover td::after": {
+ backgroundColor: "transparent !important",
+ },
+ textAlign: "left",
+ fontSize: "0.875rem",
+ padding: "0.5rem 1rem",
+ ...(column.id === 'mrt-row-expand' && {
+ width: "1.5rem",
+ maxWidth: "1.5rem",
+ minWidth: "1.5rem",
+ padding: "0.5rem 0.25rem",
+ }),
+ ...(column.id === 'project' && row.depth > 0 && {
+ pl: `${2 + row.depth * 0.5}rem`,
+ }),
+ },
+ }),
+ muiTablePaperProps: {
+ elevation: 0,
+ sx: {
+ border: "1px solid #e0e0e0",
+ borderRadius: "1rem",
+ overflow: "hidden",
+ height: "45vh",
+ display: "flex",
+ flexDirection: "column",
+ },
+ },
+ muiTableContainerProps: {
+ sx: {
+ flex: 1,
+ overflow: "auto",
+ "& thead": {
+ position: "sticky",
+ top: 0,
+ zIndex: 10,
+ },
+ },
+ },
+ };
+
+ const projectTableOptions = {
+ enableColumnActions: false,
+ enableColumnFilters: false,
+ enablePagination: false,
+ enableSorting: true,
+ enableBottomToolbar: false,
+ enableTopToolbar: false,
+ enableExpanding: false,
+ muiTableBodyRowProps: ({ row }) => ({
+ sx: {
+ backgroundColor: "#ffffff",
+ "&:hover": {
+ backgroundColor: "rgb(248, 250, 252)",
+ },
+ "&:hover td::after": {
+ backgroundColor: "transparent !important",
+ },
+ boxShadow: 'none',
+ },
+ }),
+ muiTableHeadCellProps: {
+ sx: tableHeadCellStyles,
+ },
+ muiTableBodyCellProps: ({ column }) => ({
+ sx: {
+ borderRight: "1px solid #e0e0e0",
+ "&:last-child": {
+ borderRight: "none",
+ },
+ textAlign: "left",
+ fontSize: "0.875rem",
+ padding: "0.5rem 1rem",
+ },
+ }),
+ muiTablePaperProps: {
+ elevation: 0,
+ sx: {
+ border: "1px solid #e0e0e0",
+ borderRadius: "0 0 1rem 1rem",
+ overflow: "hidden",
+ height: "35vh",
+ display: "flex",
+ flexDirection: "column",
+ },
+ },
+ muiTableContainerProps: {
+ sx: {
+ flex: 1,
+ overflow: "auto",
+ "& thead": {
+ position: "sticky",
+ top: 0,
+ zIndex: 10,
+ },
+ },
+ },
+ initialState: {
+ columnPinning: { left: ['project'] }
+ },
+ };
+
+ const summaryProjectsTable = useMaterialReactTable({
+ columns: firstColumns,
+ data: filteredData,
+ ...summaryProjectsTableOptions,
+ initialState: {
+ columnPinning: { right: ['actions'], left: ["mrt-row-expand", 'project'] }
+ },
+ });
+
+ const projectTable = useMaterialReactTable({
+ columns: secondColumns,
+ data: selectedRow ? secondTableData[selectedRow.original.id] : [],
+ ...projectTableOptions,
+ });
+
+ const getSelectedProjectTitle = () => {
+ if (selectedRow && selectedRow.original.project) {
+ return selectedRow.original.project;
+ }
+ return 'Проект не выбран';
+ };
+
+ // Обработчики фильтров
+ const handleBranchChange = (value) => {
+ setSelectedBranch(value);
+ };
+
+ const handleSearchChange = (value) => {
+ setSearchQuery(value);
+ };
+
+ return (
+
+ {/* Компонент фильтров */}
+ { }} // Можно использовать для дополнительной логики
+ selectedBranch={selectedBranch}
+ onBranchChange={handleBranchChange}
+ searchQuery={searchQuery}
+ onSearchChange={handleSearchChange}
+ />
+
+ {/* Первая таблица */}
+
+
+
+
+ {/* Вторая таблица */}
+
+
+ {getSelectedProjectTitle()}
+
+ {selectedRow ?
+
+
+
+ :
+
+ Разверните или выберите проект в верхней таблице
+
+ }
+
+
+ {/* Модалка редактирования */}
+ setEditModalOpen(false)}
+ rowData={selectedRowData}
+ onSave={onSaveEdit}
+ />
+
+ );
+};
+
+export default SummaryPage;
\ No newline at end of file
diff --git a/web/src/pages/ProjectPages/columns.js b/web/src/pages/ProjectPages/columns.js
new file mode 100644
index 0000000..84b8f5d
--- /dev/null
+++ b/web/src/pages/ProjectPages/columns.js
@@ -0,0 +1,550 @@
+import React from "react";
+import { Box, IconButton, Tooltip } from "@mui/material";
+import StatusBadge from "./components/StatusBadge/StatusBadge";
+import { Back, EditPenSvg } from "../../components/common/icons/icons";
+
+export const createFirstColumns = (handlers) => {
+ const { onEdit, onNavigate } = handlers;
+ return [
+ {
+ accessorKey: "project",
+ header: "Проект",
+ size: 200,
+ filterFn: "contains",
+ filterVariant: "text",
+ },
+ {
+ accessorKey: "status",
+ header: "Статус",
+ size: 100,
+ Cell: ({ cell }) => {
+ const status = cell.getValue();
+ return ;
+ },
+ },
+ {
+ accessorKey: "techNumber",
+ header: "Технический номер проекта",
+ size: 150,
+ },
+ {
+ accessorKey: "address",
+ header: "Адрес объекта",
+ size: 180,
+ },
+ {
+ accessorKey: "vspNumber",
+ header: "Порядковый номер ВСП",
+ size: 150,
+ Cell: ({ cell }) => cell.getValue() ?? "",
+ },
+ {
+ accessorKey: "krfDate",
+ header: "Дата решения КРФ",
+ size: 120,
+ },
+ {
+ accessorKey: "fkDate",
+ header: "Дата решения ФК",
+ size: 120,
+ },
+ {
+ accessorKey: "boardDate",
+ header: "Дата решения Правления",
+ size: 130,
+ },
+ {
+ accessorKey: "openMoveCloseDate",
+ header: "Дата открытия / переезда / закрытия",
+ size: 160,
+ },
+ {
+ accessorKey: "implementationMonths",
+ header: "Время реализации проекта в месяцах",
+ size: 140,
+ Cell: ({ cell }) => `${cell.getValue()} мес.`,
+ },
+ {
+ accessorKey: "koFinancing",
+ header: "Финансирование по решению КО",
+ size: 150,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "baseYearEstimate",
+ header: "В базовой смете года",
+ size: 130,
+ },
+ {
+ accessorKey: "correctedEstimateQ2",
+ header: "В скоррект смете 2 кв",
+ size: 140,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "correctedEstimateQ3",
+ header: "В скоррект смете 3 кв",
+ size: 140,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "correctedEstimateQ4",
+ header: "В скоррект смете 4 кв",
+ size: 140,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "developmentType",
+ header: "Тип развития",
+ size: 130,
+ },
+ {
+ accessorKey: "developmentBlock",
+ header: "Блок развития",
+ size: 120,
+ },
+ {
+ accessorKey: "reserveBankAXR",
+ header: "Из резерва Банка в смету ПРРС АХР",
+ size: 160,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "reserveBankKV",
+ header: "Из резерва Банка в смету ПРРС КВ",
+ size: 160,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actions",
+ header: "",
+ size: 120,
+ enableSorting: false,
+ Cell: ({ row }) => (
+
+
+ {
+ e.stopPropagation();
+ onEdit(row);
+ }}
+ >
+
+
+
+
+ {row.depth == 0 && (
+
+ {
+ e.stopPropagation();
+ onNavigate(row);
+ }}
+ >
+
+
+
+ )}
+
+ ),
+ },
+ ];
+};
+
+// Колонки для второй таблицы с многоуровневыми заголовками
+export const secondColumns = [
+ {
+ accessorKey: "project",
+ header: "Проект",
+ size: 120,
+ sticky: "left",
+ },
+ // Объем финансирования по контрольному листу
+ {
+ header: "Объем финансирования по контрольному листу",
+ columns: [
+ {
+ accessorKey: "financingAXR",
+ header: "АХР",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "financingAXRLimit",
+ header: "АХР лимит",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "financingAXRCurrent",
+ header: "АХР текущие",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "financingKV",
+ header: "КВ",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ // Базовый период
+ {
+ header: "Базовый период 01.01 - 31.03",
+ columns: [
+ {
+ accessorKey: "baseAXR",
+ header: "АХР",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "baseAXRLimit",
+ header: "АХР лимит",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "baseAXRCurrent",
+ header: "АХР текущие",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "baseKV",
+ header: "КВ",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ // Скорректированный период Q2
+ {
+ header: "Скорректированный период 01.04 - 30.06",
+ columns: [
+ {
+ accessorKey: "q2AXR",
+ header: "АХР",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q2AXRLimit",
+ header: "АХР лимит",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q2AXRCurrent",
+ header: "АХР текущие",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q2KV",
+ header: "КВ",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ // Скорректированный период Q3
+ {
+ header: "Скорректированный период 01.07 - 30.09",
+ columns: [
+ {
+ accessorKey: "q3AXR",
+ header: "АХР",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q3AXRLimit",
+ header: "АХР лимит",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q3AXRCurrent",
+ header: "АХР текущие",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q3KV",
+ header: "КВ",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ // Скорректированный период Q4
+ {
+ header: "Скорректированный период 01.10 - 31.12",
+ columns: [
+ {
+ accessorKey: "q4AXR",
+ header: "АХР",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q4AXRLimit",
+ header: "АХР лимит",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q4AXRCurrent",
+ header: "АХР текущие",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "q4KV",
+ header: "КВ",
+ size: 100,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ // Фактические расходы (АХР)
+ {
+ header: "Фактические расходы (АХР)",
+ columns: [
+ {
+ header: "1 квартал",
+ columns: [
+ {
+ accessorKey: "actualAXRQ1",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRLimitQ1",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRCurrentQ1",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ {
+ header: "2 квартал",
+ columns: [
+ {
+ accessorKey: "actualAXRQ2",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRLimitQ2",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRCurrentQ2",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ {
+ header: "3 квартал",
+ columns: [
+ {
+ accessorKey: "actualAXRQ3",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRLimitQ3",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRCurrentQ3",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ {
+ header: "4 квартал",
+ columns: [
+ {
+ accessorKey: "actualAXRQ4",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRLimitQ4",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualAXRCurrentQ4",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ ],
+ },
+ // Фактические расходы (КВ)
+ {
+ header: "Фактические расходы (КВ)",
+ columns: [
+ {
+ header: "1 квартал",
+ columns: [
+ {
+ accessorKey: "actualKVQ1",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVLimitQ1",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVCurrentQ1",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ {
+ header: "2 квартал",
+ columns: [
+ {
+ accessorKey: "actualKVQ2",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVLimitQ2",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVCurrentQ2",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ {
+ header: "3 квартал",
+ columns: [
+ {
+ accessorKey: "actualKVQ3",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVLimitQ3",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVCurrentQ3",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ {
+ header: "4 квартал",
+ columns: [
+ {
+ accessorKey: "actualKVQ4",
+ header: "Фактические расходы",
+ size: 120,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVLimitQ4",
+ header: "Лимит",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "actualKVCurrentQ4",
+ header: "Текущие",
+ size: 80,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+ ],
+ },
+ // Смета 2026
+ {
+ header: "Смета 2026",
+ columns: [
+ {
+ accessorKey: "includeInEstimate2026",
+ header: "Вкл в смету 2026",
+ size: 100,
+ },
+ {
+ accessorKey: "developmentBlock2026",
+ header: "Блок развития 2026",
+ size: 120,
+ },
+ {
+ accessorKey: "impactRentAXR",
+ header: "Влияние на АХР подд АРЕНДА год",
+ size: 160,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "impactOtherAXR",
+ header: "Влияние на АХР подд ПРОЧЕЕ год",
+ size: 160,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "sumEstimateAXR",
+ header: "Сумма в смету 2026 года АХР",
+ size: 150,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ {
+ accessorKey: "sumEstimateKV",
+ header: "Сумма в смету 2026 года КВ",
+ size: 150,
+ Cell: ({ cell }) => cell.getValue().toLocaleString(),
+ },
+ ],
+ },
+];
diff --git a/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx b/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx
new file mode 100644
index 0000000..1bed176
--- /dev/null
+++ b/web/src/pages/ProjectPages/components/EditModal/EditModal.jsx
@@ -0,0 +1,202 @@
+import React from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ Grid,
+ Box,
+ IconButton,
+ Typography,
+} from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+import { DangerOutlinedButton, PrimaryButton } from '../../../../components/common/Buttons/Buttons';
+
+const EditModal = ({ open, onClose, rowData, onSave }) => {
+ const [formData, setFormData] = React.useState(rowData || {});
+
+ React.useEffect(() => {
+ if (rowData) {
+ setFormData(rowData);
+ }
+ }, [rowData]);
+
+ const handleChange = (field) => (event) => {
+ setFormData({
+ ...formData,
+ [field]: event.target.value,
+ });
+ };
+
+ const handleSave = () => {
+ onSave(formData);
+ onClose();
+ };
+
+ if (!rowData) return null;
+
+ const leftColumnFields = [
+ { key: 'project', label: 'Проект', placeholder: 'Смета_2026' },
+ { key: 'techNumber', label: 'Технический номер проекта', placeholder: '2606_180801' },
+ { key: 'vspNumber', label: 'Порядковый номер ВСП', placeholder: '1808' },
+ { key: 'fkDate', label: 'Дата решения ФК', type: 'date', placeholder: '18.06.2026' },
+ { key: 'openMoveCloseDate', label: 'Дата открытия / переезда / закрытия', type: 'date' },
+ { key: 'koFinancing', label: 'Финансирование по решению КО', type: 'number' },
+ { key: 'prrsEstimate', label: 'Смета ПРРС' },
+ { key: 'correctedEstimateQ2', label: 'В скоррект смете 2 кв', type: 'number' },
+ { key: 'baseYearEstimate', label: 'В базовой смете года' },
+ { key: 'correctedEstimateQ3', label: 'В скоррект смете 3 кв', type: 'number' },
+ ];
+
+ const rightColumnFields = [
+ { key: 'correctedEstimateQ4', label: 'В скоррект смете 4 кв', type: 'number' },
+ { key: 'developmentBlock', label: 'Блок развития' },
+ { key: 'reserveBankKV', label: 'Из резерва Банка в смету ПРРС КВ', type: 'number' },
+ { key: 'status', label: 'Статус' },
+ { key: 'krfDate', label: 'Дата решения КРФ', type: 'date' },
+ { key: 'boardDate', label: 'Дата решения Правления', type: 'date' },
+ { key: 'implementationMonths', label: 'Время реализации проекта в месяцах', type: 'number' },
+ { key: 'developmentType', label: 'Тип развития', placeholder: 'Реновация ВСП' },
+ { key: 'reserveBankAXR', label: 'Из резерва Банка в смету ПРРС АХР', type: 'number' },
+ { key: 'address', label: 'Адрес объекта', multiline: true, rows: 2, placeholder: '658213, Алтайский кр., г. Рубцовск, ул. Дзержинского, д. 14' },
+ ];
+
+ // Компонент для поля ввода
+ const renderField = (field) => (
+
+
+ {field.label}
+
+
+
+ );
+
+ return (
+
+ );
+};
+
+export default EditModal;
\ No newline at end of file
diff --git a/web/src/pages/ProjectPages/components/StatusBadge/StatusBadge.jsx b/web/src/pages/ProjectPages/components/StatusBadge/StatusBadge.jsx
new file mode 100644
index 0000000..f6c7a0e
--- /dev/null
+++ b/web/src/pages/ProjectPages/components/StatusBadge/StatusBadge.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import styles from './StatusBadge.module.css';
+
+const StatusBadge = ({ status, className = '', ...props }) => {
+ const statusMap = {
+ deleted: {
+ class: styles.deleted,
+ label: 'Удален',
+ },
+ approved: {
+ class: styles.approved,
+ label: 'Согласован',
+ },
+ archived: {
+ class: styles.archived,
+ label: 'В архиве',
+ },
+ created: {
+ class: styles.created,
+ label: 'Создан',
+ },
+ };
+
+ // Если статус не найден, ничего не рендерим или показываем дефолтный
+ const currentStatus = statusMap[status];
+
+ if (!currentStatus) return null;
+
+ return (
+
+ {currentStatus.label}
+
+ );
+};
+
+export default StatusBadge;
\ No newline at end of file
diff --git a/web/src/pages/ProjectPages/components/StatusBadge/StatusBadge.module.css b/web/src/pages/ProjectPages/components/StatusBadge/StatusBadge.module.css
new file mode 100644
index 0000000..640d482
--- /dev/null
+++ b/web/src/pages/ProjectPages/components/StatusBadge/StatusBadge.module.css
@@ -0,0 +1,37 @@
+.badge {
+ display: inline-flex;
+ -moz-box-align: center;
+ align-items: center;
+ min-height: 1.5rem;
+ padding-left: 6px;
+ padding-right: 6px;
+ border: 1px solid rgb(254, 202, 202);
+ border-radius: 3914px;
+ font-size: 0.8rem;
+ font-weight: 700;
+ white-space: nowrap;
+}
+
+.deleted {
+ background-color: #fbe7e9;
+ border-color: #f5d3d6;
+ color: #7e2a2a;
+}
+
+.approved {
+ background-color: #e6f4ea;
+ border-color: #ceead6;
+ color: #1e7e34;
+}
+
+.archived {
+ background-color: #f1f3f4;
+ border-color: #dadce0;
+ color: #5f6368;
+}
+
+.created {
+ background-color: #e8f0fe;
+ border-color: #d2e3fc;
+ color: #1967d2;
+}
diff --git a/web/src/pages/ProjectPages/components/TableFilters/TableFilters.jsx b/web/src/pages/ProjectPages/components/TableFilters/TableFilters.jsx
new file mode 100644
index 0000000..ce91797
--- /dev/null
+++ b/web/src/pages/ProjectPages/components/TableFilters/TableFilters.jsx
@@ -0,0 +1,144 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Box,
+ TextField,
+ Autocomplete,
+ Paper,
+ Typography,
+ Chip,
+ CircularProgress,
+} from '@mui/material';
+import { Search } from '@mui/icons-material';
+import { SspApi } from '../../../../api/ssp';
+
+const TableFilters = ({
+ onBranchChange,
+ onSearchChange,
+ selectedBranch,
+ searchQuery,
+}) => {
+ const [branches, setBranches] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ // Загружаем филиалы из API
+ useEffect(() => {
+ const loadBranches = async () => {
+ setLoading(true);
+ try {
+ const data = await SspApi.getAll({ is_active: true });
+ setBranches(data.result);
+ } catch (error) {
+ console.error('Error loading branches:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+ loadBranches();
+ }, []);
+
+ return (
+
+
+
+ option.title || ''}
+ getOptionKey={(option) => option.id}
+ value={selectedBranch || null}
+ onChange={(event, newValue) => {
+ onBranchChange(newValue || '');
+ }}
+ renderInput={(params) => {
+ const { InputProps, ...rest } = params;
+ return (
+
+ {loading && }
+ {InputProps?.endAdornment}
+ >
+ ),
+ }}
+ />
+ );
+ }}
+ renderOption={(props, option) => (
+
+ {option.title}
+
+ )}
+ clearOnBlur={false}
+ isOptionEqualToValue={(option, value) => option === value}
+ />
+
+ onSearchChange(e.target.value)}
+ sx={{
+ minWidth: 250,
+ maxWidth: 300,
+ flex: '1 1 auto',
+ }}
+ InputProps={{
+ startAdornment: (
+
+ ),
+ }}
+ />
+
+ {(selectedBranch || searchQuery) && (
+
+ {
+ onBranchChange('');
+ onSearchChange('');
+ }}
+ sx={{
+ cursor: 'pointer',
+ '&:hover': {
+ backgroundColor: 'action.hover',
+ },
+ }}
+ />
+
+ )}
+
+
+ );
+};
+
+export default TableFilters;
\ No newline at end of file
diff --git a/web/src/pages/ProjectPages/mockData.js b/web/src/pages/ProjectPages/mockData.js
new file mode 100644
index 0000000..eb3fc10
--- /dev/null
+++ b/web/src/pages/ProjectPages/mockData.js
@@ -0,0 +1,420 @@
+export const firstTableData = [
+ {
+ id: "478",
+ org_unit_id: 6,
+ project: "Алтайский_01",
+ status: "created",
+ techNumber: "2606_180801",
+ address: "658213, Алтайский кр., г. Рубцовск, ул. Дз...",
+ vspNumber: 1808,
+ krfDate: "11.06.2026",
+ fkDate: "18.06.2026",
+ boardDate: "✅",
+ openMoveCloseDate: "15.07.2026",
+ implementationMonths: 8,
+ koFinancing: 35697,
+ baseYearEstimate: "2026",
+ correctedEstimateQ2: 12482,
+ correctedEstimateQ3: 13200,
+ correctedEstimateQ4: 14100,
+ developmentType: "Новое строительство",
+ developmentBlock: "Центральный",
+ reserveBankAXR: 1500,
+ reserveBankKV: 2300,
+ subRows: [
+ {
+ id: "sub_001_01",
+ project: "Смета_2026",
+ status: "Активна",
+ techNumber: "2606_180801_01",
+ address: "г. Рубцовск, ул. Дзержинского, 10",
+ vspNumber: 1809,
+ krfDate: "12.06.2026",
+ fkDate: "19.06.2026",
+ boardDate: "✅",
+ openMoveCloseDate: "15.07.2026",
+ implementationMonths: 8,
+ koFinancing: 11762,
+ baseYearEstimate: "2026",
+ correctedEstimateQ2: 12482,
+ correctedEstimateQ3: 13200,
+ correctedEstimateQ4: 14100,
+ developmentType: "Реконструкция",
+ developmentBlock: "Центральный",
+ reserveBankAXR: 500,
+ reserveBankKV: 800,
+ },
+ {
+ id: "sub_001_02",
+ project: "Смета_2027",
+ status: "Планируется",
+ techNumber: "2606_180801_02",
+ address: "г. Рубцовск, ул. Ленина, 25",
+ vspNumber: 1810,
+ krfDate: "13.06.2026",
+ fkDate: "20.06.2026",
+ boardDate: "⏳",
+ openMoveCloseDate: "01.08.2027",
+ implementationMonths: 12,
+ koFinancing: 11899,
+ baseYearEstimate: "2027",
+ correctedEstimateQ2: 12619,
+ correctedEstimateQ3: 13400,
+ correctedEstimateQ4: 14300,
+ developmentType: "Модернизация",
+ developmentBlock: "Северный",
+ reserveBankAXR: 600,
+ reserveBankKV: 900,
+ },
+ {
+ id: "sub_001_03",
+ project: "Смета_2028",
+ status: "Планируется",
+ techNumber: "2606_180801_03",
+ address: "г. Рубцовск, ул. Советская, 15",
+ vspNumber: 1811,
+ krfDate: "14.06.2026",
+ fkDate: "21.06.2026",
+ boardDate: "⏳",
+ openMoveCloseDate: "01.08.2028",
+ implementationMonths: 14,
+ koFinancing: 12036,
+ baseYearEstimate: "2028",
+ correctedEstimateQ2: 12756,
+ correctedEstimateQ3: 13600,
+ correctedEstimateQ4: 14500,
+ developmentType: "Расширение",
+ developmentBlock: "Южный",
+ reserveBankAXR: 700,
+ reserveBankKV: 1000,
+ },
+ ],
+ },
+ {
+ id: "proj_002",
+ project: "Алтайский_02",
+ org_unit_id: 6,
+ status: "deleted",
+ techNumber: "2602_180801",
+ address: "658213, Алтайский кр., г. Рубцовск, ул. Дз...",
+ vspNumber: 1808,
+ krfDate: "11.01.2026",
+ fkDate: "16.01.2026",
+ boardDate: "✅",
+ openMoveCloseDate: "15.02.2026",
+ implementationMonths: 3,
+ koFinancing: 25000,
+ baseYearEstimate: "2026",
+ correctedEstimateQ2: 8500,
+ correctedEstimateQ3: 9200,
+ correctedEstimateQ4: 9800,
+ developmentType: "Временный",
+ developmentBlock: "Западный",
+ reserveBankAXR: 300,
+ reserveBankKV: 500,
+ subRows: [],
+ },
+ {
+ id: "proj_003",
+ project: "Алтайский_03",
+ org_unit_id: 7,
+ status: "approved",
+ techNumber: "2512_180601",
+ address: "Алтайский кр., объект развития ВСП",
+ vspNumber: null,
+ krfDate: "10.04.2025",
+ fkDate: "25.10.2025",
+ boardDate: "02.11.2025",
+ openMoveCloseDate: "01.12.2025",
+ implementationMonths: 6,
+ koFinancing: 32000,
+ baseYearEstimate: "2025",
+ correctedEstimateQ2: 10500,
+ correctedEstimateQ3: 11200,
+ correctedEstimateQ4: 11800,
+ developmentType: "Капитальный ремонт",
+ developmentBlock: "Восточный",
+ reserveBankAXR: 400,
+ reserveBankKV: 600,
+ subRows: [
+ {
+ id: "sub_003_01",
+ project: "Подпроект А",
+ status: "Завершен",
+ techNumber: "2512_180601_А",
+ address: "г. Барнаул, ул. Пушкина, 15",
+ vspNumber: 1811,
+ krfDate: "11.04.2025",
+ fkDate: "26.10.2025",
+ boardDate: "03.11.2025",
+ openMoveCloseDate: "15.11.2025",
+ implementationMonths: 5,
+ koFinancing: 15000,
+ baseYearEstimate: "2025",
+ correctedEstimateQ2: 5000,
+ correctedEstimateQ3: 5500,
+ correctedEstimateQ4: 6000,
+ developmentType: "Реконструкция",
+ developmentBlock: "Восточный",
+ reserveBankAXR: 200,
+ reserveBankKV: 300,
+ },
+ ],
+ },
+ {
+ id: "proj_004",
+ project: "Башкирский_01",
+ status: "archived",
+ org_unit_id: 8,
+ techNumber: "2405_620501",
+ address: "Республика Башкортостан, проект развит...",
+ vspNumber: null,
+ krfDate: "03.06.2024",
+ fkDate: "14.06.2024",
+ boardDate: "02.07.2024",
+ openMoveCloseDate: "15.08.2024",
+ implementationMonths: 10,
+ koFinancing: 45000,
+ baseYearEstimate: "2024",
+ correctedEstimateQ2: 15000,
+ correctedEstimateQ3: 16000,
+ correctedEstimateQ4: 17000,
+ developmentType: "Новое строительство",
+ developmentBlock: "Центральный",
+ reserveBankAXR: 800,
+ reserveBankKV: 1200,
+ subRows: [],
+ },
+];
+
+// Вторая таблица - объект, где ключ - id проекта, значение - массив данных для этого проекта
+export const secondTableData = {
+ // Для проекта Алтайский_01 (3 дочерних строки)
+ 478: [
+ {
+ id: "sub_001_01",
+ project: "Смета_2026",
+ financingAXR: 11942,
+ financingAXRLimit: 12122,
+ financingAXRCurrent: 10502,
+ financingKV: 12482,
+ baseAXR: 2800,
+ baseAXRLimit: 3000,
+ baseAXRCurrent: 2500,
+ baseKV: 2900,
+ q2AXR: 3100,
+ q2AXRLimit: 3300,
+ q2AXRCurrent: 2800,
+ q2KV: 3200,
+ q3AXR: 3400,
+ q3AXRLimit: 3600,
+ q3AXRCurrent: 3100,
+ q3KV: 3500,
+ q4AXR: 3700,
+ q4AXRLimit: 3900,
+ q4AXRCurrent: 3400,
+ q4KV: 3800,
+ actualAXRQ1: 2400,
+ actualAXRLimitQ1: 2600,
+ actualAXRCurrentQ1: 2200,
+ actualAXRQ2: 2800,
+ actualAXRLimitQ2: 3000,
+ actualAXRCurrentQ2: 2600,
+ actualAXRQ3: 3200,
+ actualAXRLimitQ3: 3400,
+ actualAXRCurrentQ3: 3000,
+ actualAXRQ4: 3600,
+ actualAXRLimitQ4: 3800,
+ actualAXRCurrentQ4: 3400,
+ actualKVQ1: 2500,
+ actualKVLimitQ1: 2700,
+ actualKVCurrentQ1: 2300,
+ actualKVQ2: 2900,
+ actualKVLimitQ2: 3100,
+ actualKVCurrentQ2: 2700,
+ actualKVQ3: 3300,
+ actualKVLimitQ3: 3500,
+ actualKVCurrentQ3: 3100,
+ actualKVQ4: 3700,
+ actualKVLimitQ4: 3900,
+ actualKVCurrentQ4: 3500,
+ includeInEstimate2026: "Да",
+ developmentBlock2026: "Центральный",
+ impactRentAXR: 200,
+ impactOtherAXR: 100,
+ sumEstimateAXR: 11942,
+ sumEstimateKV: 12482,
+ },
+ {
+ id: "sub_001_02",
+ project: "Смета_2027",
+ financingAXR: 12079,
+ financingAXRLimit: 12259,
+ financingAXRCurrent: 10639,
+ financingKV: 12619,
+ baseAXR: 2900,
+ baseAXRLimit: 3100,
+ baseAXRCurrent: 2600,
+ baseKV: 3000,
+ q2AXR: 3200,
+ q2AXRLimit: 3400,
+ q2AXRCurrent: 2900,
+ q2KV: 3300,
+ q3AXR: 3500,
+ q3AXRLimit: 3700,
+ q3AXRCurrent: 3200,
+ q3KV: 3600,
+ q4AXR: 3800,
+ q4AXRLimit: 4000,
+ q4AXRCurrent: 3500,
+ q4KV: 3900,
+ actualAXRQ1: 2500,
+ actualAXRLimitQ1: 2700,
+ actualAXRCurrentQ1: 2300,
+ actualAXRQ2: 2900,
+ actualAXRLimitQ2: 3100,
+ actualAXRCurrentQ2: 2700,
+ actualAXRQ3: 3300,
+ actualAXRLimitQ3: 3500,
+ actualAXRCurrentQ3: 3100,
+ actualAXRQ4: 3700,
+ actualAXRLimitQ4: 3900,
+ actualAXRCurrentQ4: 3500,
+ actualKVQ1: 2600,
+ actualKVLimitQ1: 2800,
+ actualKVCurrentQ1: 2400,
+ actualKVQ2: 3000,
+ actualKVLimitQ2: 3200,
+ actualKVCurrentQ2: 2800,
+ actualKVQ3: 3400,
+ actualKVLimitQ3: 3600,
+ actualKVCurrentQ3: 3200,
+ actualKVQ4: 3800,
+ actualKVLimitQ4: 4000,
+ actualKVCurrentQ4: 3600,
+ includeInEstimate2026: "Нет",
+ developmentBlock2026: "Северный",
+ impactRentAXR: 250,
+ impactOtherAXR: 150,
+ sumEstimateAXR: 12079,
+ sumEstimateKV: 12619,
+ },
+ {
+ id: "sub_001_03",
+ project: "Смета_2028",
+ financingAXR: 12216,
+ financingAXRLimit: 12396,
+ financingAXRCurrent: 10776,
+ financingKV: 12756,
+ baseAXR: 3000,
+ baseAXRLimit: 3200,
+ baseAXRCurrent: 2700,
+ baseKV: 3100,
+ q2AXR: 3300,
+ q2AXRLimit: 3500,
+ q2AXRCurrent: 3000,
+ q2KV: 3400,
+ q3AXR: 3600,
+ q3AXRLimit: 3800,
+ q3AXRCurrent: 3300,
+ q3KV: 3700,
+ q4AXR: 3900,
+ q4AXRLimit: 4100,
+ q4AXRCurrent: 3600,
+ q4KV: 4000,
+ actualAXRQ1: 2600,
+ actualAXRLimitQ1: 2800,
+ actualAXRCurrentQ1: 2400,
+ actualAXRQ2: 3000,
+ actualAXRLimitQ2: 3200,
+ actualAXRCurrentQ2: 2800,
+ actualAXRQ3: 3400,
+ actualAXRLimitQ3: 3600,
+ actualAXRCurrentQ3: 3200,
+ actualAXRQ4: 3800,
+ actualAXRLimitQ4: 4000,
+ actualAXRCurrentQ4: 3600,
+ actualKVQ1: 2700,
+ actualKVLimitQ1: 2900,
+ actualKVCurrentQ1: 2500,
+ actualKVQ2: 3100,
+ actualKVLimitQ2: 3300,
+ actualKVCurrentQ2: 2900,
+ actualKVQ3: 3500,
+ actualKVLimitQ3: 3700,
+ actualKVCurrentQ3: 3300,
+ actualKVQ4: 3900,
+ actualKVLimitQ4: 4100,
+ actualKVCurrentQ4: 3700,
+ includeInEstimate2026: "Нет",
+ developmentBlock2026: "Южный",
+ impactRentAXR: 300,
+ impactOtherAXR: 200,
+ sumEstimateAXR: 12216,
+ sumEstimateKV: 12756,
+ },
+ ],
+
+ // Для проекта Алтайский_02 (0 дочерних строк - пустой массив)
+ proj_002: [],
+
+ // Для проекта Алтайский_03 (1 дочерняя строка)
+ proj_003: [
+ {
+ id: "sub_003_01",
+ project: "Подпроект А",
+ financingAXR: 15200,
+ financingAXRLimit: 15400,
+ financingAXRCurrent: 13800,
+ financingKV: 15800,
+ baseAXR: 3500,
+ baseAXRLimit: 3700,
+ baseAXRCurrent: 3200,
+ baseKV: 3600,
+ q2AXR: 3800,
+ q2AXRLimit: 4000,
+ q2AXRCurrent: 3500,
+ q2KV: 3900,
+ q3AXR: 4100,
+ q3AXRLimit: 4300,
+ q3AXRCurrent: 3800,
+ q3KV: 4200,
+ q4AXR: 4400,
+ q4AXRLimit: 4600,
+ q4AXRCurrent: 4100,
+ q4KV: 4500,
+ actualAXRQ1: 3200,
+ actualAXRLimitQ1: 3400,
+ actualAXRCurrentQ1: 3000,
+ actualAXRQ2: 3600,
+ actualAXRLimitQ2: 3800,
+ actualAXRCurrentQ2: 3400,
+ actualAXRQ3: 4000,
+ actualAXRLimitQ3: 4200,
+ actualAXRCurrentQ3: 3800,
+ actualAXRQ4: 4400,
+ actualAXRLimitQ4: 4600,
+ actualAXRCurrentQ4: 4200,
+ actualKVQ1: 3300,
+ actualKVLimitQ1: 3500,
+ actualKVCurrentQ1: 3100,
+ actualKVQ2: 3700,
+ actualKVLimitQ2: 3900,
+ actualKVCurrentQ2: 3500,
+ actualKVQ3: 4100,
+ actualKVLimitQ3: 4300,
+ actualKVCurrentQ3: 3900,
+ actualKVQ4: 4500,
+ actualKVLimitQ4: 4700,
+ actualKVCurrentQ4: 4300,
+ includeInEstimate2026: "Да",
+ developmentBlock2026: "Восточный",
+ impactRentAXR: 400,
+ impactOtherAXR: 200,
+ sumEstimateAXR: 15200,
+ sumEstimateKV: 15800,
+ },
+ ],
+
+ proj_004: [],
+};
diff --git a/web/src/pages/ProjectPages/utils/tableHandlers.js b/web/src/pages/ProjectPages/utils/tableHandlers.js
new file mode 100644
index 0000000..2736918
--- /dev/null
+++ b/web/src/pages/ProjectPages/utils/tableHandlers.js
@@ -0,0 +1,14 @@
+export const handleEditClick = (row, setEditModalOpen, setSelectedRowData) => {
+ setSelectedRowData(row.original);
+ setEditModalOpen(true);
+};
+
+export const handleNavigateClick = (row, navigate) => {
+ const projectId = row.original.id || row.original.project;
+ navigate(`/project/${projectId}`);
+};
+
+export const handleSaveEdit = (formData, updateTableData) => {
+ updateTableData(formData);
+ console.log('Сохраненные данные:', formData);
+};
\ No newline at end of file