+
{(['eingabe', 'liste'] as const).map((tab) => (
@@ -342,7 +363,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
{saving ? 'Speichern...' : editEntry ? 'Änderungen speichern' : 'Eintrag speichern'}
@@ -350,7 +371,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
Abbrechen
diff --git a/components/LogbuchList.tsx b/components/LogbuchList.tsx
index 44f66c0..6e19ebb 100644
--- a/components/LogbuchList.tsx
+++ b/components/LogbuchList.tsx
@@ -105,7 +105,10 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 20, co
{formatTime(e.Ende)}
)}
-
{e.ArtFuehrung} |
+
+ {e.ArtFuehrung}
+ {e.SonderName && {e.SonderName} }
+ |
{e.Besucher || ''} |
{e.BEOs || '—'} |
{e.Objekte || '—'} |
diff --git a/components/ObjektSelector.tsx b/components/ObjektSelector.tsx
index 15637d0..af2fe2e 100644
--- a/components/ObjektSelector.tsx
+++ b/components/ObjektSelector.tsx
@@ -1,8 +1,7 @@
'use client';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import type { ObjektOption, SelectedObjekt } from '@/types/logbuch';
-import CustomSelect from './CustomSelect';
interface Props {
selected: SelectedObjekt[];
@@ -11,8 +10,11 @@ interface Props {
export default function ObjektSelector({ selected, onChange }: Props) {
const [all, setAll] = useState
([]);
+ const [search, setSearch] = useState('');
+ const [dropdownOpen, setDropdownOpen] = useState(false);
const [newName, setNewName] = useState('');
const [showNewInput, setShowNewInput] = useState(false);
+ const wrapperRef = useRef(null);
useEffect(() => {
fetch('/api/objekte')
@@ -21,14 +23,26 @@ export default function ObjektSelector({ selected, onChange }: Props) {
.catch(() => {});
}, []);
+ useEffect(() => {
+ function handleOutside(e: MouseEvent) {
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
+ setDropdownOpen(false);
+ }
+ }
+ if (dropdownOpen) document.addEventListener('mousedown', handleOutside);
+ return () => document.removeEventListener('mousedown', handleOutside);
+ }, [dropdownOpen]);
+
const selectedNames = new Set(selected.map((o) => o.Name.toLowerCase()));
const available = all.filter((o) => !selectedNames.has(o.Name.toLowerCase()));
+ const filtered = search
+ ? available.filter((o) => o.Name.toLowerCase().startsWith(search.toLowerCase()))
+ : available;
- function add(value: string) {
- 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 add(obj: ObjektOption) {
+ onChange([...selected, { ID: obj.ID, Name: obj.Name }]);
+ setSearch('');
+ setDropdownOpen(false);
}
function addNew() {
@@ -66,19 +80,35 @@ export default function ObjektSelector({ selected, onChange }: Props) {
{available.length > 0 && (
-
-
({ value: String(o.ID), label: o.Name }))}
- onChange={add}
- keepOpen
+
+
{ setSearch(e.target.value); setDropdownOpen(true); }}
+ onFocus={() => setDropdownOpen(true)}
+ placeholder="Objekt suchen..."
+ className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm text-gray-900 focus:border-blue-500 focus:outline-none"
/>
+ {dropdownOpen && filtered.length > 0 && (
+
+ {filtered.map((o) => (
+ add(o)}
+ className="w-full text-left px-4 py-2 text-sm text-gray-900 hover:bg-blue-50 active:bg-blue-100 border-b border-gray-100 last:border-0"
+ >
+ {o.Name}
+
+ ))}
+
+ )}
)}
setShowNewInput((v) => !v)}
- className="px-4 py-2 border-2 border-gray-400 rounded-lg bg-white text-base text-gray-700 hover:bg-gray-50 whitespace-nowrap"
+ className="px-4 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm text-gray-700 hover:bg-gray-50 whitespace-nowrap"
>
+ Neu
@@ -92,20 +122,20 @@ export default function ObjektSelector({ selected, onChange }: Props) {
onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNew(); } }}
placeholder="Objektname eingeben"
- className="flex-1 px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none"
+ className="flex-1 px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
autoFocus
/>
OK
{ setShowNewInput(false); setNewName(''); }}
- className="px-4 py-2 bg-gray-200 text-gray-700 text-base rounded-lg hover:bg-gray-300"
+ className="px-4 py-2 bg-gray-200 text-gray-700 text-sm rounded-lg hover:bg-gray-300"
>
✕
diff --git a/components/TimeInput.tsx b/components/TimeInput.tsx
new file mode 100644
index 0000000..91e9408
--- /dev/null
+++ b/components/TimeInput.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+interface Props {
+ value: string; // "HH:MM"
+ onChange: (value: string) => void;
+ className?: string;
+}
+
+function isValid(t: string): boolean {
+ if (!/^\d{1,2}:\d{2}$/.test(t)) return false;
+ const [h, m] = t.split(':').map(Number);
+ return h >= 0 && h <= 23 && m >= 0 && m <= 59;
+}
+
+function normalize(t: string): string {
+ const [h, m] = t.split(':').map(Number);
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
+}
+
+export default function TimeInput({ value, onChange, className = '' }: Props) {
+ const [local, setLocal] = useState(value);
+ const [error, setError] = useState(false);
+
+ useEffect(() => {
+ setLocal(value);
+ setError(false);
+ }, [value]);
+
+ function handleBlur() {
+ if (isValid(local)) {
+ const norm = normalize(local);
+ setLocal(norm);
+ setError(false);
+ onChange(norm);
+ } else {
+ setError(true);
+ }
+ }
+
+ return (
+
+
{ setLocal(e.target.value); setError(false); }}
+ onBlur={handleBlur}
+ placeholder="HH:MM"
+ className={`w-full px-2 py-1 border-2 rounded-lg bg-white text-sm text-gray-900 font-mono text-center focus:outline-none ${
+ error ? 'border-red-500 focus:border-red-500' : 'border-gray-400 focus:border-blue-500'
+ }`}
+ />
+ {error && (
+
+ Ungültig (00:00 – 23:59)
+
+ )}
+
+ );
+}
diff --git a/types/logbuch.ts b/types/logbuch.ts
index a68d144..dbef6a8 100644
--- a/types/logbuch.ts
+++ b/types/logbuch.ts
@@ -47,6 +47,7 @@ export interface LogbuchEintrag {
ID: number;
Kuppel: Kuppel;
ArtFuehrung: ArtFuehrung;
+ SonderName: string | null;
Beginn: string;
Ende: string;
Besucher: number;