сетка точками и выделение
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -289,7 +289,7 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="absolute inset-0 pointer-events-none"
|
className="absolute inset-0 pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `linear-gradient(to right, #e5e7eb 1px, transparent 1px), linear-gradient(to bottom, #e5e7eb 1px, transparent 1px)`,
|
backgroundImage: `radial-gradient(circle, #e5e7eb 1px, transparent 1px)`,
|
||||||
backgroundSize: `${gridSize}px ${gridSize}px`,
|
backgroundSize: `${gridSize}px ${gridSize}px`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -301,6 +301,7 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
|
|||||||
}, [onElementUpdate]);
|
}, [onElementUpdate]);
|
||||||
|
|
||||||
const handleCanvasClick = useCallback((e: React.MouseEvent) => {
|
const handleCanvasClick = useCallback((e: React.MouseEvent) => {
|
||||||
|
// Снимаем выделение при клике по пустому месту
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
setSelectedElementId(null);
|
setSelectedElementId(null);
|
||||||
}
|
}
|
||||||
@@ -324,6 +325,7 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="relative bg-white shadow-lg mx-auto my-8"
|
className="relative bg-white shadow-lg mx-auto my-8"
|
||||||
style={{ width: canvasSize.width, height: canvasSize.height }}
|
style={{ width: canvasSize.width, height: canvasSize.height }}
|
||||||
|
onClick={handleCanvasClick}
|
||||||
>
|
>
|
||||||
{GridPattern}
|
{GridPattern}
|
||||||
{elements.map((element) => (
|
{elements.map((element) => (
|
||||||
|
|||||||
@@ -1,116 +1,131 @@
|
|||||||
import { Plus, Wrench } from 'lucide-react';
|
import { Plus, Wrench } from 'lucide-react'
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { ElementConstructor } from '../../../../components/TemplateManager/ElementConstructor';
|
import { ElementConstructor } from '../../../../components/TemplateManager/ElementConstructor'
|
||||||
import { Button } from '../../../../components/ui/button';
|
import { Button } from '../../../../components/ui/button'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../../components/ui/card';
|
import {
|
||||||
import { useTemplateContext } from '../../../../contexts/TemplateContext';
|
Card,
|
||||||
import { FormLayoutSettings, Template, TemplateElement } from '../../../../types/template';
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '../../../../components/ui/card'
|
||||||
|
import { useTemplateContext } from '../../../../contexts/TemplateContext'
|
||||||
|
import {
|
||||||
|
FormLayoutSettings,
|
||||||
|
Template,
|
||||||
|
TemplateElement,
|
||||||
|
} from '../../../../types/template'
|
||||||
|
|
||||||
export const ElementsCreation: FC = () => {
|
export const ElementsCreation: FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const { templates, updateTemplate } = useTemplateContext();
|
const { templates, updateTemplate } = useTemplateContext()
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
|
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
|
||||||
const handleTemplateSelect = (template: Template) => {
|
const handleTemplateSelect = (template: Template) => {
|
||||||
setSelectedTemplate(template);
|
setSelectedTemplate(template)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleElementAdd = (element: TemplateElement) => {
|
const handleElementAdd = (element: TemplateElement) => {
|
||||||
console.log('handleElementAdd called with element:', element);
|
console.log('handleElementAdd called with element:', element)
|
||||||
|
|
||||||
if (!selectedTemplate) {
|
if (!selectedTemplate) {
|
||||||
console.log('No selected template');
|
console.log('No selected template')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedTemplate = {
|
const updatedTemplate = {
|
||||||
...selectedTemplate,
|
...selectedTemplate,
|
||||||
elements: [...selectedTemplate.elements, element],
|
elements: [...selectedTemplate.elements, element],
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
}
|
||||||
|
|
||||||
console.log('Updated template:', updatedTemplate);
|
console.log('Updated template:', updatedTemplate)
|
||||||
console.log('Elements count:', updatedTemplate.elements.length);
|
console.log('Elements count:', updatedTemplate.elements.length)
|
||||||
|
|
||||||
updateTemplate(updatedTemplate);
|
updateTemplate(updatedTemplate)
|
||||||
setSelectedTemplate(updatedTemplate);
|
setSelectedTemplate(updatedTemplate)
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const handleElementUpdate = (
|
||||||
|
elementId: string,
|
||||||
|
updates: Partial<TemplateElement>,
|
||||||
|
) => {
|
||||||
|
if (!selectedTemplate) return
|
||||||
|
|
||||||
const handleElementUpdate = (elementId: string, updates: Partial<TemplateElement>) => {
|
|
||||||
if (!selectedTemplate) return;
|
|
||||||
|
|
||||||
const updatedTemplate = {
|
const updatedTemplate = {
|
||||||
...selectedTemplate,
|
...selectedTemplate,
|
||||||
elements: selectedTemplate.elements.map(el =>
|
elements: selectedTemplate.elements.map((el) =>
|
||||||
el.id === elementId ? { ...el, ...updates } : el
|
el.id === elementId ? { ...el, ...updates } : el,
|
||||||
),
|
),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
}
|
||||||
|
|
||||||
updateTemplate(updatedTemplate);
|
updateTemplate(updatedTemplate)
|
||||||
setSelectedTemplate(updatedTemplate);
|
setSelectedTemplate(updatedTemplate)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleElementDelete = (elementId: string) => {
|
const handleElementDelete = (elementId: string) => {
|
||||||
if (!selectedTemplate) return;
|
if (!selectedTemplate) return
|
||||||
|
|
||||||
const updatedTemplate = {
|
const updatedTemplate = {
|
||||||
...selectedTemplate,
|
...selectedTemplate,
|
||||||
elements: selectedTemplate.elements.filter(el => el.id !== elementId),
|
elements: selectedTemplate.elements.filter((el) => el.id !== elementId),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
}
|
||||||
|
|
||||||
updateTemplate(updatedTemplate);
|
updateTemplate(updatedTemplate)
|
||||||
setSelectedTemplate(updatedTemplate);
|
setSelectedTemplate(updatedTemplate)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleElementsReorder = (reorderedElements: TemplateElement[]) => {
|
const handleElementsReorder = (reorderedElements: TemplateElement[]) => {
|
||||||
if (!selectedTemplate) return;
|
if (!selectedTemplate) return
|
||||||
|
|
||||||
// Обновляем порядок элементов
|
// Обновляем порядок элементов
|
||||||
const elementsWithOrder = reorderedElements.map((element, index) => ({
|
const elementsWithOrder = reorderedElements.map((element, index) => ({
|
||||||
...element,
|
...element,
|
||||||
order: index,
|
order: index,
|
||||||
}));
|
}))
|
||||||
|
|
||||||
const updatedTemplate = {
|
const updatedTemplate = {
|
||||||
...selectedTemplate,
|
...selectedTemplate,
|
||||||
elements: elementsWithOrder,
|
elements: elementsWithOrder,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
}
|
||||||
|
|
||||||
updateTemplate(updatedTemplate);
|
updateTemplate(updatedTemplate)
|
||||||
setSelectedTemplate(updatedTemplate);
|
setSelectedTemplate(updatedTemplate)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleLayoutSettingsChange = (settings: FormLayoutSettings) => {
|
const handleLayoutSettingsChange = (settings: FormLayoutSettings) => {
|
||||||
if (!selectedTemplate) return;
|
if (!selectedTemplate) return
|
||||||
|
|
||||||
const updatedTemplate = {
|
const updatedTemplate = {
|
||||||
...selectedTemplate,
|
...selectedTemplate,
|
||||||
layoutSettings: settings,
|
layoutSettings: settings,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
}
|
||||||
|
|
||||||
updateTemplate(updatedTemplate);
|
updateTemplate(updatedTemplate)
|
||||||
setSelectedTemplate(updatedTemplate);
|
setSelectedTemplate(updatedTemplate)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
setSelectedTemplate(null);
|
setSelectedTemplate(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
// Настройки отображения по умолчанию
|
// Настройки отображения по умолчанию
|
||||||
const defaultLayoutSettings: FormLayoutSettings = {
|
const defaultLayoutSettings: FormLayoutSettings = {
|
||||||
gridSize: 20,
|
gridSize: 20,
|
||||||
showGrid: true,
|
showGrid: true,
|
||||||
};
|
}
|
||||||
|
|
||||||
if (selectedTemplate) {
|
if (selectedTemplate) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<div className="bg-white border-b border-gray-200 p-4 sticky top-0 z-50">
|
<div className="sticky top-0 z-50 border-b border-gray-200 bg-white p-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button variant="ghost" onClick={handleBack}>
|
<Button variant="ghost" onClick={handleBack}>
|
||||||
@@ -120,26 +135,21 @@ export const ElementsCreation: FC = () => {
|
|||||||
<h1 className="text-xl font-semibold text-gray-800">
|
<h1 className="text-xl font-semibold text-gray-800">
|
||||||
Конструктор элементов: {selectedTemplate.name}
|
Конструктор элементов: {selectedTemplate.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-gray-600 mt-1">
|
<p className="mt-1 text-sm text-gray-600">
|
||||||
Создайте элементы формы и разместите их на холсте перетаскиванием
|
Создайте элементы формы и разместите их на холсте
|
||||||
|
перетаскиванием
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button onClick={() => navigate('/create-protocol')} variant="outline">
|
|
||||||
Предпросмотр формы
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => navigate('/templates')} variant="outline">
|
|
||||||
К шаблонам
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-[calc(100vh-104px)]">
|
<div className="h-[calc(100vh-104px)]">
|
||||||
<ElementConstructor
|
<ElementConstructor
|
||||||
elements={selectedTemplate.elements}
|
elements={selectedTemplate.elements}
|
||||||
layoutSettings={selectedTemplate.layoutSettings || defaultLayoutSettings}
|
layoutSettings={
|
||||||
|
selectedTemplate.layoutSettings || defaultLayoutSettings
|
||||||
|
}
|
||||||
onElementAdd={handleElementAdd}
|
onElementAdd={handleElementAdd}
|
||||||
onElementUpdate={handleElementUpdate}
|
onElementUpdate={handleElementUpdate}
|
||||||
onElementDelete={handleElementDelete}
|
onElementDelete={handleElementDelete}
|
||||||
@@ -148,19 +158,23 @@ export const ElementsCreation: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-7xl mx-auto">
|
<div className="mx-auto max-w-7xl p-6">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="mb-6 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Создание элементов</h1>
|
<h1 className="text-3xl font-bold text-gray-900">
|
||||||
<p className="text-gray-600 mt-2">Выберите шаблон для создания элементов интерфейса</p>
|
Создание элементов
|
||||||
|
</h1>
|
||||||
|
<p className="mt-2 text-gray-600">
|
||||||
|
Выберите шаблон для создания элементов интерфейса
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => navigate('/templates')}
|
onClick={() => navigate('/templates')}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
@@ -170,19 +184,26 @@ export const ElementsCreation: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{templates.length === 0 ? (
|
{templates.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="py-12 text-center">
|
||||||
<Wrench className="h-16 w-16 text-gray-400 mx-auto mb-4" />
|
<Wrench className="mx-auto mb-4 h-16 w-16 text-gray-400" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Нет доступных шаблонов</h3>
|
<h3 className="mb-2 text-lg font-medium text-gray-900">
|
||||||
<p className="text-gray-600 mb-6">Создайте шаблон протокола для начала работы с элементами</p>
|
Нет доступных шаблонов
|
||||||
|
</h3>
|
||||||
|
<p className="mb-6 text-gray-600">
|
||||||
|
Создайте шаблон протокола для начала работы с элементами
|
||||||
|
</p>
|
||||||
<Button onClick={() => navigate('/templates')}>
|
<Button onClick={() => navigate('/templates')}>
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
Создать шаблон
|
Создать шаблон
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{templates.map((template) => (
|
{templates.map((template) => (
|
||||||
<Card key={template.id} className="hover:shadow-lg transition-shadow cursor-pointer">
|
<Card
|
||||||
|
key={template.id}
|
||||||
|
className="cursor-pointer transition-shadow hover:shadow-lg"
|
||||||
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">{template.name}</CardTitle>
|
<CardTitle className="text-lg">{template.name}</CardTitle>
|
||||||
{template.description && (
|
{template.description && (
|
||||||
@@ -197,7 +218,7 @@ export const ElementsCreation: FC = () => {
|
|||||||
<Wrench className="h-4 w-4" />
|
<Wrench className="h-4 w-4" />
|
||||||
<span>{template.elements.length} элементов создано</span>
|
<span>{template.elements.length} элементов создано</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 mt-2">
|
<div className="mt-2 text-xs text-gray-500">
|
||||||
Обновлен: {template.updatedAt.toLocaleDateString('ru-RU')}
|
Обновлен: {template.updatedAt.toLocaleDateString('ru-RU')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,5 +236,5 @@ export const ElementsCreation: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
|||||||
{/* Полноэкранная форма */}
|
{/* Полноэкранная форма */}
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<div
|
<div
|
||||||
className="relative bg-white shadow-lg mx-auto my-8"
|
className="relative mx-auto my-8"
|
||||||
style={{ width: canvasSize.width, height: canvasSize.height }}
|
style={{ width: canvasSize.width, height: canvasSize.height }}
|
||||||
>
|
>
|
||||||
{template.elements.map((element) => {
|
{template.elements.map((element) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user