Add multi-select mode to BeoSelector and ObjektSelector
CustomSelect gains keepOpen prop: dropdown stays open after each selection, closes via Fertig button or tap outside. ObjektSelector separates Neu into its own button to keep the dropdown flow clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,9 +54,10 @@ export default function BeoSelector({ selected, onChange }: Props) {
|
|||||||
|
|
||||||
{available.length > 0 && (
|
{available.length > 0 && (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
placeholder="+ BEO hinzufügen"
|
placeholder="+ BEOs hinzufügen"
|
||||||
options={available.map((b) => ({ value: String(b.ID), label: `${b.Kuerzel} — ${b.Name}` }))}
|
options={available.map((b) => ({ value: String(b.ID), label: `${b.Kuerzel} — ${b.Name}` }))}
|
||||||
onChange={add}
|
onChange={add}
|
||||||
|
keepOpen
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ interface Props {
|
|||||||
options: SelectOption[];
|
options: SelectOption[];
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
keepOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CustomSelect({ options, placeholder, onChange }: Props) {
|
export default function CustomSelect({ options, placeholder, onChange, keepOpen = false }: Props) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ export default function CustomSelect({ options, placeholder, onChange }: Props)
|
|||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
function select(value: string) {
|
function select(value: string) {
|
||||||
setOpen(false);
|
if (!keepOpen) setOpen(false);
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +63,15 @@ export default function CustomSelect({ options, placeholder, onChange }: Props)
|
|||||||
{opt.label}
|
{opt.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
{keepOpen && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className="w-full px-4 py-3 text-base font-medium text-center bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-b-lg border-t-2 border-gray-300"
|
||||||
|
>
|
||||||
|
Fertig
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
|||||||
const available = all.filter((o) => !selectedNames.has(o.Name.toLowerCase()));
|
const available = all.filter((o) => !selectedNames.has(o.Name.toLowerCase()));
|
||||||
|
|
||||||
function add(value: string) {
|
function add(value: string) {
|
||||||
if (value === 'neu') {
|
|
||||||
setShowNewInput(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const obj = all.find((o) => o.ID === parseInt(value));
|
const obj = all.find((o) => o.ID === parseInt(value));
|
||||||
if (obj && !selectedNames.has(obj.Name.toLowerCase())) {
|
if (obj && !selectedNames.has(obj.Name.toLowerCase())) {
|
||||||
onChange([...selected, { ID: obj.ID, Name: obj.Name }]);
|
onChange([...selected, { ID: obj.ID, Name: obj.Name }]);
|
||||||
@@ -47,11 +43,6 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
|||||||
onChange(selected.filter((o) => o.Name !== name));
|
onChange(selected.filter((o) => o.Name !== name));
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = [
|
|
||||||
{ value: 'neu', label: '— Neues Objekt eingeben —' },
|
|
||||||
...available.map((o) => ({ value: String(o.ID), label: o.Name })),
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
@@ -73,11 +64,25 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{available.length > 0 && (
|
||||||
|
<div className="flex-1">
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
placeholder="+ Objekt hinzufügen"
|
placeholder="+ Objekte hinzufügen"
|
||||||
options={options}
|
options={available.map((o) => ({ value: String(o.ID), label: o.Name }))}
|
||||||
onChange={add}
|
onChange={add}
|
||||||
|
keepOpen
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowNewInput((v) => !v)}
|
||||||
|
className="px-4 py-3 border-2 border-gray-400 rounded-lg bg-white text-base text-gray-700 hover:bg-gray-50 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
+ Neu
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{showNewInput && (
|
{showNewInput && (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -88,6 +93,7 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
|||||||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNew(); } }}
|
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNew(); } }}
|
||||||
placeholder="Objektname eingeben"
|
placeholder="Objektname eingeben"
|
||||||
className="flex-1 px-3 py-3 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none"
|
className="flex-1 px-3 py-3 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none"
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -101,7 +107,7 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
|||||||
onClick={() => { setShowNewInput(false); setNewName(''); }}
|
onClick={() => { setShowNewInput(false); setNewName(''); }}
|
||||||
className="px-4 py-3 bg-gray-200 text-gray-700 text-base rounded-lg hover:bg-gray-300"
|
className="px-4 py-3 bg-gray-200 text-gray-700 text-base rounded-lg hover:bg-gray-300"
|
||||||
>
|
>
|
||||||
Abbrechen
|
✕
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user