ручной рефакторинг
This commit is contained in:
@@ -8,15 +8,15 @@ const Layout: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="h-screen flex flex-col bg-gray-50">
|
<div className="h-screen flex flex-col bg-gray-50">
|
||||||
{/* Верхняя навигация */}
|
{/* Верхняя навигация */}
|
||||||
<nav className="bg-white border-b border-gray-200 px-6 py-4">
|
<nav className="bg-white border-b border-gray-200 px-6 py-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-8">
|
<div className="flex items-center gap-8">
|
||||||
<h1 className="text-xl font-bold text-gray-900">Система протоколов</h1>
|
<h1 className="text-xl font-bold text-gray-900">Protoc</h1>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link
|
<Link
|
||||||
to="/templates"
|
to="/templates"
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`flex items-center gap-2 px-3 py-1 rounded-md text-sm font-medium transition-colors ${
|
||||||
location.pathname === '/templates'
|
location.pathname === '/templates'
|
||||||
? 'bg-blue-100 text-blue-700'
|
? 'bg-blue-100 text-blue-700'
|
||||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
||||||
@@ -28,7 +28,7 @@ const Layout: FC = () => {
|
|||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/elements"
|
to="/elements"
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`flex items-center gap-2 px-3 py-1 rounded-md text-sm font-medium transition-colors ${
|
||||||
location.pathname === '/elements'
|
location.pathname === '/elements'
|
||||||
? 'bg-blue-100 text-blue-700'
|
? 'bg-blue-100 text-blue-700'
|
||||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
||||||
@@ -40,7 +40,7 @@ const Layout: FC = () => {
|
|||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/create-protocol"
|
to="/create-protocol"
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`flex items-center gap-2 px-3 py-1 rounded-md text-sm font-medium transition-colors ${
|
||||||
location.pathname === '/create-protocol'
|
location.pathname === '/create-protocol'
|
||||||
? 'bg-blue-100 text-blue-700'
|
? 'bg-blue-100 text-blue-700'
|
||||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ type SheetType = 'report' | 'calculations';
|
|||||||
const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
||||||
const multiSheetEngine = useMultiSheetEngine({
|
const multiSheetEngine = useMultiSheetEngine({
|
||||||
sheets: [
|
sheets: [
|
||||||
{ name: 'Report', rows: 50, cols: 20 },
|
{ name: 'Report', rows: 90, cols: 20 },
|
||||||
{ name: 'Calculations', rows: 50, cols: 20 }
|
{ name: 'Calculations', rows: 90, cols: 20 }
|
||||||
],
|
],
|
||||||
initialData: templateData || {}
|
initialData: templateData || {}
|
||||||
});
|
});
|
||||||
@@ -27,7 +27,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
|
|
||||||
// Объединенные состояния для обеих таблиц
|
// Объединенные состояния для обеих таблиц
|
||||||
const [sheetData, setSheetData] = useState<Record<SheetType, CellData[][]>>({
|
const [sheetData, setSheetData] = useState<Record<SheetType, CellData[][]>>({
|
||||||
report: Array(50).fill(null).map(() =>
|
report: Array(90).fill(null).map(() =>
|
||||||
Array(20).fill(null).map(() => ({
|
Array(20).fill(null).map(() => ({
|
||||||
value: '',
|
value: '',
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
@@ -35,7 +35,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
templateValue: ''
|
templateValue: ''
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
calculations: Array(50).fill(null).map(() =>
|
calculations: Array(90).fill(null).map(() =>
|
||||||
Array(20).fill(null).map(() => ({
|
Array(20).fill(null).map(() => ({
|
||||||
value: '',
|
value: '',
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
@@ -56,7 +56,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
calculations: Array(20).fill(96)
|
calculations: Array(20).fill(96)
|
||||||
});
|
});
|
||||||
|
|
||||||
const [rowHeights] = useState<number[]>(Array(50).fill(32));
|
const [rowHeights] = useState<number[]>(Array(90).fill(32));
|
||||||
|
|
||||||
// Объединенные refs
|
// Объединенные refs
|
||||||
const containerRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
|
const containerRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
|
||||||
@@ -76,8 +76,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
|
|
||||||
// Инициализация refs
|
// Инициализация refs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRefs.current.report = Array(50).fill(null).map(() => Array(20).fill(null));
|
inputRefs.current.report = Array(90).fill(null).map(() => Array(20).fill(null));
|
||||||
inputRefs.current.calculations = Array(50).fill(null).map(() => Array(20).fill(null));
|
inputRefs.current.calculations = Array(90).fill(null).map(() => Array(20).fill(null));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Загрузка шаблонных данных
|
// Загрузка шаблонных данных
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui
|
|||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog';
|
||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { TemplateEditor } from './TemplateEditor';
|
import { TemplateEditor } from './TemplateEditor';
|
||||||
import { TemplateRenderer } from './TemplateRenderer';
|
|
||||||
|
|
||||||
interface TemplateManagerProps {
|
interface TemplateManagerProps {
|
||||||
onTemplateSelect?: (template: Template) => void;
|
onTemplateSelect?: (template: Template) => void;
|
||||||
@@ -65,16 +64,6 @@ export const TemplateManager: React.FC<TemplateManagerProps> = ({ onTemplateSele
|
|||||||
setIsEditMode(true);
|
setIsEditMode(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selectedTemplate && !isEditMode) {
|
|
||||||
return (
|
|
||||||
<TemplateRenderer
|
|
||||||
template={selectedTemplate}
|
|
||||||
onBack={() => setSelectedTemplate(null)}
|
|
||||||
onEdit={() => setIsEditMode(true)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedTemplate && isEditMode) {
|
if (selectedTemplate && isEditMode) {
|
||||||
return (
|
return (
|
||||||
<TemplateEditor
|
<TemplateEditor
|
||||||
@@ -104,6 +93,7 @@ export const TemplateManager: React.FC<TemplateManagerProps> = ({ onTemplateSele
|
|||||||
Создать шаблон
|
Создать шаблон
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Создать новый шаблон</DialogTitle>
|
<DialogTitle>Создать новый шаблон</DialogTitle>
|
||||||
@@ -198,7 +188,7 @@ export const TemplateManager: React.FC<TemplateManagerProps> = ({ onTemplateSele
|
|||||||
<span>{template.elements.length} элементов интерфейса</span>
|
<span>{template.elements.length} элементов интерфейса</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 mt-2">
|
<div className="text-xs text-gray-500 mt-2">
|
||||||
Создан: {template.createdAt.toLocaleDateString()}
|
Создан: {template.createdAt.toLocaleDateString('ru-RU')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 mt-4">
|
<div className="flex gap-2 mt-4">
|
||||||
@@ -210,16 +200,6 @@ export const TemplateManager: React.FC<TemplateManagerProps> = ({ onTemplateSele
|
|||||||
>
|
>
|
||||||
Предпросмотр
|
Предпросмотр
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleEditTemplate(template);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Настроить
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,331 +0,0 @@
|
|||||||
import { ArrowLeft, Send, Settings } from 'lucide-react';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { ExportData, Template, TemplateElement } from '../../types/template';
|
|
||||||
import DualSpreadsheet from '../DualSpreadsheet/DualSpreadsheet';
|
|
||||||
import { Button } from '../ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
|
||||||
import { Checkbox } from '../ui/checkbox';
|
|
||||||
import { Input } from '../ui/input';
|
|
||||||
import { RadioGroup, RadioGroupItem } from '../ui/radio-group';
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
|
||||||
import { Textarea } from '../ui/textarea';
|
|
||||||
|
|
||||||
interface TemplateRendererProps {
|
|
||||||
template: Template;
|
|
||||||
onBack: () => void;
|
|
||||||
onEdit: () => void;
|
|
||||||
hideSpreadsheet?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TemplateRenderer: React.FC<TemplateRendererProps> = ({
|
|
||||||
template,
|
|
||||||
onBack,
|
|
||||||
onEdit,
|
|
||||||
hideSpreadsheet = false,
|
|
||||||
}) => {
|
|
||||||
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
||||||
const [spreadsheetData, setSpreadsheetData] = useState<Record<string, any>>(template.excelData || {});
|
|
||||||
|
|
||||||
// Инициализация формы значениями по умолчанию
|
|
||||||
useEffect(() => {
|
|
||||||
const initialData: Record<string, any> = {};
|
|
||||||
template.elements.forEach(element => {
|
|
||||||
if (element.defaultValue) {
|
|
||||||
initialData[element.id] = element.defaultValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setFormData(initialData);
|
|
||||||
}, [template.elements]);
|
|
||||||
|
|
||||||
// Обновление данных таблицы при изменении формы
|
|
||||||
useEffect(() => {
|
|
||||||
const updatedData = { ...template.excelData || {}, ...spreadsheetData };
|
|
||||||
|
|
||||||
template.elements.forEach(element => {
|
|
||||||
const value = formData[element.id];
|
|
||||||
if (value !== undefined && value !== null && value !== '') {
|
|
||||||
updatedData[element.targetCell] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setSpreadsheetData(updatedData);
|
|
||||||
}, [formData, template.elements, template.excelData]);
|
|
||||||
|
|
||||||
const handleInputChange = (elementId: string, value: any) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
[elementId]: value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Очищаем ошибку при изменении значения
|
|
||||||
if (errors[elementId]) {
|
|
||||||
setErrors(prev => {
|
|
||||||
const newErrors = { ...prev };
|
|
||||||
delete newErrors[elementId];
|
|
||||||
return newErrors;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateForm = (): boolean => {
|
|
||||||
const newErrors: Record<string, string> = {};
|
|
||||||
|
|
||||||
template.elements.forEach(element => {
|
|
||||||
if (element.required) {
|
|
||||||
const value = formData[element.id];
|
|
||||||
if (!value || (typeof value === 'string' && value.trim() === '')) {
|
|
||||||
newErrors[element.id] = `Поле "${element.label}" обязательно для заполнения`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setErrors(newErrors);
|
|
||||||
return Object.keys(newErrors).length === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if (!validateForm()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем измененные ячейки из таблицы
|
|
||||||
const changedCells = (window as any).getChangedCells?.() || {};
|
|
||||||
|
|
||||||
// Создаем данные для экспорта
|
|
||||||
const exportData: ExportData = {
|
|
||||||
templateId: template.id,
|
|
||||||
templateName: template.name,
|
|
||||||
excelFile: template.excelFile!,
|
|
||||||
cellUpdates: {
|
|
||||||
...changedCells,
|
|
||||||
// Добавляем значения из формы
|
|
||||||
...template.elements.reduce((acc, element) => {
|
|
||||||
const value = formData[element.id];
|
|
||||||
if (value !== undefined && value !== null && value !== '') {
|
|
||||||
acc[element.targetCell] = { rawValue: value, calculatedValue: value };
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, any>),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Данные для отправки на бэкенд:', exportData);
|
|
||||||
|
|
||||||
// Здесь будет отправка на бэкенд
|
|
||||||
alert(`Отчет "${template.name}" успешно сформирован!\n\nПроверьте консоль для подробностей.`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFormElement = (element: TemplateElement) => {
|
|
||||||
const value = formData[element.id] || '';
|
|
||||||
const hasError = !!errors[element.id];
|
|
||||||
|
|
||||||
const commonProps = {
|
|
||||||
className: hasError ? 'border-red-500' : '',
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (element.type) {
|
|
||||||
case 'text':
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
{...commonProps}
|
|
||||||
type="text"
|
|
||||||
placeholder={element.placeholder}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => handleInputChange(element.id, e.target.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'textarea':
|
|
||||||
return (
|
|
||||||
<Textarea
|
|
||||||
{...commonProps}
|
|
||||||
placeholder={element.placeholder}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => handleInputChange(element.id, e.target.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'number':
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
{...commonProps}
|
|
||||||
type="number"
|
|
||||||
placeholder={element.placeholder}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => handleInputChange(element.id, e.target.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'date':
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
{...commonProps}
|
|
||||||
type="date"
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => handleInputChange(element.id, e.target.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'select':
|
|
||||||
return (
|
|
||||||
<Select value={value} onValueChange={(val) => handleInputChange(element.id, val)}>
|
|
||||||
<SelectTrigger className={hasError ? 'border-red-500' : ''}>
|
|
||||||
<SelectValue placeholder={element.placeholder || 'Выберите значение'} />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{element.options?.map((option) => (
|
|
||||||
<SelectItem key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'radio':
|
|
||||||
return (
|
|
||||||
<RadioGroup value={value} onValueChange={(val) => handleInputChange(element.id, val)}>
|
|
||||||
{element.options?.map((option) => (
|
|
||||||
<div key={option.value} className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value={option.value} id={`${element.id}-${option.value}`} />
|
|
||||||
<label htmlFor={`${element.id}-${option.value}`} className="text-sm font-medium">
|
|
||||||
{option.label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'checkbox':
|
|
||||||
return (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={element.id}
|
|
||||||
checked={!!value}
|
|
||||||
onCheckedChange={(checked) => handleInputChange(element.id, checked)}
|
|
||||||
/>
|
|
||||||
<label htmlFor={element.id} className="text-sm font-medium">
|
|
||||||
{element.placeholder || 'Отметить'}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return <div>Неизвестный тип элемента: {element.type}</div>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!template.excelFile) {
|
|
||||||
return (
|
|
||||||
<div className="p-6 max-w-7xl mx-auto">
|
|
||||||
<div className="flex items-center gap-4 mb-6">
|
|
||||||
<Button variant="ghost" size="icon" onClick={onBack}>
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold text-gray-900">{template.name}</h1>
|
|
||||||
<p className="text-gray-600">Шаблон не настроен</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Settings className="h-5 w-5" />
|
|
||||||
Необходима настройка
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<p className="text-gray-600 mb-4">
|
|
||||||
Для использования этого шаблона необходимо загрузить Excel файл и настроить элементы интерфейса.
|
|
||||||
</p>
|
|
||||||
<Button onClick={onEdit}>
|
|
||||||
Настроить шаблон
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-screen flex flex-col bg-gray-50">
|
|
||||||
{/* Заголовок */}
|
|
||||||
<div className="bg-white border-b border-gray-200 p-4 flex-shrink-0">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" size="icon" onClick={onBack}>
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-xl font-semibold text-gray-800">{template.name}</h1>
|
|
||||||
<p className="text-sm text-gray-600 mt-1">
|
|
||||||
Заполните форму для создания отчета
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={onEdit}>
|
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
|
||||||
Настроить
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleSubmit} className="flex items-center gap-2">
|
|
||||||
<Send className="h-4 w-4" />
|
|
||||||
Сформировать отчет
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 flex min-h-0">
|
|
||||||
{/* Левая панель - форма */}
|
|
||||||
<div className={`${hideSpreadsheet ? "w-full" : "w-80 border-r border-gray-200"} bg-white flex flex-col`}>
|
|
||||||
<div className="p-4 border-b border-gray-200 flex-shrink-0">
|
|
||||||
<h2 className="text-lg font-semibold text-gray-800">Параметры отчета</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
|
||||||
<div className="space-y-6">
|
|
||||||
{template.elements.map((element) => (
|
|
||||||
<div key={element.id} className="space-y-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700 flex items-center gap-1">
|
|
||||||
{element.label}
|
|
||||||
{element.required && <span className="text-red-500">*</span>}
|
|
||||||
</label>
|
|
||||||
{renderFormElement(element)}
|
|
||||||
{errors[element.id] && (
|
|
||||||
<p className="text-sm text-red-600">{errors[element.id]}</p>
|
|
||||||
)}
|
|
||||||
{!hideSpreadsheet && (
|
|
||||||
<div className="text-xs text-gray-500">
|
|
||||||
Ячейка: {element.targetCell}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Правая панель - таблица (скрыта если hideSpreadsheet=true) */}
|
|
||||||
{!hideSpreadsheet && (
|
|
||||||
<div className="flex-1 min-h-0">
|
|
||||||
<DualSpreadsheet
|
|
||||||
templateData={{ Report: spreadsheetData }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Скрытый DualSpreadsheet для работы в фоне */}
|
|
||||||
{hideSpreadsheet && (
|
|
||||||
<div className="hidden">
|
|
||||||
<DualSpreadsheet
|
|
||||||
templateData={{ Report: spreadsheetData }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,41 +1,14 @@
|
|||||||
import { FileText, Plus } from 'lucide-react';
|
import { FileText, Plus } from 'lucide-react';
|
||||||
import { FC, useState } from 'react';
|
import { FC } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { TemplateRenderer } from '../../../../components/TemplateManager/TemplateRenderer';
|
|
||||||
import { Button } from '../../../../components/ui/button';
|
import { Button } from '../../../../components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../../components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../../components/ui/card';
|
||||||
import { useTemplateContext } from '../../../../contexts/TemplateContext';
|
import { useTemplateContext } from '../../../../contexts/TemplateContext';
|
||||||
import { Template } from '../../../../types/template';
|
|
||||||
|
|
||||||
export const ProtocolCreation: FC = () => {
|
export const ProtocolCreation: FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { templates } = useTemplateContext();
|
const { templates } = useTemplateContext();
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
|
|
||||||
|
|
||||||
const handleTemplateSelect = (template: Template) => {
|
|
||||||
setSelectedTemplate(template);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
setSelectedTemplate(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEdit = () => {
|
|
||||||
// Переходим на страницу редактирования шаблона
|
|
||||||
navigate('/templates');
|
|
||||||
};
|
|
||||||
|
|
||||||
if (selectedTemplate) {
|
|
||||||
return (
|
|
||||||
<TemplateRenderer
|
|
||||||
template={selectedTemplate}
|
|
||||||
onBack={handleBack}
|
|
||||||
onEdit={handleEdit}
|
|
||||||
hideSpreadsheet={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-7xl mx-auto">
|
<div className="p-6 max-w-7xl mx-auto">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user