feat: Druck – kleinere Schrift und chronologische Reihenfolge

Beim Drucken wird die Tabellenschrift auf 0.72rem verkleinert.
Der Drucken-Button lädt vorab alle Einträge des Monats in
aufsteigender Reihenfolge (älteste zuerst) und ruft dann
window.print() auf. API unterstützt jetzt order=asc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 21:19:06 +02:00
parent b18dfbe3f8
commit 8fabf7bb30
4 changed files with 46 additions and 16 deletions
-9
View File
@@ -150,15 +150,6 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) {
{/* Liste-Tab: vollständige Liste */} {/* Liste-Tab: vollständige Liste */}
{activeTab === 'liste' && ( {activeTab === 'liste' && (
<div className="border-2 border-gray-400 rounded-xl bg-white p-3 print:border-0 print:rounded-none print:p-0"> <div className="border-2 border-gray-400 rounded-xl bg-white p-3 print:border-0 print:rounded-none print:p-0">
<div className="flex justify-between items-center mb-2 print:hidden">
<span className="text-sm font-semibold text-gray-600">Einträge {activeKuppel}-Kuppel</span>
<button
onClick={() => window.print()}
className="text-sm px-3 py-1.5 bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-lg"
>
🖨 Drucken
</button>
</div>
<div className="hidden print:block mb-4"> <div className="hidden print:block mb-4">
<div className="text-lg font-bold">Sternwarte Welzheim Logbuch {activeKuppel}-Kuppel</div> <div className="text-lg font-bold">Sternwarte Welzheim Logbuch {activeKuppel}-Kuppel</div>
<div className="text-sm text-gray-500">Ausdruck vom {new Date().toLocaleDateString('de-DE')}</div> <div className="text-sm text-gray-500">Ausdruck vom {new Date().toLocaleDateString('de-DE')}</div>
+4 -4
View File
@@ -21,17 +21,17 @@ const LIST_SQL =
' LEFT JOIN logbuch_objekte lo ON lo.LogbuchID = l.ID' + ' LEFT JOIN logbuch_objekte lo ON lo.LogbuchID = l.ID' +
' LEFT JOIN objekte o ON o.ID = lo.ObjektID' + ' LEFT JOIN objekte o ON o.ID = lo.ObjektID' +
' WHERE l.Kuppel = ?' + ' WHERE l.Kuppel = ?' +
' GROUP BY l.ID' + ' GROUP BY l.ID';
' ORDER BY l.Beginn DESC';
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
const session = await getSession(); const session = await getSession();
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 }); if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const kuppel = searchParams.get('kuppel') || 'West'; const kuppel = searchParams.get('kuppel') || 'West';
const limit = Math.min(parseInt(searchParams.get('limit') || '10') || 10, 100); const limit = Math.min(parseInt(searchParams.get('limit') || '10') || 10, 500);
const offset = Math.max(0, parseInt(searchParams.get('offset') || '0') || 0); const offset = Math.max(0, parseInt(searchParams.get('offset') || '0') || 0);
const month = searchParams.get('month') || ''; const month = searchParams.get('month') || '';
const order = searchParams.get('order') === 'asc' ? 'ASC' : 'DESC';
let listWhere = 'WHERE l.Kuppel = ?'; let listWhere = 'WHERE l.Kuppel = ?';
let countWhere = 'WHERE Kuppel = ?'; let countWhere = 'WHERE Kuppel = ?';
@@ -50,7 +50,7 @@ export async function GET(request: NextRequest) {
try { try {
const [countRows, entries] = await Promise.all([ const [countRows, entries] = await Promise.all([
query('SELECT COUNT(*) AS total FROM logbuch ' + countWhere, params) as Promise<{ total: number }[]>, query('SELECT COUNT(*) AS total FROM logbuch ' + countWhere, params) as Promise<{ total: number }[]>,
query(LIST_SQL.replace('WHERE l.Kuppel = ?', listWhere) + ` LIMIT ${limit} OFFSET ${offset}`, params), query(LIST_SQL.replace('WHERE l.Kuppel = ?', listWhere) + ` ORDER BY l.Beginn ${order} LIMIT ${limit} OFFSET ${offset}`, params),
]); ]);
return NextResponse.json({ entries, total: (countRows as unknown as { total: number }[])[0]?.total ?? 0 }); return NextResponse.json({ entries, total: (countRows as unknown as { total: number }[])[0]?.total ?? 0 });
} catch (error) { } catch (error) {
+3
View File
@@ -33,4 +33,7 @@ body {
body { body {
background: white; background: white;
} }
table {
font-size: 0.72rem;
}
} }
+39 -3
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import type { Kuppel, LogbuchEintrag } from '@/types/logbuch'; import type { Kuppel, LogbuchEintrag } from '@/types/logbuch';
interface Props { interface Props {
@@ -58,6 +58,8 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [deleteId, setDeleteId] = useState<number | null>(null); const [deleteId, setDeleteId] = useState<number | null>(null);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [printEntries, setPrintEntries] = useState<LogbuchEintrag[] | null>(null);
const printPending = useRef(false);
useEffect(() => { setPage(0); }, [kuppel, refreshKey, month]); useEffect(() => { setPage(0); }, [kuppel, refreshKey, month]);
@@ -72,6 +74,27 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co
.catch(() => { setError('Fehler beim Laden.'); setLoading(false); }); .catch(() => { setError('Fehler beim Laden.'); setLoading(false); });
}, [kuppel, refreshKey, limit, page, month]); }, [kuppel, refreshKey, limit, page, month]);
useEffect(() => {
function onAfterPrint() { setPrintEntries(null); }
window.addEventListener('afterprint', onAfterPrint);
return () => window.removeEventListener('afterprint', onAfterPrint);
}, []);
useEffect(() => {
if (printPending.current && printEntries !== null) {
printPending.current = false;
window.print();
}
}, [printEntries]);
async function handlePrint() {
const url = `/api/logbuch?kuppel=${encodeURIComponent(kuppel)}&limit=500&offset=0` +
(month ? `&month=${encodeURIComponent(month)}` : '') + '&order=asc';
const data = await fetch(url).then((r) => r.json());
printPending.current = true;
setPrintEntries(data.entries);
}
async function confirmDelete(id: number) { async function confirmDelete(id: number) {
try { try {
const res = await fetch(`/api/logbuch/${id}`, { method: 'DELETE' }); const res = await fetch(`/api/logbuch/${id}`, { method: 'DELETE' });
@@ -129,8 +152,21 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co
if (loading) return <>{monthNav}<div className="text-gray-500 text-sm py-4">Lade Einträge...</div></>; if (loading) return <>{monthNav}<div className="text-gray-500 text-sm py-4">Lade Einträge...</div></>;
if (error) return <>{monthNav}<div className="text-red-600 text-sm py-4">{error}</div></>; if (error) return <>{monthNav}<div className="text-red-600 text-sm py-4">{error}</div></>;
const displayEntries = printEntries ?? entries;
return ( return (
<div> <div>
{!compact && (
<div className="flex justify-between items-center mb-2 print:hidden">
<span className="text-sm font-semibold text-gray-600">Einträge {kuppel}-Kuppel</span>
<button
onClick={handlePrint}
className="text-sm px-3 py-1.5 bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-lg"
>
🖨 Drucken
</button>
</div>
)}
{monthNav} {monthNav}
{printHeader} {printHeader}
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -156,13 +192,13 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{entries.length === 0 ? ( {displayEntries.length === 0 ? (
<tr> <tr>
<td colSpan={compact ? 7 : 10} className="px-3 py-4 text-gray-500 text-sm text-center"> <td colSpan={compact ? 7 : 10} className="px-3 py-4 text-gray-500 text-sm text-center">
Keine Einträge für {monthLabel(month)}. Keine Einträge für {monthLabel(month)}.
</td> </td>
</tr> </tr>
) : entries.map((e) => ( ) : displayEntries.map((e) => (
<tr key={e.ID} className="hover:bg-gray-50"> <tr key={e.ID} className="hover:bg-gray-50">
<td className={`${cell} whitespace-nowrap`}>{formatDate(e.Beginn, compact)}</td> <td className={`${cell} whitespace-nowrap`}>{formatDate(e.Beginn, compact)}</td>
{compact ? ( {compact ? (