fix(cockpit): Modul-Spalten sauber ausgerichtet + Zahl-Spalten im Rapport (v1.8.8)

- table-module: Spaltenbreiten respektieren Breiten-Hinweise (w-16…w-32); Eingabezeile im
  selben Raster wie Kopf/Daten (keine abgeschnittenen Platzhalter, saubere Ausrichtung)
- Rapport (/rapport/[token]): Zahl-Spalten rechtsbündig/monospace
- Hinweis: Modul→Rapport war bereits vollständig integriert; leere Module werden im
  Rapport ausgelassen (erst mit Zeilen sichtbar)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-23 22:04:08 +02:00
parent 4a59b5ca0b
commit 2323f1d360
3 changed files with 33 additions and 20 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "lageplan",
"version": "1.8.7",
"version": "1.8.8",
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
"private": true,
"scripts": {

View File

@@ -268,7 +268,7 @@ export default function RapportViewerPage({ params }: { params: Promise<{ token:
<thead>
<tr className="bg-gray-900 text-white">
{(mt.columns || []).map((c: any) => (
<th key={c.key} className={`p-1.5 font-semibold uppercase tracking-wider text-[7pt] ${c.type === 'check' ? 'text-center w-10' : c.type === 'time' ? 'text-left w-16' : 'text-left'}`}>{c.label}</th>
<th key={c.key} className={`p-1.5 font-semibold uppercase tracking-wider text-[7pt] ${c.type === 'check' ? 'text-center w-10' : c.type === 'time' ? 'text-left w-16' : c.type === 'number' ? 'text-right w-20' : 'text-left'}`}>{c.label}</th>
))}
</tr>
</thead>
@@ -276,7 +276,7 @@ export default function RapportViewerPage({ params }: { params: Promise<{ token:
{mt.rows.map((row: any, ri: number) => (
<tr key={ri} className={ri % 2 === 1 ? 'bg-gray-50' : ''}>
{(mt.columns || []).map((c: any) => (
<td key={c.key} className={`p-1.5 border-b border-gray-100 ${c.type === 'check' ? 'text-center font-bold' : ''} ${c.type === 'time' ? 'font-mono text-[8pt] text-gray-500' : ''}`}>
<td key={c.key} className={`p-1.5 border-b border-gray-100 ${c.type === 'check' ? 'text-center font-bold' : ''} ${c.type === 'time' ? 'font-mono text-[8pt] text-gray-500' : ''} ${c.type === 'number' ? 'text-right font-mono tabular-nums' : ''}`}>
{c.type === 'check'
? (row[c.key] ? '✓' : '—')
: c.type === 'time'

View File

@@ -115,10 +115,17 @@ export function TableModule({ projectId, module, canEdit, notifyChanged }: Table
}
}, [projectId, module.id, notifyChanged])
// Spaltenbreite ableiten — respektiert optionale Breiten-Hinweise (w-16 … w-32)
const WIDTH_MAP: Record<string, string> = { 'w-10': '40px', 'w-16': '68px', 'w-20': '92px', 'w-24': '108px', 'w-28': '124px', 'w-32': '140px' }
const colSize = (c: ModuleColumn): string => {
if (c.type === 'check') return '36px'
if (c.type === 'time') return '58px'
if (c.width && WIDTH_MAP[c.width]) return WIDTH_MAP[c.width]
if (c.type === 'number') return '96px'
return 'minmax(90px,1fr)'
}
// Grid-Template aus Spalten ableiten (+ Lösch-Spalte)
const gridCols = columns.map(c =>
c.type === 'check' ? '32px' : c.type === 'time' ? '52px' : c.type === 'number' ? 'minmax(0,0.6fr)' : 'minmax(0,1fr)'
).join(' ') + (canEdit ? ' 24px' : '')
const gridCols = columns.map(colSize).join(' ') + (canEdit ? ' 28px' : '')
return (
<div>
@@ -194,21 +201,27 @@ export function TableModule({ projectId, module, canEdit, notifyChanged }: Table
</div>
)}
{/* Neue Zeile */}
{/* Neue Zeile — im selben Raster wie die Spalten, damit Eingaben unter den Titeln stehen */}
{canEdit && (
<div className="flex gap-1 px-1.5 py-1.5 border-t border-border/50 print:hidden">
{textColumns.map((c, i) => (
<div className="grid items-center gap-1 px-1.5 py-1.5 border-t border-border/50 print:hidden" style={{ gridTemplateColumns: gridCols }}>
{columns.map(c => {
if (c.type === 'text' || c.type === 'number') {
return (
<Input
key={c.key}
placeholder={c.label + '…'}
placeholder={c.label}
inputMode={c.type === 'number' ? 'numeric' : undefined}
value={draft[c.key] || ''}
onChange={(e) => setDraft(prev => ({ ...prev, [c.key]: e.target.value }))}
onKeyDown={(e) => e.key === 'Enter' && addRow()}
className={`h-6 text-[11px] ${i === 0 && c.type !== 'number' ? 'flex-1 min-w-0' : 'w-20 shrink-0'}`}
className="h-6 text-[11px] min-w-0 px-1.5"
/>
))}
<Button size="sm" variant="ghost" onClick={addRow} disabled={!textColumns.some(c => (draft[c.key] || '').trim())} className="h-6 px-1.5 shrink-0">
)
}
// Zeit/Haken: leere Zelle (wird automatisch/durch Klick gesetzt)
return <div key={c.key} className="text-center text-[10px] text-muted-foreground/50">{c.type === 'time' ? 'auto' : ''}</div>
})}
<Button size="sm" variant="ghost" onClick={addRow} disabled={!textColumns.some(c => (draft[c.key] || '').trim())} className="h-6 w-6 p-0 shrink-0">
<Plus className="w-3 h-3" />
</Button>
</div>