сетка точками и выделение

This commit is contained in:
2025-07-18 08:35:36 +03:00
parent 7189266bdb
commit fda99279d3
4 changed files with 540 additions and 400 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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) => (

View File

@@ -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>
); )
}; }

View File

@@ -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) => {