feat: show investigation panel on incident detail page
Fetches investigation record (5-Why steps, findings, root cause, alcohol test, witness refs) and renders below incident details. Shows completion status and investigator name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
type FiveWhyStep = { why: string; answer: string }
|
||||
|
||||
interface Investigation {
|
||||
id: string
|
||||
method: 'five_why' | 'fishbone' | 'other'
|
||||
five_why_steps: FiveWhyStep[] | null
|
||||
fishbone_categories: Record<string, string> | null
|
||||
findings_text: string | null
|
||||
root_cause_summary: string | null
|
||||
alcohol_test_result: string | null
|
||||
witness_statement_refs: string[]
|
||||
completed_at: string | null
|
||||
investigator: { name: string | null; email: string } | null
|
||||
}
|
||||
|
||||
interface Props {
|
||||
investigation: Investigation
|
||||
}
|
||||
|
||||
const METHOD_LABELS = {
|
||||
five_why: '5-Why Analysis',
|
||||
fishbone: 'Fishbone (Ishikawa)',
|
||||
other: 'Other Method',
|
||||
}
|
||||
|
||||
export function InvestigationPanel({ investigation: inv }: Props) {
|
||||
const investigatorName = inv.investigator?.name ?? inv.investigator?.email ?? 'Unknown'
|
||||
|
||||
return (
|
||||
<div className="mt-6 bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-gray-100">
|
||||
<h2 className="text-sm font-semibold text-gray-900">Investigation</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-500">{METHOD_LABELS[inv.method] ?? inv.method}</span>
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${
|
||||
inv.completed_at
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-yellow-100 text-yellow-700'
|
||||
}`}>
|
||||
{inv.completed_at ? 'Completed' : 'In Progress'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-5 py-4 space-y-5">
|
||||
<p className="text-xs text-gray-500">
|
||||
Investigator: <span className="font-medium text-gray-700">{investigatorName}</span>
|
||||
{inv.completed_at && (
|
||||
<span className="ml-2">
|
||||
· Completed {new Date(inv.completed_at).toLocaleDateString('en-MY', { day: 'numeric', month: 'short', year: 'numeric' })}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{/* 5-Why steps */}
|
||||
{inv.method === 'five_why' && inv.five_why_steps && inv.five_why_steps.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3">5-Why Steps</h3>
|
||||
<ol className="space-y-3">
|
||||
{inv.five_why_steps.map((step, i) => (
|
||||
<li key={i} className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-blue-100 text-blue-700 text-xs font-bold flex items-center justify-center">
|
||||
{i + 1}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-medium text-gray-700">{step.why}</p>
|
||||
{step.answer && (
|
||||
<p className="text-sm text-gray-900 mt-0.5">{step.answer}</p>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Fishbone categories */}
|
||||
{inv.method === 'fishbone' && inv.fishbone_categories && Object.keys(inv.fishbone_categories).length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3">Fishbone Categories</h3>
|
||||
<dl className="space-y-2">
|
||||
{Object.entries(inv.fishbone_categories).map(([cat, val]) => (
|
||||
<div key={cat}>
|
||||
<dt className="text-xs font-medium text-gray-500 capitalize">{cat}</dt>
|
||||
<dd className="text-sm text-gray-900">{val}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Findings */}
|
||||
{inv.findings_text && (
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1">Findings</h3>
|
||||
<p className="text-sm text-gray-900 whitespace-pre-wrap">{inv.findings_text}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Root cause */}
|
||||
{inv.root_cause_summary && (
|
||||
<div className="bg-red-50 border border-red-100 rounded-lg px-4 py-3">
|
||||
<h3 className="text-xs font-semibold text-red-600 uppercase tracking-wide mb-1">Root Cause</h3>
|
||||
<p className="text-sm text-gray-900 whitespace-pre-wrap">{inv.root_cause_summary}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Alcohol test */}
|
||||
{inv.alcohol_test_result && (
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1">Alcohol Test Result</h3>
|
||||
<p className="text-sm text-gray-900">{inv.alcohol_test_result}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Witness refs */}
|
||||
{inv.witness_statement_refs && inv.witness_statement_refs.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1">Witness Statements</h3>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{inv.witness_statement_refs.map((ref, i) => (
|
||||
<li key={i} className="text-sm text-gray-900">{ref}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user