204 lines
6.6 KiB
TypeScript
204 lines
6.6 KiB
TypeScript
import { Button } from '@/component/ui/button'
|
||
import { Input } from '@/component/ui/input'
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectGroup,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from '@/component/ui/select'
|
||
import { ElementDefinition } from '@/entitiy/element/model/interface'
|
||
import { useToast } from '@/lib/hooks/useToast'
|
||
import { ElementOption } from '@/type/template'
|
||
import { ChevronDown, Plus, Trash2 } from 'lucide-react'
|
||
|
||
interface SelectConfig {
|
||
placeholder?: string
|
||
required?: boolean
|
||
options: ElementOption[]
|
||
targetCells: any[]
|
||
}
|
||
|
||
export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
||
type: 'select',
|
||
label: 'Выпадающий список',
|
||
icon: <ChevronDown className="h-4 w-4" />,
|
||
version: 1,
|
||
defaultConfig: {
|
||
placeholder: '',
|
||
required: false,
|
||
options: [],
|
||
targetCells: [],
|
||
},
|
||
mapToCells: config => config.targetCells || [],
|
||
mapToCellValues: (config, value) => {
|
||
const cells = config.targetCells || []
|
||
return cells.map(target => ({ target, value: value || '' }))
|
||
},
|
||
Editor: ({ config, onChange }) => {
|
||
const { warning } = useToast()
|
||
|
||
const handleAddOption = () => {
|
||
onChange({
|
||
...config,
|
||
options: [...config.options, { value: '', label: '' }],
|
||
})
|
||
}
|
||
|
||
const handleUpdateOption = (
|
||
index: number,
|
||
field: 'value' | 'label',
|
||
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({
|
||
...config,
|
||
options: config.options.map((option, i) =>
|
||
i === index ? { ...option, [field]: value } : option
|
||
),
|
||
})
|
||
}
|
||
|
||
const handleRemoveOption = (index: number) => {
|
||
onChange({
|
||
...config,
|
||
options: config.options.filter((_, i) => i !== index),
|
||
})
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-sm font-medium">Варианты</label>
|
||
<Button variant="outline" size="sm" onClick={handleAddOption}>
|
||
<Plus className="mr-1 h-4 w-4" />
|
||
Добавить
|
||
</Button>
|
||
</div>
|
||
<div className="max-h-32 space-y-2 overflow-y-auto">
|
||
{config.options.map((option, index) => {
|
||
const isDuplicateLabel = config.options.some(
|
||
(otherOption, i) =>
|
||
i !== index &&
|
||
option.label.trim() !== '' &&
|
||
otherOption.label.trim().toLowerCase() ===
|
||
option.label.trim().toLowerCase()
|
||
)
|
||
// Убираем проверку дублирования для значений
|
||
const isDuplicateValue = false
|
||
|
||
return (
|
||
<div key={index} className="flex gap-2">
|
||
<div className="flex-1">
|
||
<Input
|
||
placeholder="Значение"
|
||
value={option.value}
|
||
onChange={e =>
|
||
handleUpdateOption(index, 'value', e.target.value)
|
||
}
|
||
className={isDuplicateValue ? 'border-red-500' : ''}
|
||
/>
|
||
{isDuplicateValue && (
|
||
<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>
|
||
)
|
||
},
|
||
Preview: ({ config }) => (
|
||
<Select>
|
||
<SelectTrigger className="w-full min-w-32">
|
||
<SelectValue placeholder={config.placeholder || 'Выберите значение'} />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectGroup>
|
||
{config.options
|
||
.filter(option => option.value.trim() !== '')
|
||
.map((option, index) => (
|
||
<SelectItem key={index} value={option.value}>
|
||
{option.label}
|
||
</SelectItem>
|
||
))}
|
||
</SelectGroup>
|
||
</SelectContent>
|
||
</Select>
|
||
),
|
||
Render: ({ config, value, onChange }) => {
|
||
const elementId = (config as any).id || config.placeholder || 'default'
|
||
|
||
const storageKey = `select_${elementId}`
|
||
|
||
const handleValueChange = (newValue: string) => {
|
||
localStorage.setItem(storageKey, newValue)
|
||
onChange?.(newValue)
|
||
}
|
||
|
||
// Если значение не установлено, пытаемся загрузить из localStorage
|
||
const currentValue = value || localStorage.getItem(storageKey) || ''
|
||
|
||
return (
|
||
<Select value={currentValue} onValueChange={handleValueChange}>
|
||
<SelectTrigger className="w-full min-w-32">
|
||
<SelectValue
|
||
placeholder={config.placeholder || 'Выберите значение'}
|
||
/>
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectGroup>
|
||
{config.options
|
||
.filter(option => option.value.trim() !== '')
|
||
.map((option, index) => (
|
||
<SelectItem key={index} value={option.value}>
|
||
{option.label}
|
||
</SelectItem>
|
||
))}
|
||
</SelectGroup>
|
||
</SelectContent>
|
||
</Select>
|
||
)
|
||
},
|
||
}
|