Files
Lageplan/src/components/journal/modules/pendenzen-module.tsx
Pepe Ziberi 2dbb2204f6 feat(cockpit): Modul-Baukasten – Einsatz-Cockpit frei zusammenstellbar (v1.5.0)
Jede Feuerwehr stellt ihr Cockpit selbst zusammen (Wunsch: modular & einfach):

M0 – Zerlegt:
- SOMA + Pendenzen als eigenständige Modul-Komponenten, gemeinsame Typen
  (journal/types.ts), einheitliche ModuleCard-Hülle

M1 – Registry & Verwaltung:
- Modul-Registry src/lib/modules.ts, Konfiguration pro Mandant in
  tenants.modulesConfig (JSONB, idempotente Migration)
- API GET/PUT /api/tenant/modules (normalisiert + validiert via normalizeModules)
- "Module"-Button im Journal (Admins): an/aus, Reihenfolge hoch/runter,
  eigene Module löschen

M2 – Eigene Module:
- Generisches Tabellen-Modul: frei definierbare Spalten (Text / Haken mit
  Zeitstempel / Auto-Zeit), max 8 Spalten
- Generische Daten-API /api/projects/[id]/modules/[moduleId]/items (+ [itemId]),
  neue Tabelle module_items, Live-Sync über journal-refresh Events

M3 – Feld-Vorlagen:
- Checkliste, Atemschutz-Überwachung (Trupp/Druck/Zeit/Draussen),
  Kräfte vor Ort (Name/Funktion/seit/Weg), Lagemeldungen (Hauptbereich),
  leeres Modul mit Spalten-Editor

Eingebaute Module (Journal/SOMA/Pendenzen) bleiben ohne Konfiguration exakt
wie bisher — kaputte Configs fallen sicher auf den Standard zurück.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 18:15:34 +02:00

109 lines
3.9 KiB
TypeScript

'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Plus, Trash2, Check } from 'lucide-react'
import { type JournalPendenz, formatTime } from '@/components/journal/types'
interface PendenzenModuleProps {
items: JournalPendenz[]
canEdit: boolean
onAdd: (what: string, who: string, whenHow: string) => void
onToggle: (p: JournalPendenz) => void
onDelete: (id: string) => void
}
/** Pendenzen — eingebautes Cockpit-Modul (Was/Wer/Wann mit Erledigt-Haken) */
export function PendenzenModule({ items, canEdit, onAdd, onToggle, onDelete }: PendenzenModuleProps) {
const [what, setWhat] = useState('')
const [who, setWho] = useState('')
const [whenHow, setWhenHow] = useState('')
const submit = () => {
if (!what.trim()) return
onAdd(what.trim(), who.trim(), whenHow.trim())
setWhat('')
setWho('')
setWhenHow('')
}
return (
<>
{items.length === 0 ? (
<div className="text-center text-muted-foreground py-4 text-xs">
Keine Pendenzen
</div>
) : (
<div className="divide-y divide-border/50">
{items.map((p) => (
<div key={p.id} className={`flex items-start gap-1.5 px-2 py-1.5 text-xs print:text-[9px] group ${p.done ? 'bg-green-50 dark:bg-green-950/20 print:bg-green-50' : ''}`}>
<button
onClick={() => canEdit && onToggle(p)}
className={`w-4 h-4 mt-0.5 rounded border-2 flex items-center justify-center shrink-0 transition-colors ${
p.done
? 'bg-green-500 border-green-500 text-white'
: 'border-muted-foreground/30 hover:border-primary'
}`}
disabled={!canEdit}
>
{p.done && <Check className="w-2.5 h-2.5" />}
</button>
<div className="flex-1 min-w-0">
<p className={`${p.done ? 'text-muted-foreground' : ''}`}>
{p.what}
{p.done && p.doneAt && (
<span className="ml-1 text-green-600 text-[10px] print:text-[8px] font-medium no-underline">
(erledigt um {formatTime(p.doneAt)})
</span>
)}
</p>
<div className="flex gap-2 text-[10px] text-muted-foreground">
{p.who && <span>Wer: {p.who}</span>}
{p.whenHow && <span>Wann: {p.whenHow}</span>}
</div>
</div>
{canEdit && (
<button
onClick={() => onDelete(p.id)}
className="opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive shrink-0 mt-0.5 print:hidden"
>
<Trash2 className="w-3 h-3" />
</button>
)}
</div>
))}
</div>
)}
{canEdit && (
<div className="border-t border-border p-1.5 print:hidden">
<div className="flex gap-1">
<Input
placeholder="Was..."
value={what}
onChange={(e) => setWhat(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && submit()}
className="flex-1 h-6 text-[11px]"
/>
<Input
placeholder="Wer"
value={who}
onChange={(e) => setWho(e.target.value)}
className="w-14 h-6 text-[11px]"
/>
<Input
placeholder="Wann"
value={whenHow}
onChange={(e) => setWhenHow(e.target.value)}
className="w-14 h-6 text-[11px]"
/>
<Button size="sm" variant="ghost" onClick={submit} disabled={!what.trim()} className="h-6 px-1.5">
<Plus className="w-3 h-3" />
</Button>
</div>
</div>
)}
</>
)
}