Files
logbuch/components/ObjektSelector.tsx
Reinhard X. Fürst 911b041136 Replace native selects with custom dropdown for mobile usability
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>
2026-04-27 18:13:06 +02:00

111 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
}