diff --git a/frontends/nextjs/src/components/managers/DropdownConfigManager.tsx b/frontends/nextjs/src/components/managers/DropdownConfigManager.tsx index 254d0132e..dbd2edc00 100644 --- a/frontends/nextjs/src/components/managers/DropdownConfigManager.tsx +++ b/frontends/nextjs/src/components/managers/DropdownConfigManager.tsx @@ -1,26 +1,16 @@ -import { useState, useEffect } from 'react' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui' -import { Button } from '@/components/ui' -import { Input } from '@/components/ui' -import { Label } from '@/components/ui' -import { ScrollArea } from '@/components/ui' -import { Card } from '@/components/ui' -import { Badge } from '@/components/ui' -import { Separator } from '@/components/ui' +import { useEffect, useState } from 'react' +import { Button, Card } from '@/components/ui' import { Database } from '@/lib/database' -import { Plus, X, FloppyDisk, Trash, Pencil } from '@phosphor-icons/react' +import { Plus } from '@phosphor-icons/react' import { toast } from 'sonner' import type { DropdownConfig } from '@/lib/database' +import { DropdownConfigForm } from './dropdown/DropdownConfigForm' +import { PreviewPane } from './dropdown/PreviewPane' export function DropdownConfigManager() { const [dropdowns, setDropdowns] = useState([]) const [isEditing, setIsEditing] = useState(false) const [editingDropdown, setEditingDropdown] = useState(null) - const [dropdownName, setDropdownName] = useState('') - const [dropdownLabel, setDropdownLabel] = useState('') - const [options, setOptions] = useState>([]) - const [newOptionValue, setNewOptionValue] = useState('') - const [newOptionLabel, setNewOptionLabel] = useState('') useEffect(() => { loadDropdowns() @@ -31,63 +21,34 @@ export function DropdownConfigManager() { setDropdowns(configs) } - const startEdit = (dropdown?: DropdownConfig) => { - if (dropdown) { - setEditingDropdown(dropdown) - setDropdownName(dropdown.name) - setDropdownLabel(dropdown.label) - setOptions(dropdown.options) - } else { - setEditingDropdown(null) - setDropdownName('') - setDropdownLabel('') - setOptions([]) - } + const openEditor = (dropdown?: DropdownConfig) => { + setEditingDropdown(dropdown ?? null) setIsEditing(true) } - const addOption = () => { - if (newOptionValue && newOptionLabel) { - setOptions(current => [...current, { value: newOptionValue, label: newOptionLabel }]) - setNewOptionValue('') - setNewOptionLabel('') - } - } - - const removeOption = (index: number) => { - setOptions(current => current.filter((_, i) => i !== index)) - } - - const handleSave = async () => { - if (!dropdownName || !dropdownLabel || options.length === 0) { - toast.error('Please fill all fields and add at least one option') - return - } - - const newDropdown: DropdownConfig = { - id: editingDropdown?.id || `dropdown_${Date.now()}`, - name: dropdownName, - label: dropdownLabel, - options, - } - - if (editingDropdown) { - await Database.updateDropdownConfig(newDropdown.id, newDropdown) + const handleSave = async (config: DropdownConfig, isEdit: boolean) => { + if (isEdit) { + await Database.updateDropdownConfig(config.id, config) toast.success('Dropdown updated successfully') } else { - await Database.addDropdownConfig(newDropdown) + await Database.addDropdownConfig(config) toast.success('Dropdown created successfully') } setIsEditing(false) - loadDropdowns() + await loadDropdowns() } const handleDelete = async (id: string) => { - if (confirm('Are you sure you want to delete this dropdown configuration?')) { - await Database.deleteDropdownConfig(id) - toast.success('Dropdown deleted') - loadDropdowns() + await Database.deleteDropdownConfig(id) + toast.success('Dropdown deleted') + await loadDropdowns() + } + + const handleDialogChange = (open: boolean) => { + setIsEditing(open) + if (!open) { + setEditingDropdown(null) } } @@ -98,7 +59,7 @@ export function DropdownConfigManager() {

Dropdown Configurations

Manage dynamic dropdown options for properties

- @@ -106,30 +67,12 @@ export function DropdownConfigManager() {
{dropdowns.map(dropdown => ( - -
-
-

{dropdown.label}

-

{dropdown.name}

-
-
- - -
-
- -
- {dropdown.options.map((opt, i) => ( - - {opt.label} - - ))} -
-
+ ))}
@@ -139,88 +82,12 @@ export function DropdownConfigManager() { )} - - - - {editingDropdown ? 'Edit' : 'Create'} Dropdown Configuration - - -
-
- - setDropdownName(e.target.value)} - /> -

Unique identifier for this dropdown

