feat(rapport): eigene Cockpit-Module erscheinen im Einsatzrapport (v1.5.5)

- Rapport öffnen sammelt jetzt auch die Daten der eigenen Tabellen-Module
  (Atemschutz, Kräfte vor Ort, Lagemeldungen, eigene Checklisten)
- Neue Rapport-Sektion rendert jede Modul-Tabelle mit ihren Spalten
  (Text / Haken / Zeit) im PDF unter Pendenzen
- Vorschau der Module auch im Rapport-Dialog
- Zeit-Spalten: ISO → HH:MM, Haken → ✓/—

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-22 19:50:02 +02:00
parent bbf6fbdd13
commit 0a6c9cbcf2
4 changed files with 133 additions and 47 deletions

View File

@@ -414,6 +414,75 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
window.print()
}, [])
// Rapport öffnen: Journal + SOMA + Pendenzen UND die eigenen Tabellen-Module
// einsammeln, damit alles im PDF-Rapport erscheint.
const [rapportLoading, setRapportLoading] = useState(false)
const handleOpenRapport = useCallback(async () => {
if (!projectId) return
setRapportLoading(true)
try {
// Daten der aktiven eigenen Module (type 'table') laden
const tableModules = modules.filter(m => m.enabled && m.type === 'table' && m.columns?.length)
const moduleTables = await Promise.all(tableModules.map(async (mod) => {
try {
const res = await fetch(`/api/projects/${projectId}/modules/${mod.id}/items`)
const data = res.ok ? await res.json() : { items: [] }
return {
name: mod.name,
columns: mod.columns,
rows: (data.items || []).map((it: any) => ({ ...it.data, _createdAt: it.createdAt })),
}
} catch {
return { name: mod.name, columns: mod.columns, rows: [] }
}
}))
const now = new Date()
setRapportForm({
organisation: tenantName || '',
abteilung: '',
datum: now.toLocaleDateString('de-CH'),
uhrzeit: now.toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit' }),
einsatzNr: einsatzNr || '',
alarmzeit: entries.length > 0 ? formatTime(entries[0].time) : '',
prioritaet: '',
einsatzort: projectLocation || '',
koordinaten: '',
objekt: '',
alarmierungsart: '',
stichwort: projectTitle || '',
zeitAlarm: entries.length > 0 ? formatTime(entries[0].time) : '',
zeitAusruecken: '', zeitEintreffen: '', zeitBereit: '',
zeitKontrolle: '', zeitAus: '', zeitEinruecken: '', zeitEnde: '',
lageEintreffen: '',
massnahmen: entries.map(e => `${formatTime(e.time)} ${e.what}${e.who ? ` (${e.who})` : ''}`),
somaItems: checkItems.map(c => ({
label: c.label,
confirmed: c.confirmed,
ok: c.ok,
confirmedAt: c.confirmedAt ? formatTime(c.confirmedAt) : null,
})),
pendenzenItems: pendenzen.map(p => ({
what: p.what,
who: p.who || '',
whenHow: p.whenHow || '',
done: p.done,
doneAt: p.doneAt ? formatTime(p.doneAt) : null,
})),
moduleTables,
fahrzeuge: [] as any[],
bemerkungen: '',
einsatzleiter: einsatzleiter || '',
rapporteur: journalfuehrer || '',
reportNumber: '',
logoUrl: tenantLogoUrl || '',
})
setShowRapportDialog(true)
} finally {
setRapportLoading(false)
}
}, [projectId, modules, tenantName, einsatzNr, entries, projectLocation, projectTitle, checkItems, pendenzen, einsatzleiter, journalfuehrer, tenantLogoUrl])
if (!projectId) {
return (
<div className="flex items-center justify-center h-full text-muted-foreground">
@@ -456,53 +525,10 @@ export function JournalView({ projectId, projectTitle, projectLocation, mode, ei
<Button
variant="outline"
size="sm"
disabled={!projectId}
onClick={() => {
if (!projectId) return
const now = new Date()
// Pre-fill form with available data
setRapportForm({
organisation: tenantName || '',
abteilung: '',
datum: now.toLocaleDateString('de-CH'),
uhrzeit: now.toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit' }),
einsatzNr: einsatzNr || '',
alarmzeit: entries.length > 0 ? formatTime(entries[0].time) : '',
prioritaet: '',
einsatzort: projectLocation || '',
koordinaten: '',
objekt: '',
alarmierungsart: '',
stichwort: projectTitle || '',
zeitAlarm: entries.length > 0 ? formatTime(entries[0].time) : '',
zeitAusruecken: '', zeitEintreffen: '', zeitBereit: '',
zeitKontrolle: '', zeitAus: '', zeitEinruecken: '', zeitEnde: '',
lageEintreffen: '',
massnahmen: entries.map(e => `${formatTime(e.time)} ${e.what}${e.who ? ` (${e.who})` : ''}`),
somaItems: checkItems.map(c => ({
label: c.label,
confirmed: c.confirmed,
ok: c.ok,
confirmedAt: c.confirmedAt ? formatTime(c.confirmedAt) : null,
})),
pendenzenItems: pendenzen.map(p => ({
what: p.what,
who: p.who || '',
whenHow: p.whenHow || '',
done: p.done,
doneAt: p.doneAt ? formatTime(p.doneAt) : null,
})),
fahrzeuge: [] as any[],
bemerkungen: '',
einsatzleiter: einsatzleiter || '',
rapporteur: journalfuehrer || '',
reportNumber: '',
logoUrl: tenantLogoUrl || '',
})
setShowRapportDialog(true)
}}
disabled={!projectId || rapportLoading}
onClick={handleOpenRapport}
>
<FileText className="w-4 h-4 mr-1.5" />
{rapportLoading ? <Loader2 className="w-4 h-4 mr-1.5 animate-spin" /> : <FileText className="w-4 h-4 mr-1.5" />}
Rapport
</Button>
<Button variant="outline" size="sm" onClick={() => setShowSendDialog(!showSendDialog)}>