feat: Custom Hazard Modal + Residual Risk Panel

- CustomHazardModal: Eigene Gefaehrdung erstellen mit S/E/P/A Slidern
- ResidualRiskPanel: Akzeptabel-Toggle pro Hazard + Fortschrittsbalken
- RiskAssessmentTable: Accept/Reject Buttons pro Zeile integriert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-07 16:09:50 +02:00
parent 5244500af6
commit a93ba9ee40
4 changed files with 386 additions and 8 deletions
@@ -1,12 +1,15 @@
'use client'
import React, { useState } from 'react'
import React, { useState, useMemo, useCallback } from 'react'
import { useParams } from 'next/navigation'
import { HazardForm } from './_components/HazardForm'
import { HazardTable } from './_components/HazardTable'
import { RiskAssessmentTable } from './_components/RiskAssessmentTable'
import { ResidualRiskPanel, getResidualStatus } from './_components/ResidualRiskPanel'
import type { ResidualFilter } from './_components/ResidualRiskPanel'
import { LibraryModal } from './_components/LibraryModal'
import { AutoSuggestPanel } from './_components/AutoSuggestPanel'
import { CustomHazardModal } from './_components/CustomHazardModal'
import { useHazards } from './_hooks/useHazards'
type ViewMode = 'list' | 'risk'
@@ -16,6 +19,30 @@ export default function HazardsPage() {
const projectId = params.projectId as string
const h = useHazards(projectId)
const [view, setView] = useState<ViewMode>('risk')
const [showCustomModal, setShowCustomModal] = useState(false)
const [residualFilter, setResidualFilter] = useState<ResidualFilter>('all')
const [decisions, setDecisions] = useState<Record<string, boolean | null>>({})
const handleDecision = useCallback(async (hazardId: string, acceptable: boolean | null) => {
setDecisions(prev => ({ ...prev, [hazardId]: acceptable }))
if (acceptable !== null) {
try {
await fetch(`/api/sdk/v1/iace/projects/${projectId}/hazards/${hazardId}/reassess`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ hazard_id: hazardId, is_acceptable: acceptable }),
})
} catch (err) { console.error('Decision save failed:', err) }
}
}, [projectId])
const filteredHazards = useMemo(() => {
if (residualFilter === 'all') return h.hazards
return h.hazards.filter(hz => {
const status = getResidualStatus(hz, decisions[hz.id] ?? null)
return status === residualFilter
})
}, [h.hazards, decisions, residualFilter])
if (h.loading) {
return (
@@ -64,6 +91,13 @@ export default function HazardsPage() {
</svg>
Aus Bibliothek
</button>
<button onClick={() => setShowCustomModal(true)}
className="flex items-center gap-2 px-3 py-2 border border-orange-300 text-orange-700 rounded-lg hover:bg-orange-50 transition-colors text-sm">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Eigene Gefaehrdung
</button>
<button onClick={() => h.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 text-sm">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -124,9 +158,20 @@ export default function HazardsPage() {
<LibraryModal library={h.library} onAdd={h.handleAddFromLibrary} onClose={() => h.setShowLibrary(false)} />
)}
{showCustomModal && (
<CustomHazardModal roles={h.roles}
onSubmit={async (data) => { await h.handleSubmit(data); setShowCustomModal(false) }}
onClose={() => setShowCustomModal(false)} />
)}
{h.hazards.length > 0 ? (
view === 'risk' ? (
<RiskAssessmentTable projectId={projectId} hazards={h.hazards} onReassess={h.refetch} />
<>
<ResidualRiskPanel hazards={h.hazards} decisions={decisions}
activeFilter={residualFilter} onFilterChange={setResidualFilter} />
<RiskAssessmentTable projectId={projectId} hazards={filteredHazards}
onReassess={h.refetch} decisions={decisions} onDecision={handleDecision} />
</>
) : (
<HazardTable hazards={h.hazards} lifecyclePhases={h.lifecyclePhases} onDelete={h.handleDelete} />
)