новые стили редактирования элементов

This commit is contained in:
2025-07-24 13:55:43 +03:00
parent 80e97f8334
commit a13bbc7a68
3 changed files with 424 additions and 388 deletions

View File

@@ -44,35 +44,28 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({
} }
return ( return (
<div className="space-y-6"> <div className="space-y-4">
<div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium">
<Settings className="h-4 w-4" />
Настройки эталонов
</label>
<p className="text-xs text-gray-500">
Настройте параметры для выбора измерительных эталонов
</p>
</div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">Максимум эталонов</label> <label className="text-sm font-medium">Максимум эталонов</label>
<Select <Select
value={safeConfig.maxItems.toString()} value={safeConfig.maxItems.toString()}
onValueChange={value => updateConfig({ maxItems: parseInt(value) })} onValueChange={value => updateConfig({ maxItems: parseInt(value) })}
> >
<SelectTrigger> <SelectTrigger className="h-8 w-16">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="3">3 эталона</SelectItem> <SelectItem value="3">3</SelectItem>
<SelectItem value="5">5 эталонов</SelectItem> <SelectItem value="7">7</SelectItem>
<SelectItem value="7">7 эталонов</SelectItem> <SelectItem value="9">9</SelectItem>
<SelectItem value="10">10 эталонов</SelectItem> <SelectItem value="10">10</SelectItem>
<SelectItem value="12">12</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div>
<p className="text-xs text-gray-500"> <p className="text-xs text-gray-500">
Максимальное количество эталонов для выбора Максимальное количество эталонов для выбора (0-12)
</p> </p>
</div> </div>
@@ -105,16 +98,6 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({
<p className="text-xs text-gray-500"> <p className="text-xs text-gray-500">
Эталоны, которые будут выбраны по умолчанию при создании протокола Эталоны, которые будут выбраны по умолчанию при создании протокола
</p> </p>
<StandardsSelector
isOpen={isStandardsDialogOpen}
onClose={() => setIsStandardsDialogOpen(false)}
value={safeConfig.selectedStandards}
onChange={standardIds => {
updateConfig({ selectedStandards: standardIds })
}}
maxItems={safeConfig.maxItems}
/>
</div> </div>
<div className="rounded-lg bg-blue-50 p-3"> <div className="rounded-lg bg-blue-50 p-3">
@@ -130,6 +113,16 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({
</div> </div>
</div> </div>
</div> </div>
<StandardsSelector
isOpen={isStandardsDialogOpen}
onClose={() => setIsStandardsDialogOpen(false)}
value={safeConfig.selectedStandards}
onChange={standardIds => {
updateConfig({ selectedStandards: standardIds })
}}
maxItems={safeConfig.maxItems}
/>
</div> </div>
) )
} }

View File

@@ -11,9 +11,20 @@ import {
Template, Template,
TemplateElement, TemplateElement,
} from '@/type/template' } from '@/type/template'
import { Plus, Trash2 } from 'lucide-react' import {
AlertTriangle,
Eye,
HelpCircle,
Plus,
Settings2,
Tag,
Target,
Trash2,
Type,
} from 'lucide-react'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Button } from '../ui/button' import { Button } from '../ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
import { Checkbox } from '../ui/checkbox' import { Checkbox } from '../ui/checkbox'
import { import {
Dialog, Dialog,
@@ -23,6 +34,7 @@ import {
DialogTitle, DialogTitle,
} from '../ui/dialog' } from '../ui/dialog'
import { Input } from '../ui/input' import { Input } from '../ui/input'
import { Label } from '../ui/label'
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -30,6 +42,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '../ui/select' } from '../ui/select'
import { Separator } from '../ui/separator'
import { ElementFormulaBar } from './ElementFormulaBar' import { ElementFormulaBar } from './ElementFormulaBar'
import { VisualLayoutEditor } from './VisualLayoutEditor' import { VisualLayoutEditor } from './VisualLayoutEditor'
@@ -79,72 +92,92 @@ const CellTargetEditor: React.FC<{
} }
return ( return (
<div className="space-y-1.5"> <Card>
<CardHeader className="pb-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="text-sm font-medium">Целевые ячейки</label> <div className="flex items-center gap-2">
<Target className="h-4 w-4 text-blue-600" />
<CardTitle className="text-sm">Целевые ячейки</CardTitle>
</div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={handleAddTarget} onClick={handleAddTarget}
className="h-7 px-2 text-xs" className="h-6 px-2 text-xs"
> >
<Plus className="mr-1 h-3 w-3" /> <Plus className="mr-1 h-3 w-3" />
Добавить Добавить
</Button> </Button>
</div> </div>
<div className="max-h-40 space-y-2 overflow-y-auto"> </CardHeader>
<CardContent className="pt-0">
<div className="max-h-48 space-y-2 overflow-y-auto">
{targets.map((target, index) => ( {targets.map((target, index) => (
<div key={index} className="rounded border bg-gray-50/80 p-2"> <div
<div className="mb-1.5 grid grid-cols-2 gap-2"> key={index}
<div> className="flex items-center gap-2 rounded-lg border bg-gray-50/50 p-2"
<label className="text-xs font-medium text-gray-600">
Лист
</label>
<Select
value={target.sheet}
onValueChange={value =>
handleUpdateTarget(index, 'sheet', value)
}
> >
<SelectTrigger className="h-7 text-xs"> {/* Кнопки выбора листа */}
<SelectValue /> <div className="flex gap-1">
</SelectTrigger> <Button
<SelectContent> variant={target.sheet === 'L' ? 'default' : 'outline'}
<SelectItem value="L">L (Левый)</SelectItem> size="sm"
<SelectItem value="R">R (Правый)</SelectItem> onClick={() => handleUpdateTarget(index, 'sheet', 'L')}
</SelectContent> className="h-6 w-7 p-0 text-xs"
</Select> >
L
</Button>
<Button
variant={target.sheet === 'R' ? 'default' : 'outline'}
size="sm"
onClick={() => handleUpdateTarget(index, 'sheet', 'R')}
className="h-6 w-7 p-0 text-xs"
>
R
</Button>
</div> </div>
<div>
<label className="text-xs font-medium text-gray-600"> {/* Поле ввода ячейки */}
Ячейка
</label>
<Input <Input
placeholder="A1, B5..." placeholder="A1"
value={target.cell} value={target.cell}
onChange={e => handleCellInputChange(index, e.target.value)} onChange={e => handleCellInputChange(index, e.target.value)}
className="h-7 text-xs" className="h-6 w-16 px-2 text-xs"
/> />
</div>
</div> {/* Кнопка удаления */}
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => handleRemoveTarget(index)} onClick={() => handleRemoveTarget(index)}
className="h-5 px-1 text-xs text-red-600 hover:bg-red-50 hover:text-red-700" className="ml-auto h-6 w-6 p-0 text-red-600 hover:bg-red-50 hover:text-red-700"
> >
<Trash2 className="mr-1 h-2.5 w-2.5" /> <Trash2 className="h-3 w-3" />
Удалить
</Button> </Button>
</div> </div>
))} ))}
{targets.length === 0 && ( {targets.length === 0 && (
<p className="py-3 text-center text-xs text-gray-500"> <div className="py-4 text-center">
<Target className="mx-auto mb-1 h-6 w-6 text-gray-300" />
<p className="text-xs text-gray-500">
Добавьте ячейки для связи с элементом Добавьте ячейки для связи с элементом
</p> </p>
<p className="mt-1 text-xs text-gray-400">
Например: A1, B5, C10
</p>
</div>
)} )}
</div> </div>
{targets.length > 0 && (
<div className="mt-2 flex items-center gap-1 text-xs text-gray-500">
<HelpCircle className="h-3 w-3" />
Всего ячеек: {targets.length}
</div> </div>
)}
</CardContent>
</Card>
) )
} }
@@ -156,42 +189,40 @@ const ElementPreview: React.FC<{ element: Partial<TemplateElement> }> = ({
? getElementDefinition(element.type) ? getElementDefinition(element.type)
: null : null
if (elementDefinition && elementDefinition.Preview) {
return <elementDefinition.Preview config={element as any} />
}
// Fallback для неизвестных типов элементов
return ( return (
<Card className="h-full">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Eye className="h-4 w-4 text-green-600" />
<CardTitle className="text-sm">Предпросмотр элемента</CardTitle>
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="min-h-[200px] rounded-lg border border-gray-200 bg-white p-4">
{elementDefinition && elementDefinition.Preview ? (
<elementDefinition.Preview config={element as any} />
) : (
<div className="space-y-3"> <div className="space-y-3">
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
<div className="rounded-lg border border-gray-200 bg-white p-4">
{element.label && ( {element.label && (
<label className="mb-2 block text-sm font-medium text-gray-700"> <label className="mb-2 block text-sm font-medium text-gray-700">
{element.label} {element.label}
{element.required && <span className="ml-1 text-red-500">*</span>} {element.required && (
<span className="ml-1 text-red-500">*</span>
)}
</label> </label>
)} )}
<div className="rounded border border-gray-200 bg-gray-50 p-3 text-center text-sm text-gray-500"> <div className="flex items-center justify-center gap-2 rounded border border-gray-200 bg-gray-50 p-3 text-center text-sm text-gray-500">
<AlertTriangle className="h-4 w-4" />
{element.type {element.type
? `Элемент типа "${element.type}" не найден` ? `Элемент типа "${element.type}" не найден`
: 'Выберите тип элемента'} : 'Выберите тип элемента'}
</div> </div>
{element.targetCells && element.targetCells.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{element.targetCells.map((target, index) => (
<span
key={index}
className="inline-block rounded bg-blue-100 px-1.5 py-0.5 text-xs text-blue-700"
>
{target.sheet}!{target.cell}
</span>
))}
</div> </div>
)} )}
</div> </div>
</div> </CardContent>
</Card>
) )
} }
@@ -228,18 +259,26 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
} }
const needsOptions = const needsOptions =
formData.type === 'select' || formData.type === 'select' || formData.type === 'button_group'
formData.type === 'radio' ||
formData.type === 'button_group'
// Если есть определение элемента в реестре, используем его редактор
if (elementDefinition && elementDefinition.Editor) {
return ( return (
<div className="grid grid-cols-2 gap-6"> <div className="grid h-full grid-cols-1 gap-6 lg:grid-cols-2">
{/* Форма настроек */} {/* Настройки элемента */}
<div className="space-y-4"> <div className="max-h-[60vh] space-y-4 overflow-y-auto pr-2">
<div className="space-y-1.5"> {/* Основные настройки */}
<label className="text-sm font-medium">Тип элемента</label> <Card>
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Settings2 className="h-4 w-4 text-blue-600" />
<CardTitle className="text-sm">Основные настройки</CardTitle>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="flex items-center gap-2 text-sm font-medium">
<Type className="h-3 w-3" />
Тип элемента
</Label>
<Select <Select
value={formData.type} value={formData.type}
onValueChange={value => { onValueChange={value => {
@@ -249,11 +288,9 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
setFormData(prev => { setFormData(prev => {
const updates: Partial<TemplateElement> = { const updates: Partial<TemplateElement> = {
type: newType, type: newType,
// Сбрасываем targetCells при смене типа
targetCells: [], targetCells: [],
} }
// Применяем конфиг по умолчанию из определения элемента
if (elementDefinition && elementDefinition.defaultConfig) { if (elementDefinition && elementDefinition.defaultConfig) {
Object.assign(updates, elementDefinition.defaultConfig) Object.assign(updates, elementDefinition.defaultConfig)
} }
@@ -278,9 +315,12 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
</Select> </Select>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div className="space-y-1.5"> <div className="space-y-2">
<label className="text-sm font-medium">Подпись</label> <Label className="flex items-center gap-2 text-sm font-medium">
<Tag className="h-3 w-3" />
Подпись
</Label>
<Input <Input
placeholder="Название поля" placeholder="Название поля"
value={formData.label} value={formData.label}
@@ -291,8 +331,11 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
/> />
</div> </div>
<div className="space-y-1.5"> <div className="space-y-2">
<label className="text-sm font-medium">Подсказка</label> <Label className="flex items-center gap-2 text-sm font-medium">
<HelpCircle className="h-3 w-3" />
Подсказка
</Label>
<Input <Input
placeholder="Введите значение..." placeholder="Введите значение..."
value={formData.placeholder} value={formData.placeholder}
@@ -315,11 +358,14 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
setFormData(prev => ({ ...prev, required: !!checked })) setFormData(prev => ({ ...prev, required: !!checked }))
} }
/> />
<label htmlFor="required" className="text-sm font-medium"> <Label htmlFor="required" className="text-sm font-medium">
Обязательное поле Обязательное поле
</label> </Label>
</div> </div>
</CardContent>
</Card>
{/* Целевые ячейки */}
<CellTargetEditor <CellTargetEditor
targets={formData.targetCells || []} targets={formData.targetCells || []}
onChange={targets => onChange={targets =>
@@ -328,90 +374,36 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
/> />
{/* Специфичный редактор элемента */} {/* Специфичный редактор элемента */}
{elementDefinition && elementDefinition.Editor && (
<Card>
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Settings2 className="h-4 w-4 text-purple-600" />
<CardTitle className="text-sm">
Дополнительные настройки
</CardTitle>
</div>
</CardHeader>
<CardContent>
<elementDefinition.Editor <elementDefinition.Editor
config={formData as any} config={formData as any}
onChange={newConfig => onChange={newConfig =>
setFormData(prev => ({ ...prev, ...newConfig })) setFormData(prev => ({ ...prev, ...newConfig }))
} }
/> />
</div> </CardContent>
</Card>
{/* Live Preview */} )}
<div className="sticky top-0 flex min-h-[50vh] items-center">
<ElementPreview element={formData} />
</div>
</div>
)
}
// Fallback для старых элементов
return (
<div className="grid grid-cols-2 gap-6">
{/* Форма настроек */}
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-sm font-medium">Тип элемента</label>
<Select
value={formData.type}
onValueChange={value => {
const newType = value as ElementType
const elementDefinition = getElementDefinition(newType)
setFormData(prev => {
const updates: Partial<TemplateElement> = {
type: newType,
// Сбрасываем targetCells при смене типа
targetCells: [],
}
// Применяем конфиг по умолчанию из определения элемента
if (elementDefinition && elementDefinition.defaultConfig) {
Object.assign(updates, elementDefinition.defaultConfig)
}
return { ...prev, ...updates }
})
}}
>
<SelectTrigger className="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
{getAvailableElementTypes().map(({ type, label, icon }) => (
<SelectItem key={type} value={type}>
<div className="flex items-center gap-2">
{icon}
{label}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium">Подпись</label>
<Input
placeholder="Название поля"
value={formData.label}
onChange={e =>
setFormData(prev => ({ ...prev, label: e.target.value }))
}
className="h-9"
/>
</div>
<CellTargetEditor
targets={formData.targetCells || []}
onChange={targets =>
setFormData(prev => ({ ...prev, targetCells: targets }))
}
/>
{/* Варианты для select/radio/button_group */}
{needsOptions && ( {needsOptions && (
<div className="space-y-1.5"> <Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="text-sm font-medium">Варианты</label> <div className="flex items-center gap-2">
<Tag className="h-4 w-4 text-orange-600" />
<CardTitle className="text-sm">Варианты выбора</CardTitle>
</div>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@@ -422,16 +414,18 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
Добавить Добавить
</Button> </Button>
</div> </div>
<div className="max-h-28 space-y-1.5 overflow-y-auto"> </CardHeader>
<CardContent>
<div className="max-h-32 space-y-2 overflow-y-auto">
{formData.options?.map((option, index) => ( {formData.options?.map((option, index) => (
<div key={index} className="flex gap-1.5"> <div key={index} className="flex gap-2">
<Input <Input
placeholder="Значение" placeholder="Значение"
value={option.value} value={option.value}
onChange={e => onChange={e =>
handleUpdateOption(index, 'value', e.target.value) handleUpdateOption(index, 'value', e.target.value)
} }
className="h-7 text-xs" className="h-8 text-xs"
/> />
<Input <Input
placeholder="Подпись" placeholder="Подпись"
@@ -439,25 +433,34 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
onChange={e => onChange={e =>
handleUpdateOption(index, 'label', e.target.value) handleUpdateOption(index, 'label', e.target.value)
} }
className="h-7 text-xs" className="h-8 text-xs"
/> />
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => handleRemoveOption(index)} onClick={() => handleRemoveOption(index)}
className="h-7 w-7 text-red-600 hover:bg-red-50 hover:text-red-700" className="h-8 w-8 text-red-600 hover:bg-red-50 hover:text-red-700"
> >
<Trash2 className="h-3 w-3" /> <Trash2 className="h-3 w-3" />
</Button> </Button>
</div> </div>
))} ))}
</div> {(!formData.options || formData.options.length === 0) && (
<div className="py-4 text-center">
<Tag className="mx-auto mb-2 h-6 w-6 text-gray-300" />
<p className="text-xs text-gray-500">
Добавьте варианты выбора
</p>
</div> </div>
)} )}
</div> </div>
</CardContent>
</Card>
)}
</div>
{/* Live Preview */} {/* Предпросмотр */}
<div className="sticky top-0 flex min-h-[50vh] items-center"> <div className="h-fit lg:sticky lg:top-0">
<ElementPreview element={formData} /> <ElementPreview element={formData} />
</div> </div>
</div> </div>
@@ -661,11 +664,6 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
resetForm() resetForm()
} }
// const reordered = (reorderedElements: TemplateElement[]) => {
// onElementsReorder(reorderedElements);
// onLayoutSettingsChange({ ...layoutSettings, showGrid: true });
// };
return ( return (
<div className="flex h-full w-full flex-col bg-background"> <div className="flex h-full w-full flex-col bg-background">
<ElementFormulaBar <ElementFormulaBar
@@ -704,48 +702,91 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
{/* Диалог добавления элемента */} {/* Диалог добавления элемента */}
<Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}> <Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
<DialogContent className="max-h-[80vh] max-w-4xl overflow-y-auto"> <DialogContent className="max-h-[90vh] max-w-6xl overflow-hidden">
<DialogHeader> <DialogHeader className="pb-4">
<DialogTitle>Добавить новый элемент</DialogTitle> <DialogTitle className="flex items-center gap-2 text-lg">
<Plus className="h-5 w-5 text-blue-600" />
Добавить новый элемент
</DialogTitle>
<DialogDescription> <DialogDescription>
Настройте параметры нового элемента формы. Создайте и настройте новый элемент формы. Вы можете увидеть
предпросмотр справа.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex-1 overflow-hidden">
<ElementForm formData={formData} setFormData={setFormData} /> <ElementForm formData={formData} setFormData={setFormData} />
<div className="flex justify-end gap-2 pt-4"> </div>
<Button onClick={() => setIsAddDialogOpen(false)} variant="outline"> <Separator className="my-4" />
<div className="flex items-center justify-between pt-2">
<div className="text-sm text-gray-500">
{!formData.label && (
<div className="flex items-center gap-1 text-amber-600">
<AlertTriangle className="h-3 w-3" />
Заполните обязательные поля
</div>
)}
</div>
<div className="flex gap-3">
<Button
onClick={() => setIsAddDialogOpen(false)}
variant="outline"
>
Отмена Отмена
</Button> </Button>
<Button <Button
onClick={handleAddElement} onClick={handleAddElement}
disabled={disabled || !formData.label} disabled={disabled || !formData.label}
className="min-w-[140px]"
> >
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
Добавить элемент Добавить элемент
</Button> </Button>
</div> </div>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Диалог редактирования элемента */} {/* Диалог редактирования элемента */}
{editingElement && ( {editingElement && (
<Dialog open={!!editingElement} onOpenChange={() => handleCancelEdit()}> <Dialog open={!!editingElement} onOpenChange={() => handleCancelEdit()}>
<DialogContent className="max-h-[80vh] max-w-4xl overflow-y-auto"> <DialogContent className="max-h-[90vh] max-w-6xl overflow-hidden">
<DialogHeader> <DialogHeader className="pb-4">
<DialogTitle>Редактировать элемент</DialogTitle> <DialogTitle className="flex items-center gap-2 text-lg">
<Settings2 className="h-5 w-5 text-green-600" />
Редактировать элемент
</DialogTitle>
<DialogDescription> <DialogDescription>
Измените параметры элемента формы. Измените параметры элемента "
{editingElement.label || editingElement.name}".
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex-1 overflow-hidden">
<ElementForm formData={formData} setFormData={setFormData} /> <ElementForm formData={formData} setFormData={setFormData} />
<div className="flex justify-end gap-2 pt-4"> </div>
<Separator className="my-4" />
<div className="flex items-center justify-between pt-2">
<div className="text-sm text-gray-500">
{!formData.label && (
<div className="flex items-center gap-1 text-amber-600">
<AlertTriangle className="h-3 w-3" />
Заполните обязательные поля
</div>
)}
</div>
<div className="flex gap-3">
<Button onClick={handleCancelEdit} variant="outline"> <Button onClick={handleCancelEdit} variant="outline">
Отмена Отмена
</Button> </Button>
<Button onClick={handleUpdateElement} disabled={disabled}> <Button
onClick={handleUpdateElement}
disabled={disabled || !formData.label}
className="min-w-[160px]"
>
<Settings2 className="mr-2 h-4 w-4" />
Сохранить изменения Сохранить изменения
</Button> </Button>
</div> </div>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
)} )}

View File

@@ -150,14 +150,16 @@ const DraggableElement: React.FC<DraggableElementProps> = React.memo(
> >
{/* Controls (only visible when selected or hovered) */} {/* Controls (only visible when selected or hovered) */}
<div <div
className={`absolute right-1 top-1 flex items-center gap-1 ${ className={`absolute right-1 top-1 flex items-center gap-0.5 ${
isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-60' isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-60'
}`} }`}
> >
{/* Индикатор ячеек */} {/* Индикатор ячеек */}
{element.targetCells && element.targetCells.length > 0 && ( {element.targetCells && element.targetCells.length > 0 && (
<div className="group/cells relative"> <div className="group/cells relative">
<Grid className="h-3 w-3 cursor-help text-gray-500 opacity-60 transition-opacity hover:opacity-100" /> <div className="flex !h-5 !w-5 cursor-help items-center justify-center rounded hover:bg-gray-100">
<Grid className="!h-3 !w-3 text-gray-500" />
</div>
<div className="pointer-events-none absolute right-0 top-full z-50 mt-2 whitespace-nowrap rounded bg-gray-900 p-2 text-xs text-white opacity-0 shadow-lg transition-opacity group-hover/cells:opacity-100"> <div className="pointer-events-none absolute right-0 top-full z-50 mt-2 whitespace-nowrap rounded bg-gray-900 p-2 text-xs text-white opacity-0 shadow-lg transition-opacity group-hover/cells:opacity-100">
<div className="mb-1 font-medium">Целевые ячейки:</div> <div className="mb-1 font-medium">Целевые ячейки:</div>
{element.targetCells.map((cell, i) => ( {element.targetCells.map((cell, i) => (
@@ -177,19 +179,19 @@ const DraggableElement: React.FC<DraggableElementProps> = React.memo(
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-4 w-4 cursor-pointer hover:bg-blue-100" className="!h-5 !w-5 !min-w-0 cursor-pointer !p-0 hover:bg-blue-100"
onClick={handleEditClick} onClick={handleEditClick}
> >
<Edit className="h-2.5 w-2.5 text-blue-600" /> <Edit className="!h-3 !w-3 text-blue-600" />
</Button> </Button>
)} )}
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-4 w-4 cursor-pointer hover:bg-red-100" className="!h-5 !w-5 !min-w-0 cursor-pointer !p-0 hover:bg-red-100"
onClick={handleDeleteClick} onClick={handleDeleteClick}
> >
<Trash2 className="h-2.5 w-2.5 text-red-600" /> <Trash2 className="!h-3 !w-3 text-red-600" />
</Button> </Button>
</div> </div>
@@ -286,7 +288,7 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
onClick={handleCanvasClick} onClick={handleCanvasClick}
> >
<div <div
className="relative mx-auto my-8 select-none bg-white shadow-lg" className="relative mx-auto my-8 select-none border border-gray-300 bg-white"
style={{ width: canvasSize.width, height: canvasSize.height }} style={{ width: canvasSize.width, height: canvasSize.height }}
onClick={handleCanvasClick} onClick={handleCanvasClick}
> >