diff --git a/src/app/App.tsx b/src/app/App.tsx index ca0690b..f624d3a 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,11 +1,23 @@ import { FC } from "react"; -import { TemplateManager } from "../components/TemplateManager/TemplateManager"; +import { Navigate, Route, Routes } from "react-router-dom"; +import { TemplateProvider } from "../contexts/TemplateContext"; +import { ElementsCreation } from "../pages/ElementsCreation"; +import { ProtocolCreation } from "../pages/ProtocolCreation"; +import { TemplateSetup } from "../pages/TemplateSetup"; +import { Layout } from "./Layout"; const App: FC = () => { return ( -
- -
+ + + }> + } /> + } /> + } /> + } /> + + + ); }; diff --git a/src/app/Layout/Layout.tsx b/src/app/Layout/Layout.tsx index a9db1a4..b54cec9 100644 --- a/src/app/Layout/Layout.tsx +++ b/src/app/Layout/Layout.tsx @@ -1,10 +1,61 @@ +import { FileText, Settings, Wrench } from "lucide-react"; import { FC } from "react"; -import { Outlet } from "react-router-dom"; +import { Link, Outlet, useLocation } from "react-router-dom"; const Layout: FC = () => { + const location = useLocation(); + return ( -
-
+
+ {/* Верхняя навигация */} + + + {/* Основной контент */} +
diff --git a/src/components/DualSpreadsheet/DualSpreadsheet.tsx b/src/components/DualSpreadsheet/DualSpreadsheet.tsx index 601f6a9..c598a39 100644 --- a/src/components/DualSpreadsheet/DualSpreadsheet.tsx +++ b/src/components/DualSpreadsheet/DualSpreadsheet.tsx @@ -17,8 +17,8 @@ type SheetType = 'report' | 'calculations'; const DualSpreadsheet: FC = ({ templateData = {} }) => { const multiSheetEngine = useMultiSheetEngine({ sheets: [ - { name: 'Report', rows: 20, cols: 10 }, - { name: 'Calculations', rows: 20, cols: 10 } + { name: 'Report', rows: 50, cols: 20 }, + { name: 'Calculations', rows: 50, cols: 20 } ], initialData: templateData || {} }); @@ -27,16 +27,16 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { // Объединенные состояния для обеих таблиц const [sheetData, setSheetData] = useState>({ - report: Array(20).fill(null).map(() => - Array(10).fill(null).map(() => ({ + report: Array(50).fill(null).map(() => + Array(20).fill(null).map(() => ({ value: '', isSelected: false, isModified: false, templateValue: '' })) ), - calculations: Array(20).fill(null).map(() => - Array(10).fill(null).map(() => ({ + calculations: Array(50).fill(null).map(() => + Array(20).fill(null).map(() => ({ value: '', isSelected: false, isModified: false @@ -52,11 +52,11 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { // Объединенные размеры колонок const [columnWidths, setColumnWidths] = useState>({ - report: Array(10).fill(96), - calculations: Array(10).fill(96) + report: Array(20).fill(96), + calculations: Array(20).fill(96) }); - const [rowHeights] = useState(Array(20).fill(32)); + const [rowHeights] = useState(Array(50).fill(32)); // Объединенные refs const containerRefs = useRef>({ @@ -76,8 +76,8 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { // Инициализация refs useEffect(() => { - inputRefs.current.report = Array(20).fill(null).map(() => Array(10).fill(null)); - inputRefs.current.calculations = Array(20).fill(null).map(() => Array(10).fill(null)); + inputRefs.current.report = Array(50).fill(null).map(() => Array(20).fill(null)); + inputRefs.current.calculations = Array(50).fill(null).map(() => Array(20).fill(null)); }, []); // Загрузка шаблонных данных @@ -99,16 +99,16 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { })); // Загружаем данные в движок - Object.entries(templateData.Report).forEach(([cellName, value]) => { - const match = cellName.match(/^([A-Z]+)(\d+)$/); - if (match) { - const col = match[1].charCodeAt(0) - 65; - const row = parseInt(match[2]) - 1; - if (row >= 0 && row < 20 && col >= 0 && col < 10) { - multiSheetEngine.setCellValue('Report', row, col, String(value)); + Object.entries(templateData.Report).forEach(([cellName, value]) => { + const match = cellName.match(/^([A-Z]+)(\d+)$/); + if (match) { + const col = match[1].charCodeAt(0) - 65; + const row = parseInt(match[2]) - 1; + if (row >= 0 && row < 50 && col >= 0 && col < 20) { + multiSheetEngine.setCellValue('Report', row, col, String(value)); + } } - } - }); + }); } }, [templateData]); @@ -264,8 +264,8 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { const moveSelection = (deltaRow: number, deltaCol: number) => { if (!selectedCell) return; - const newRow = Math.max(0, Math.min(19, selectedCell.row + deltaRow)); - const newCol = Math.max(0, Math.min(9, selectedCell.col + deltaCol)); + const newRow = Math.max(0, Math.min(49, selectedCell.row + deltaRow)); + const newCol = Math.max(0, Math.min(19, selectedCell.col + deltaCol)); setSelectedCell({ row: newRow, col: newCol }); setIsEditing(false); @@ -413,17 +413,26 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { // Универсальный рендер таблицы const renderSpreadsheet = (sheetType: SheetType) => { - const cells = sheetData[sheetType]; - const sheetName = sheetType === 'report' ? 'Report' : 'Calculations'; + const data = sheetData[sheetType]; const widths = columnWidths[sheetType]; return ( -
- a + b, 0)}px` }}> +
containerRefs.current[sheetType] = el} + className="bg-white rounded-lg shadow-md overflow-auto border border-gray-300" + style={{ + width: '100%', + height: '100%', + position: 'relative', + }} + onKeyDown={handleKeyDown} + tabIndex={0} + > +
- {Array.from({ length: 10 }, (_, i) => ( + {Array.from({ length: 20 }, (_, i) => ( - {cells.map((row, rowIndex) => ( + {data.map((row, rowIndex) => ( {row.map((cell, colIndex) => ( - ))} @@ -562,73 +535,36 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { }; return ( -
- {/* Заголовок */} -
-
-
-

Система отчетов

-
-
-
- Измененные ячейки -
-
-
- Шаблонные данные -
-
- Ctrl+Z - восстановить шаблон -
-
-
- -
-
- +
{/* Двойная панель */} -
+
{/* Левая панель - Отчет */} -
-
-

Отчет

-
- -
containerRefs.current.report = el} - className="flex-1 bg-white outline-none overflow-hidden" - tabIndex={activeSheet === 'report' ? 0 : -1} - onKeyDown={activeSheet === 'report' ? handleKeyDown : undefined} - onClick={() => setActiveSheet('report')} - > - {renderSpreadsheet('report')} -
+
+

Отчет

+ {renderSpreadsheet('report')}
{/* Правая панель - Вычисления */}
-
-

Вычисления

-
- -
containerRefs.current.calculations = el} - className="flex-1 bg-white outline-none overflow-hidden" - tabIndex={activeSheet === 'calculations' ? 0 : -1} - onKeyDown={activeSheet === 'calculations' ? handleKeyDown : undefined} - onClick={() => setActiveSheet('calculations')} - > - {renderSpreadsheet('calculations')} -
+

Расчеты

+ {renderSpreadsheet('calculations')}
+ +
+ +
+ +
+ +
); }; -export default DualSpreadsheet; \ No newline at end of file +export default DualSpreadsheet; \ No newline at end of file diff --git a/src/components/TemplateManager/ElementConstructor.tsx b/src/components/TemplateManager/ElementConstructor.tsx index 74c91b2..5845dff 100644 --- a/src/components/TemplateManager/ElementConstructor.tsx +++ b/src/components/TemplateManager/ElementConstructor.tsx @@ -4,7 +4,7 @@ import { ElementType, TemplateElement } from '../../types/template'; import { Button } from '../ui/button'; import { Card, CardContent, CardHeader } from '../ui/card'; import { Checkbox } from '../ui/checkbox'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog'; import { Input } from '../ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; @@ -231,9 +231,9 @@ export const ElementConstructor: React.FC = ({ }; return ( -
-
-

Конструктор элементов

+
+
+

Элементы формы

-
- {elements.map(element => ( - - -
- {ELEMENT_TYPES.find(t => t.type === element.type)?.icon} -

{element.label}

-
-
- !isOpen && handleCancelEdit()} - > - - - - - - Редактировать элемент - - -
- +
+ ) : ( + elements.map(element => ( + + +
+ {ELEMENT_TYPES.find(t => t.type === element.type)?.icon} +

{element.label}

+
+
+ !isOpen && handleCancelEdit()} + > + + - -
-
-
+ + + + Редактировать элемент + + Измените настройки элемента формы + + + +
+ + +
+
+ - -
-
- -
-

- Тип: {ELEMENT_TYPES.find(t => t.type === element.type)?.label} -

-

- Имя: {element.name} -

-

- Ячейка: {element.targetCell} -

-
-
-
- ))} + +
+ + +
+

+ Тип: {ELEMENT_TYPES.find(t => t.type === element.type)?.label} +

+

+ Имя: {element.name} +

+

+ Ячейка: {element.targetCell} +

+
+
+ + )) + )}
); diff --git a/src/components/TemplateManager/TemplateEditor.tsx b/src/components/TemplateManager/TemplateEditor.tsx index 927d022..1171e03 100644 --- a/src/components/TemplateManager/TemplateEditor.tsx +++ b/src/components/TemplateManager/TemplateEditor.tsx @@ -1,10 +1,10 @@ -import { ArrowLeft, Save, Upload } from 'lucide-react'; +import { ArrowLeft, Save, Upload, Wrench } from 'lucide-react'; import React, { useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import * as XLSX from 'xlsx'; -import { Template, TemplateElement } from '../../types/template'; +import { Template } from '../../types/template'; import DualSpreadsheet from '../DualSpreadsheet/DualSpreadsheet'; import { Button } from '../ui/button'; -import { ElementConstructor } from './ElementConstructor'; interface TemplateEditorProps { template: Template; @@ -22,6 +22,7 @@ export const TemplateEditor: React.FC = ({ const [editedTemplate, setEditedTemplate] = useState
= ({ templateData = {} }) => {
{rowIndex + 1} { - if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex && activeSheet === sheetType) { - return; - } - if (activeSheet !== sheetType) { - setActiveSheet(sheetType); - } + if (activeSheet !== sheetType) setActiveSheet(sheetType); handleCellClick(rowIndex, colIndex); }} - onDoubleClick={() => { - if (activeSheet !== sheetType) { - setActiveSheet(sheetType); - } - handleCellDoubleClick(rowIndex, colIndex); - }} + onDoubleClick={() => handleCellDoubleClick(rowIndex, colIndex)} > - { - const newValue = e.target.value; - handleCellChange(rowIndex, colIndex, newValue, false); - setFormulaBarValue(newValue); - - // Отслеживаем изменения в ячейке - if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex && activeSheet === sheetType) { - setHasEditingChanges(newValue !== initialEditingValue); - } - }} - onFocus={(e) => { - if (activeSheet !== sheetType) { - setActiveSheet(sheetType); - } - setSelectedCell({ row: rowIndex, col: colIndex }); - if (!isEditing || activeSheet !== sheetType) { - (e.target as HTMLInputElement).blur(); - } - }} - onBlur={() => stopEditing(true)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - stopEditing(true); - moveSelection(1, 0); - } else if (e.key === 'Escape') { - e.preventDefault(); - stopEditing(false); - } - }} - onMouseUp={(e) => { - if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex && activeSheet === sheetType) { - e.stopPropagation(); - } - }} - readOnly={!isEditing || selectedCell?.row !== rowIndex || selectedCell?.col !== colIndex || activeSheet !== sheetType} - ref={(el) => { - if (inputRefs.current[sheetType][rowIndex]) { - inputRefs.current[sheetType][rowIndex][colIndex] = el; - } - }} - /> + {isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex ? ( + { + if (inputRefs.current[sheetType][rowIndex]) { + inputRefs.current[sheetType][rowIndex][colIndex] = el; + } + }} + type="text" + value={sheetData[sheetType][rowIndex][colIndex].value} + onChange={(e) => handleCellChange(rowIndex, colIndex, e.target.value)} + onBlur={() => stopEditing(true)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + stopEditing(true); + } else if (e.key === 'Escape') { + stopEditing(false); + } + }} + className="absolute top-0 left-0 w-full h-full p-2 border-2 border-blue-500 z-20 box-border" + /> + ) : ( +
+ {String(multiSheetEngine.getCellValue(sheetType === 'report' ? 'Report' : 'Calculations', rowIndex, colIndex) || '')} +
+ )}