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:
@@ -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} />
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user