-
- -
- - setDropdownLabel(e.target.value)} - /> -
- - - -
- -
- setNewOptionValue(e.target.value)} - /> - setNewOptionLabel(e.target.value)} - /> - -
-
- - {options.length > 0 && ( - -
- {options.map((opt, i) => ( -
-
- {opt.value} - - {opt.label} -
- -
- ))} -
-
- )} -
- - - - - -
-
+ ) } diff --git a/frontends/nextjs/src/components/managers/dropdown/DropdownConfigForm.tsx b/frontends/nextjs/src/components/managers/dropdown/DropdownConfigForm.tsx new file mode 100644 index 000000000..754d4cf73 --- /dev/null +++ b/frontends/nextjs/src/components/managers/dropdown/DropdownConfigForm.tsx @@ -0,0 +1,182 @@ +import { useEffect, useMemo, useState } from 'react' +import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, ScrollArea, Separator } from '@/components/ui' +import { FloppyDisk, Plus, X } from '@phosphor-icons/react' +import { toast } from 'sonner' +import type { DropdownConfig } from '@/lib/database' + +interface DropdownConfigFormProps { + open: boolean + editingDropdown: DropdownConfig | null + onOpenChange: (open: boolean) => void + onSave: (config: DropdownConfig, isEdit: boolean) => Promise | void +} + +const getDefaultOptions = (dropdown?: DropdownConfig | null) => dropdown?.options ?? [] + +const buildDropdownConfig = ( + dropdown: DropdownConfig | null, + name: string, + label: string, + options: Array<{ value: string; label: string }> +): DropdownConfig => ({ + id: dropdown?.id ?? `dropdown_${Date.now()}`, + name: name.trim(), + label: label.trim(), + options, +}) + +export function DropdownConfigForm({ open, editingDropdown, onOpenChange, onSave }: DropdownConfigFormProps) { + const [dropdownName, setDropdownName] = useState('') + const [dropdownLabel, setDropdownLabel] = useState('') + const [options, setOptions] = useState>([]) + const [newOptionValue, setNewOptionValue] = useState('') + const [newOptionLabel, setNewOptionLabel] = useState('') + + const isEditMode = useMemo(() => Boolean(editingDropdown), [editingDropdown]) + + useEffect(() => { + if (open) { + setDropdownName(editingDropdown?.name ?? '') + setDropdownLabel(editingDropdown?.label ?? '') + setOptions(getDefaultOptions(editingDropdown)) + } else { + setDropdownName('') + setDropdownLabel('') + setOptions([]) + setNewOptionValue('') + setNewOptionLabel('') + } + }, [open, editingDropdown]) + + const addOption = () => { + if (!newOptionValue.trim() || !newOptionLabel.trim()) { + toast.error('Please provide both a value and label for the option') + return + } + + const duplicate = options.some( + (opt) => opt.value.toLowerCase() === newOptionValue.trim().toLowerCase() + ) + + if (duplicate) { + toast.error('An option with this value already exists') + return + } + + setOptions((current) => [ + ...current, + { value: newOptionValue.trim(), label: newOptionLabel.trim() }, + ]) + setNewOptionValue('') + setNewOptionLabel('') + } + + const removeOption = (index: number) => { + setOptions((current) => current.filter((_, i) => i !== index)) + } + + const handleSave = async () => { + if (!dropdownName.trim() || !dropdownLabel.trim() || options.length === 0) { + toast.error('Please fill all fields and add at least one option') + return + } + + const config = buildDropdownConfig(editingDropdown, dropdownName, dropdownLabel, options) + await onSave(config, isEditMode) + onOpenChange(false) + } + + return ( + + + + {isEditMode ? 'Edit' : 'Create'} Dropdown Configuration + + +
+
+ + setDropdownName(e.target.value)} + /> +

Unique identifier for this dropdown

+
+ +
+ + setDropdownLabel(e.target.value)} + /> +
+ + + +
+ +
+ setNewOptionValue(e.target.value)} + /> + setNewOptionLabel(e.target.value)} + /> + +
+
+ + {options.length > 0 && ( + +
+ {options.map((opt, i) => ( +
+
+ {opt.value} + + {opt.label} +
+ +
+ ))} +
+
+ )} +
+ + {options.length === 0 && ( +
+ Tip + Add at least one option to save this dropdown configuration. +
+ )} + + + + + +
+
+ ) +} diff --git a/frontends/nextjs/src/components/managers/dropdown/PreviewPane.tsx b/frontends/nextjs/src/components/managers/dropdown/PreviewPane.tsx new file mode 100644 index 000000000..bbc144249 --- /dev/null +++ b/frontends/nextjs/src/components/managers/dropdown/PreviewPane.tsx @@ -0,0 +1,44 @@ +import { Badge, Button, Card, Separator } from '@/components/ui' +import { Pencil, Trash } from '@phosphor-icons/react' +import type { DropdownConfig } from '@/lib/database' + +interface PreviewPaneProps { + dropdown: DropdownConfig + onEdit: (dropdown: DropdownConfig) => void + onDelete: (id: string) => void +} + +export function PreviewPane({ dropdown, onEdit, onDelete }: PreviewPaneProps) { + const handleDelete = () => { + if (confirm('Are you sure you want to delete this dropdown configuration?')) { + onDelete(dropdown.id) + } + } + + return ( + +
+
+

{dropdown.label}

+

{dropdown.name}

+
+
+ + +
+
+ +
+ {dropdown.options.map((opt, i) => ( + + {opt.label} + + ))} +
+
+ ) +}