From 9e2f430d4a299362efe04944859e23e340758c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Thu, 14 May 2026 21:45:09 +0200 Subject: [PATCH] v1.7.4: Suche in Listenansicht (Bemerkungen, Objekte, BEOs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suchfeld in der Toolbar der Listenansicht: Suche über alle Einträge der Kuppel in Bemerkungen, Objekte und BEOs. Monatsauswahl, Suchfeld und Drucken-Button in einer Zeile; Monatsauswahl wird bei aktiver Suche unsichtbar aber platzhaltend ausgeblendet. Co-Authored-By: Claude Sonnet 4.6 --- ANLEITUNG.md | 165 +++++++++++++++++++++++++++++++++++++ app/api/logbuch/route.ts | 31 +++++++ components/LogbuchList.tsx | 110 +++++++++++++++---------- package-lock.json | 4 +- package.json | 2 +- 5 files changed, 264 insertions(+), 48 deletions(-) create mode 100644 ANLEITUNG.md diff --git a/ANLEITUNG.md b/ANLEITUNG.md new file mode 100644 index 0000000..1094023 --- /dev/null +++ b/ANLEITUNG.md @@ -0,0 +1,165 @@ +# Bedienungsanleitung – Logbuch Sternwarte Welzheim + +## Inhaltsverzeichnis + +1. [Anmelden](#1-anmelden) +2. [Grundaufbau der App](#2-grundaufbau-der-app) +3. [Eintrag erfassen (Tab „Eingabe")](#3-eintrag-erfassen-tab-eingabe) +4. [Einträge einsehen und verwalten (Tab „Liste")](#4-einträge-einsehen-und-verwalten-tab-liste) +5. [Jahresstatistik (Tab „Statistik")](#5-jahresstatistik-tab-statistik) +6. [Drucken](#6-drucken) +7. [Administration (nur Admins)](#7-administration-nur-admins) + +--- + +## 1. Anmelden + +Die App ist passwortgeschützt. Beim ersten Aufruf erscheint die Anmeldeseite. + +- **Kürzel**: das persönliche BEO-Kürzel (z. B. `RXF`) +- **Passwort**: individuell gesetztes Passwort + +Wurde das Passwort noch nicht geändert (Anzeige „Standard"), muss nach dem ersten Login sofort ein neues Passwort vergeben werden. Das Standard-Passwort lautet `welzheim`. + +--- + +## 2. Grundaufbau der App + +### Kuppel-Auswahl + +Oben befinden sich vier Reiter für die vier Kuppeln: + +| Reiter | Bedeutung | +|--------|-----------| +| West | West-Kuppel | +| Ost | Ost-Kuppel | +| Süd | Süd-Kuppel | +| Pluto | Pluto-Kuppel | + +Alle Einträge, Listen und Statistiken beziehen sich immer auf die gerade gewählte Kuppel. + +### Funktions-Tabs + +Unterhalb der Kuppelauswahl gibt es drei Tabs: + +| Tab | Funktion | +|-----|----------| +| **Eingabe** | Neuen Eintrag anlegen oder bestehenden bearbeiten | +| **Liste** | Alle Einträge monatsweise ansehen, bearbeiten oder löschen | +| **Statistik** | Jahresübersicht Besucher und Führungen | + +--- + +## 3. Eintrag erfassen (Tab „Eingabe") + +### Pflichtfelder + +**Art der Führung** – Auswahl aus dem Dropdown: + +| Kürzel | Bedeutung | +|--------|-----------| +| regulär | Reguläre öffentliche Führung | +| sonder | Sonderführung (für Gruppen, Schulen etc.) | +| sonnen | Sonnenführung | +| privat | Privatführung | +| BEOS | BEO-Sitzung (keine Besucher/Objekte) | +| TD | Treff/Diskussion (keine Besucher/Objekte) | +| Beobachtung | Reine Beobachtung ohne Führung | +| ToT | Teleskop ohne Termin | +| Sonstiges | Sonstige Veranstaltung | + +**Datum** – Datum der Veranstaltung (Standardwert: heute). + +**Startzeit / Endzeit** – Uhrzeit von Beginn und Ende. Die Startzeit wird beim Laden automatisch auf die aktuelle Uhrzeit gesetzt; die Endzeit ist zunächst leer und muss eingetragen werden. + +**Besucher** – Anzahl der Besucher (nicht bei BEOS und TD). + +### Optionale Felder + +**Name / Gruppe** – erscheint nur bei Sonderführung; Name der Gruppe oder Person. + +**BEOs** – beteiligte Beobachter. Der eigene Name ist automatisch vorausgewählt. Weitere BEOs können über das Suchfeld hinzugefügt werden; ein Klick auf × entfernt sie wieder. + +**Beobachtete Objekte** – nicht sichtbar bei BEOS und TD; bei Sonnenführungen fest auf „Sonne" gesetzt. Für alle anderen Arten: +- Bekannte Objekte durch Eintippen suchen und aus dem Dropdown auswählen. +- Noch unbekannte Objekte einfach eintippen – am Ende der Dropdown-Liste erscheint dann **+ „[Name]" hinzufügen**. Ein Klick (oder Enter bei leerem Suchergebnis) legt das Objekt neu an. +- Ausgewählte Objekte erscheinen als grüne Chips; × entfernt sie. + +**Bemerkungen** – freier Text, max. 500 Zeichen. + +**Wetterdaten** – Temperatur (°C), Luftfeuchtigkeit (%) und Luftdruck (hPa) werden automatisch vom lokalen Wetterdienst vorausgefüllt und können manuell korrigiert werden. + +### Eintrag speichern + +Schaltfläche **Eintrag speichern** unten im Formular. Eine grüne Meldung bestätigt die Speicherung; das Formular wird zurückgesetzt. + +> Auf Desktop-Geräten erscheinen unterhalb des Formulars die letzten 5 Einträge der aktuellen Kuppel als kompakte Vorschau. + +### Eintrag bearbeiten + +Im Tab „Liste" das Stift-Symbol (✎) anklicken. Die App springt zum Tab „Eingabe" und zeigt einen gelben Hinweis „Eintrag bearbeiten (ID …)". Nach der Änderung **Änderungen speichern** klicken oder mit **Abbrechen** verwerfen. + +--- + +## 4. Einträge einsehen und verwalten (Tab „Liste") + +### Monatsnavigation + +Mit den Pfeiltasten ← → den Monat wechseln oder direkt im Monatsfeld eingeben. **Aktueller Monat** springt zurück auf den laufenden Monat. + +### Tabelleninhalt + +Die Tabelle zeigt pro Eintrag: Datum, Uhrzeit (Beginn–Ende), Art der Führung, Besucher, beteiligte BEOs, beobachtete Objekte, Bemerkungen und Wetterdaten. Der Ersteller des Eintrags ist in der BEO-Spalte **fettgedruckt** und steht an erster Stelle. + +### Eintrag bearbeiten + +Stift-Symbol ✎ rechts in der Zeile. + +### Eintrag löschen + +× rechts in der Zeile – es erscheint ein Bestätigungsdialog. Das Löschen ist **unwiderruflich**. + +### Seitennavigation + +Bei mehr als 15 Einträgen im Monat erscheinen Vor/Zurück-Schaltflächen am unteren Rand. + +--- + +## 5. Jahresstatistik (Tab „Statistik") + +Zeigt eine Monatstabelle mit Anzahl der Führungen und Besuchern, aufgeschlüsselt nach Art der Führung. + +- **Jahr** oben links änderbar (Eingabefeld). +- Darunter vier Kennzahlen-Kacheln: + - Kumulierte Besucher des Jahres für die gewählte Kuppel + - Führungstage des Jahres für die gewählte Kuppel + - Kumulierte Besucher für die gesamte Sternwarte (alle Kuppeln) + - Führungstage für die gesamte Sternwarte + +--- + +## 6. Drucken + +Im Tab **Liste**: Schaltfläche **🖨 Drucken** oben rechts in der Liste. + +- Es werden **alle Einträge des aktuell gewählten Monats** geladen (nicht nur die angezeigte Seite). +- Die Reihenfolge ist beim Ausdruck **chronologisch** (ältester Eintrag zuerst). +- Navigations- und Aktionselemente werden ausgeblendet; oben erscheint ein Kopfzeile mit Kuppelname und Druckdatum. +- Seitenformat: A4 Hochformat, Rand 1,5 cm. + +Im Tab **Statistik**: ebenfalls eine **🖨 Drucken**-Schaltfläche für die Jahresstatistik. + +--- + + +--- + +## 7. Administration (nur Admins) + +Erreichbar über die Schaltfläche **Admin** oben rechts (nur für Benutzer mit Admin-Rolle sichtbar). + +### Benutzerverwaltung + +Die Tabelle zeigt alle BEOs mit Kürzel, Name, Vorname, Rolle und Passwortstatus. + +**Passwort zurücksetzen**: Schaltfläche „Zurücksetzen" neben dem jeweiligen Benutzer. Das Passwort wird auf NULL gesetzt; beim nächsten Login muss der Benutzer das Standard-Passwort `welzheim` verwenden und wird anschließend aufgefordert, ein neues Passwort zu vergeben. diff --git a/app/api/logbuch/route.ts b/app/api/logbuch/route.ts index fc43f8d..ae1fca2 100644 --- a/app/api/logbuch/route.ts +++ b/app/api/logbuch/route.ts @@ -33,6 +33,37 @@ export async function GET(request: NextRequest) { const month = searchParams.get('month') || ''; const order = searchParams.get('order') === 'asc' ? 'ASC' : 'DESC'; + const search = (searchParams.get('search') || '').trim(); + + if (search) { + const pattern = '%' + search + '%'; + const searchParams2 = [kuppel, pattern, pattern, pattern]; + const countSQL = + 'SELECT COUNT(*) AS total FROM (' + + 'SELECT l.ID FROM logbuch l' + + ' LEFT JOIN logbuch_beos lb ON lb.LogbuchID = l.ID' + + ' LEFT JOIN (SELECT id, `kürzel` AS kuerzel FROM beos) bk ON bk.id = lb.BeoID' + + ' LEFT JOIN logbuch_objekte lo ON lo.LogbuchID = l.ID' + + ' LEFT JOIN objekte o ON o.ID = lo.ObjektID' + + ' WHERE l.Kuppel = ?' + + ' GROUP BY l.ID' + + " HAVING (MAX(l.Bemerkungen) LIKE ? OR GROUP_CONCAT(DISTINCT bk.kuerzel ORDER BY bk.kuerzel SEPARATOR ', ') LIKE ? OR GROUP_CONCAT(DISTINCT o.Name ORDER BY o.Name SEPARATOR ', ') LIKE ?)" + + ') AS sub'; + const listSQL = LIST_SQL + + ' HAVING (MAX(l.Bemerkungen) LIKE ? OR BEOs LIKE ? OR Objekte LIKE ?)' + + ` ORDER BY l.Beginn DESC LIMIT ${limit} OFFSET ${offset}`; + try { + const [countRows, entries] = await Promise.all([ + query(countSQL, searchParams2) as Promise<{ total: number }[]>, + query(listSQL, searchParams2), + ]); + return NextResponse.json({ entries, total: (countRows as unknown as { total: number }[])[0]?.total ?? 0 }); + } catch (error) { + console.error('GET /api/logbuch (search):', error); + return NextResponse.json({ error: 'Datenbankfehler' }, { status: 500 }); + } + } + let listWhere = 'WHERE l.Kuppel = ?'; let countWhere = 'WHERE Kuppel = ?'; let params: (string | number | null)[] = [kuppel]; diff --git a/components/LogbuchList.tsx b/components/LogbuchList.tsx index 79aebfb..73a0d00 100644 --- a/components/LogbuchList.tsx +++ b/components/LogbuchList.tsx @@ -59,20 +59,27 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co const [deleteId, setDeleteId] = useState(null); const [error, setError] = useState(''); const [printEntries, setPrintEntries] = useState(null); + const [search, setSearch] = useState(''); + const [activeSearch, setActiveSearch] = useState(''); const printPending = useRef(false); - useEffect(() => { setPage(0); }, [kuppel, refreshKey, month]); + useEffect(() => { + const t = setTimeout(() => setActiveSearch(search.trim()), 300); + return () => clearTimeout(t); + }, [search]); + + useEffect(() => { setPage(0); }, [kuppel, refreshKey, month, activeSearch]); useEffect(() => { setLoading(true); const offset = page * limit; const url = `/api/logbuch?kuppel=${encodeURIComponent(kuppel)}&limit=${limit}&offset=${offset}` + - (month ? `&month=${encodeURIComponent(month)}` : ''); + (activeSearch ? `&search=${encodeURIComponent(activeSearch)}` : (month ? `&month=${encodeURIComponent(month)}` : '')); fetch(url) .then((r) => { if (!r.ok) throw new Error(); return r.json(); }) .then((data) => { setEntries(data.entries); setTotal(data.total); setLoading(false); }) .catch(() => { setError('Fehler beim Laden.'); setLoading(false); }); - }, [kuppel, refreshKey, limit, page, month]); + }, [kuppel, refreshKey, limit, page, month, activeSearch]); useEffect(() => { function onAfterPrint() { setPrintEntries(null); } @@ -108,32 +115,57 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co } } - const monthNav = !compact && ( + const toolbar = !compact && (
- - setMonth(e.target.value > currentMonth() ? currentMonth() : e.target.value)} - className="border border-gray-300 rounded-lg px-2 py-1 text-sm" - /> - - {month !== currentMonth() && ( +
- )} + onClick={() => setMonth((m) => prevMonth(m))} + className="px-2 py-1 text-sm rounded-lg bg-gray-200 hover:bg-gray-300" + >← + setMonth(e.target.value > currentMonth() ? currentMonth() : e.target.value)} + className="border border-gray-300 rounded-lg px-2 py-1 text-sm" + /> + + {month !== currentMonth() && ( + + )} +
+
+ setSearch(e.target.value)} + placeholder="Suche in Bemerkungen, Objekte, BEOs…" + className="w-full px-3 py-1.5 pr-8 border border-gray-300 rounded-lg text-sm focus:outline-none focus:border-blue-500" + /> + {search ? ( + + ) : ( + 🔍 + )} +
+
); @@ -150,27 +182,15 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co ? 'px-1.5 py-1 border border-gray-300 text-xs font-semibold' : 'px-3 py-2 border border-gray-300'; - if (loading) return <>{monthNav}
Lade Einträge...
; - if (error) return <>{monthNav}
{error}
; - const displayEntries = printEntries ?? entries; return (
- {!compact && ( -
- Einträge {kuppel}-Kuppel - -
- )} - {monthNav} + {toolbar} {printHeader} -
+ {loading &&
Lade Einträge...
} + {error &&
{error}
} + {!loading && !error &&
@@ -196,7 +216,7 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co {displayEntries.length === 0 ? ( ) : displayEntries.map((e) => ( @@ -257,7 +277,7 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co ))}
- Keine Einträge für {monthLabel(month)}. + {activeSearch ? `Keine Einträge für „${activeSearch}" gefunden.` : `Keine Einträge für ${monthLabel(month)}.`}
-
+
} {!compact && total > limit && (
diff --git a/package-lock.json b/package-lock.json index ee5f501..79d5d4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "logbuch", - "version": "1.7.3", + "version": "1.7.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "logbuch", - "version": "1.7.3", + "version": "1.7.4", "dependencies": { "bcryptjs": "^3.0.3", "jose": "^6.2.2", diff --git a/package.json b/package.json index c97a9e0..421b1ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logbuch", - "version": "1.7.3", + "version": "1.7.4", "private": true, "scripts": { "dev": "next dev",