Native <select> popups ignore CSS on iOS/Android. CustomSelect renders a styled div-based dropdown with full-width touch-friendly option buttons (py-3, text-base). Used in BeoSelector and ObjektSelector. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useState } from 'react';
|
||
import type { ObjektOption, SelectedObjekt } from '@/types/logbuch';
|
||
import CustomSelect from './CustomSelect';
|
||
|
||
interface Props {
|
||
selected: SelectedObjekt[];
|
||
onChange: (objekte: SelectedObjekt[]) => void;
|
||
}
|
||
|
||
export default function ObjektSelector({ selected, onChange }: Props) {
|
||
const [all, setAll] = useState<ObjektOption[]>([]);
|
||
const [newName, setNewName] = useState('');
|
||
const [showNewInput, setShowNewInput] = useState(false);
|
||
|
||
useEffect(() => {
|
||
fetch('/api/objekte')
|
||
.then((r) => { if (!r.ok) throw new Error('Fehler'); return r.json(); })
|
||
.then(setAll)
|
||
.catch(() => {});
|
||
}, []);
|
||
|
||
const selectedNames = new Set(selected.map((o) => o.Name.toLowerCase()));
|
||
const available = all.filter((o) => !selectedNames.has(o.Name.toLowerCase()));
|
||
|
||
function add(value: string) {
|
||
if (value === 'neu') {
|
||
setShowNewInput(true);
|
||
return;
|
||
}
|
||
const obj = all.find((o) => o.ID === parseInt(value));
|
||
if (obj && !selectedNames.has(obj.Name.toLowerCase())) {
|
||
onChange([...selected, { ID: obj.ID, Name: obj.Name }]);
|
||
}
|
||
}
|
||
|
||
function addNew() {
|
||
const name = newName.trim();
|
||
if (!name || selectedNames.has(name.toLowerCase())) return;
|
||
onChange([...selected, { ID: null, Name: name }]);
|
||
setNewName('');
|
||
setShowNewInput(false);
|
||
}
|
||
|
||
function remove(name: string) {
|
||
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 (
|
||
<div className="space-y-3">
|
||
<div className="flex flex-wrap gap-2">
|
||
{selected.map((o) => (
|
||
<span
|
||
key={o.Name}
|
||
className="inline-flex items-center gap-2 bg-green-100 text-green-800 text-base px-3 py-2 rounded-full"
|
||
>
|
||
{o.Name}
|
||
<button
|
||
type="button"
|
||
onClick={() => remove(o.Name)}
|
||
className="flex items-center justify-center w-7 h-7 rounded-full text-green-600 hover:bg-red-100 hover:text-red-600 font-bold text-xl leading-none"
|
||
aria-label={`${o.Name} entfernen`}
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
|
||
<CustomSelect
|
||
placeholder="+ Objekt hinzufügen"
|
||
options={options}
|
||
onChange={add}
|
||
/>
|
||
|
||
{showNewInput && (
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
value={newName}
|
||
onChange={(e) => setNewName(e.target.value)}
|
||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNew(); } }}
|
||
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"
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={addNew}
|
||
className="px-4 py-3 bg-green-600 text-white text-base rounded-lg hover:bg-green-700"
|
||
>
|
||
OK
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => { setShowNewInput(false); setNewName(''); }}
|
||
className="px-4 py-3 bg-gray-200 text-gray-700 text-base rounded-lg hover:bg-gray-300"
|
||
>
|
||
Abbrechen
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|