'use client' import React, { useState, useEffect } from 'react' import { useParams } from 'next/navigation' interface Mitigation { id: string title: string description: string reduction_type: 'design' | 'protection' | 'information' status: 'planned' | 'implemented' | 'verified' linked_hazard_ids: string[] linked_hazard_names: string[] created_at: string verified_at: string | null verified_by: string | null } interface Hazard { id: string name: string risk_level: string } const REDUCTION_TYPES = { design: { label: 'Design', description: 'Inhaerent sichere Konstruktion', color: 'border-blue-200 bg-blue-50', headerColor: 'bg-blue-100 text-blue-800', icon: ( ), }, protection: { label: 'Schutz', description: 'Technische Schutzmassnahmen', color: 'border-green-200 bg-green-50', headerColor: 'bg-green-100 text-green-800', icon: ( ), }, information: { label: 'Information', description: 'Hinweise und Schulungen', color: 'border-yellow-200 bg-yellow-50', headerColor: 'bg-yellow-100 text-yellow-800', icon: ( ), }, } function StatusBadge({ status }: { status: string }) { const colors: Record = { planned: 'bg-gray-100 text-gray-700', implemented: 'bg-blue-100 text-blue-700', verified: 'bg-green-100 text-green-700', } const labels: Record = { planned: 'Geplant', implemented: 'Umgesetzt', verified: 'Verifiziert', } return ( {labels[status] || status} ) } interface MitigationFormData { title: string description: string reduction_type: 'design' | 'protection' | 'information' linked_hazard_ids: string[] } function MitigationForm({ onSubmit, onCancel, hazards, preselectedType, }: { onSubmit: (data: MitigationFormData) => void onCancel: () => void hazards: Hazard[] preselectedType?: 'design' | 'protection' | 'information' }) { const [formData, setFormData] = useState({ title: '', description: '', reduction_type: preselectedType || 'design', linked_hazard_ids: [], }) function toggleHazard(id: string) { setFormData((prev) => ({ ...prev, linked_hazard_ids: prev.linked_hazard_ids.includes(id) ? prev.linked_hazard_ids.filter((h) => h !== id) : [...prev.linked_hazard_ids, id], })) } return ( Neue Massnahme Titel * setFormData({ ...formData, title: e.target.value })} placeholder="z.B. Lichtvorhang an Gefahrenstelle" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white" /> Reduktionstyp setFormData({ ...formData, reduction_type: e.target.value as MitigationFormData['reduction_type'] })} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white" > Design - Inhaerent sichere Konstruktion Schutz - Technische Schutzmassnahmen Information - Hinweise und Schulungen Beschreibung setFormData({ ...formData, description: e.target.value })} rows={2} placeholder="Detaillierte Beschreibung der Massnahme..." className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white" /> {hazards.length > 0 && ( Verknuepfte Gefaehrdungen {hazards.map((h) => ( toggleHazard(h.id)} className={`px-3 py-1.5 text-xs rounded-lg border transition-colors ${ formData.linked_hazard_ids.includes(h.id) ? 'border-purple-400 bg-purple-50 text-purple-700' : 'border-gray-200 bg-white text-gray-600 hover:bg-gray-50' }`} > {h.name} ))} )} onSubmit(formData)} disabled={!formData.title} className={`px-6 py-2 rounded-lg font-medium transition-colors ${ formData.title ? 'bg-purple-600 text-white hover:bg-purple-700' : 'bg-gray-200 text-gray-400 cursor-not-allowed' }`} > Hinzufuegen Abbrechen ) } function MitigationCard({ mitigation, onVerify, onDelete, }: { mitigation: Mitigation onVerify: (id: string) => void onDelete: (id: string) => void }) { return ( {mitigation.title} {mitigation.description && ( {mitigation.description} )} {mitigation.linked_hazard_names.length > 0 && ( {mitigation.linked_hazard_names.map((name, i) => ( {name} ))} )} {mitigation.status !== 'verified' && ( onVerify(mitigation.id)} className="text-xs px-2.5 py-1 bg-green-50 text-green-700 border border-green-200 rounded-lg hover:bg-green-100 transition-colors" > Verifizieren )} onDelete(mitigation.id)} className="text-xs px-2.5 py-1 text-red-600 hover:bg-red-50 rounded-lg transition-colors" > Loeschen ) } export default function MitigationsPage() { const params = useParams() const projectId = params.projectId as string const [mitigations, setMitigations] = useState([]) const [hazards, setHazards] = useState([]) const [loading, setLoading] = useState(true) const [showForm, setShowForm] = useState(false) const [preselectedType, setPreselectedType] = useState<'design' | 'protection' | 'information' | undefined>() useEffect(() => { fetchData() }, [projectId]) async function fetchData() { try { const [mitRes, hazRes] = await Promise.all([ fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations`), fetch(`/api/sdk/v1/iace/projects/${projectId}/hazards`), ]) if (mitRes.ok) { const json = await mitRes.json() setMitigations(json.mitigations || json || []) } if (hazRes.ok) { const json = await hazRes.json() setHazards((json.hazards || json || []).map((h: Hazard) => ({ id: h.id, name: h.name, risk_level: h.risk_level }))) } } catch (err) { console.error('Failed to fetch data:', err) } finally { setLoading(false) } } async function handleSubmit(data: MitigationFormData) { try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (res.ok) { setShowForm(false) setPreselectedType(undefined) await fetchData() } } catch (err) { console.error('Failed to add mitigation:', err) } } async function handleVerify(id: string) { try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations/${id}/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }) if (res.ok) { await fetchData() } } catch (err) { console.error('Failed to verify mitigation:', err) } } async function handleDelete(id: string) { if (!confirm('Massnahme wirklich loeschen?')) return try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations/${id}`, { method: 'DELETE' }) if (res.ok) { await fetchData() } } catch (err) { console.error('Failed to delete mitigation:', err) } } function handleAddForType(type: 'design' | 'protection' | 'information') { setPreselectedType(type) setShowForm(true) } const byType = { design: mitigations.filter((m) => m.reduction_type === 'design'), protection: mitigations.filter((m) => m.reduction_type === 'protection'), information: mitigations.filter((m) => m.reduction_type === 'information'), } if (loading) { return ( ) } return ( {/* Header */} Massnahmen Risikominderung nach dem 3-Stufen-Verfahren: Design, Schutz, Information. { setPreselectedType(undefined) setShowForm(true) }} className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors" > Massnahme hinzufuegen {/* Form */} {showForm && ( { setShowForm(false) setPreselectedType(undefined) }} hazards={hazards} preselectedType={preselectedType} /> )} {/* 3-Column Layout */} {(['design', 'protection', 'information'] as const).map((type) => { const config = REDUCTION_TYPES[type] const items = byType[type] return ( {config.icon} {config.label} {config.description} {items.length} {items.map((m) => ( ))} handleAddForType(type)} className="mt-3 w-full py-2 text-sm text-gray-500 hover:text-purple-600 hover:bg-white rounded-lg border border-dashed border-gray-300 hover:border-purple-300 transition-colors" > + Massnahme hinzufuegen ) })} ) }
{mitigation.description}
Risikominderung nach dem 3-Stufen-Verfahren: Design, Schutz, Information.
{config.description}