дублирование в списке и отключить копирование при открытом диалговом окне

This commit is contained in:
2025-07-25 15:54:05 +03:00
parent 34c1cf6e71
commit b47ec33655
3 changed files with 93 additions and 39 deletions

View File

@@ -9,6 +9,7 @@ import {
SelectValue, SelectValue,
} from '@/component/ui/select' } from '@/component/ui/select'
import { ElementDefinition } from '@/entitiy/element/model/interface' import { ElementDefinition } from '@/entitiy/element/model/interface'
import { useToast } from '@/lib/hooks/useToast'
import { ElementOption } from '@/type/template' import { ElementOption } from '@/type/template'
import { ChevronDown, Plus, Trash2 } from 'lucide-react' import { ChevronDown, Plus, Trash2 } from 'lucide-react'
@@ -36,6 +37,8 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
return cells.map(target => ({ target, value: value || '' })) return cells.map(target => ({ target, value: value || '' }))
}, },
Editor: ({ config, onChange }) => { Editor: ({ config, onChange }) => {
const { warning } = useToast()
const handleAddOption = () => { const handleAddOption = () => {
onChange({ onChange({
...config, ...config,
@@ -48,6 +51,19 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
field: 'value' | 'label', field: 'value' | 'label',
value: string value: string
) => { ) => {
// Проверяем дублирование только для подписей (label)
if (field === 'label' && value.trim() !== '') {
const isDuplicate = config.options.some(
(option, i) =>
i !== index &&
option.label.trim().toLowerCase() === value.trim().toLowerCase()
)
if (isDuplicate) {
warning(`Такая подпись уже существует: "${value.trim()}"`)
return
}
}
onChange({ onChange({
...config, ...config,
options: config.options.map((option, i) => options: config.options.map((option, i) =>
@@ -74,31 +90,59 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
</Button> </Button>
</div> </div>
<div className="max-h-32 space-y-2 overflow-y-auto"> <div className="max-h-32 space-y-2 overflow-y-auto">
{config.options.map((option, index) => ( {config.options.map((option, index) => {
<div key={index} className="flex gap-2"> const isDuplicateLabel = config.options.some(
<Input (otherOption, i) =>
placeholder="Значение" i !== index &&
value={option.value} option.label.trim() !== '' &&
onChange={e => otherOption.label.trim().toLowerCase() ===
handleUpdateOption(index, 'value', e.target.value) option.label.trim().toLowerCase()
} )
/> // Убираем проверку дублирования для значений
<Input const isDuplicateValue = false
placeholder="Подпись"
value={option.label} return (
onChange={e => <div key={index} className="flex gap-2">
handleUpdateOption(index, 'label', e.target.value) <div className="flex-1">
} <Input
/> placeholder="Значение"
<Button value={option.value}
variant="outline" onChange={e =>
size="icon" handleUpdateOption(index, 'value', e.target.value)
onClick={() => handleRemoveOption(index)} }
> className={isDuplicateValue ? 'border-red-500' : ''}
<Trash2 className="h-4 w-4" /> />
</Button> {isDuplicateValue && (
</div> <p className="mt-1 text-xs text-red-500">
))} Это значение уже используется
</p>
)}
</div>
<div className="flex-1">
<Input
placeholder="Подпись"
value={option.label}
onChange={e =>
handleUpdateOption(index, 'label', e.target.value)
}
className={isDuplicateLabel ? 'border-red-500' : ''}
/>
{isDuplicateLabel && (
<p className="mt-1 text-xs text-red-500">
Такая подпись уже существует
</p>
)}
</div>
<Button
variant="outline"
size="icon"
onClick={() => handleRemoveOption(index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)
})}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -621,6 +621,7 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
onLayoutSettingsChange={onLayoutSettingsChange || (() => {})} onLayoutSettingsChange={onLayoutSettingsChange || (() => {})}
onElementEdit={handleEditElement} onElementEdit={handleEditElement}
onElementAdd={handleElementCopy} onElementAdd={handleElementCopy}
isDialogOpen={isAddDialogOpen || !!editingElement}
/> />
</div> </div>

View File

@@ -25,19 +25,6 @@ const ELEMENT_MIN_SIZE: Record<string, { width: number; height: number }> = {
button_group: { width: 180, height: 80 }, button_group: { width: 180, height: 80 },
} }
interface VisualLayoutEditorProps {
elements: TemplateElement[]
layoutSettings: FormLayoutSettings
onElementUpdate: (
elementId: string,
updates: Partial<TemplateElement>
) => void
onElementDelete: (elementId: string) => void
onLayoutSettingsChange: (settings: FormLayoutSettings) => void
onElementEdit?: (element: TemplateElement) => void
onElementAdd?: (element: Omit<TemplateElement, 'id'>) => void
}
interface DraggableElementProps { interface DraggableElementProps {
element: TemplateElement element: TemplateElement
isSelected: boolean isSelected: boolean
@@ -213,6 +200,20 @@ const DraggableElement: React.FC<DraggableElementProps> = React.memo(
} }
) )
interface VisualLayoutEditorProps {
elements: TemplateElement[]
layoutSettings: FormLayoutSettings
onElementUpdate: (
elementId: string,
updates: Partial<TemplateElement>
) => void
onElementDelete: (elementId: string) => void
onLayoutSettingsChange: (settings: FormLayoutSettings) => void
onElementEdit?: (element: TemplateElement) => void
onElementAdd?: (element: Omit<TemplateElement, 'id'>) => void
isDialogOpen?: boolean // Добавляем новый проп
}
export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
elements, elements,
layoutSettings, layoutSettings,
@@ -221,6 +222,7 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
onLayoutSettingsChange, onLayoutSettingsChange,
onElementEdit, onElementEdit,
onElementAdd, onElementAdd,
isDialogOpen = false, // Значение по умолчанию
}) => { }) => {
// console.log('VisualLayoutEditor render with elements:', elements) // console.log('VisualLayoutEditor render with elements:', elements)
const [selectedElementId, setSelectedElementId] = useState<string | null>( const [selectedElementId, setSelectedElementId] = useState<string | null>(
@@ -232,6 +234,11 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
// Обработчик клавиш для копирования/вставки // Обработчик клавиш для копирования/вставки
useEffect(() => { useEffect(() => {
// Отключаем обработчики если открыт диалог
if (isDialogOpen) {
return
}
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
if (e.key === 'c' && selectedElementId) { if (e.key === 'c' && selectedElementId) {
@@ -248,6 +255,8 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
...copiedElement.layout, ...copiedElement.layout,
x: (copiedElement.layout?.x || 0) + 20, x: (copiedElement.layout?.x || 0) + 20,
y: (copiedElement.layout?.y || 0) + 20, y: (copiedElement.layout?.y || 0) + 20,
width: copiedElement.layout?.width || 300,
height: copiedElement.layout?.height || 80,
}, },
} }
delete (newElement as any).id // Удаляем id чтобы создался новый delete (newElement as any).id // Удаляем id чтобы создался новый
@@ -258,7 +267,7 @@ export const VisualLayoutEditor: React.FC<VisualLayoutEditorProps> = ({
document.addEventListener('keydown', handleKeyDown) document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown)
}, [selectedElementId, elements, copiedElement, onElementAdd]) }, [selectedElementId, elements, copiedElement, onElementAdd, isDialogOpen])
const canvasSize = useMemo(() => { const canvasSize = useMemo(() => {
const PADDING_Y = 200 const PADDING_Y = 200