wing-ops/frontend/src/tabs/incidents/components/IncidentTable.tsx
htlee 46c7307ab9 feat(incidents): 사고관리 탭 mock → DB/API 전환
- DB: ACDNT, SPIL_DATA, PRED_EXEC, ACDNT_WEATHER, ACDNT_MEDIA 5개 테이블 생성
- 시드: 사고 12건, 유출정보 12건, 예측실행 18건, 기상 6건, 미디어 6건
- 백엔드: incidentsService + incidentsRouter (사고 목록/상세/예측/기상/미디어 5개 API)
- 프론트: IncidentsView, IncidentTable, IncidentsLeftPanel, MediaModal mock → API 전환
- mockIncidents, WEATHER_DATA, MEDIA_DATA 3개 mock 완전 제거
- SECTION_DATA, MOCK_SENSITIVE, mockVessels는 별도 도메인으로 유지

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:20:37 +09:00

99 lines
4.9 KiB
TypeScript
Executable File

import { useState, useEffect } from 'react'
import type { IncidentListItem } from '../services/incidentsApi'
import { fetchIncidentsRaw } from '../services/incidentsApi'
export function IncidentTable() {
const [incidents, setIncidents] = useState<IncidentListItem[]>([])
const [searchTerm, setSearchTerm] = useState('')
useEffect(() => {
fetchIncidentsRaw()
.then(setIncidents)
.catch(() => setIncidents([]))
}, []);
const filteredIncidents = incidents.filter((inc) => {
if (!searchTerm) return true;
return inc.acdntNm.toLowerCase().includes(searchTerm.toLowerCase());
});
return (
<div className="flex flex-col h-full bg-bg-0">
{/* 헤더 */}
<div className="flex items-center justify-between px-5 py-4 border-b border-border">
<div>
<h1 className="text-xl font-bold text-text-1"> </h1>
<p className="text-sm text-text-3 mt-1"> {filteredIncidents.length}</p>
</div>
<div className="flex items-center gap-3">
<button className="px-4 py-2 text-sm font-semibold border border-border rounded-md bg-bg-3 text-text-2 hover:bg-bg-hover hover:text-text-1 transition-all">
</button>
<div className="relative">
<input
type="text"
placeholder="검색..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-64 px-4 py-2 text-sm bg-bg-2 border border-border rounded-md text-text-1 placeholder-text-3 focus:border-primary-cyan focus:outline-none"
/>
</div>
<button className="px-4 py-2 text-sm font-semibold rounded-md bg-primary-cyan text-bg-0 hover:shadow-[0_0_16px_rgba(6,182,212,0.3)] transition-all">
+
</button>
</div>
</div>
{/* 테이블 */}
<div className="flex-1 overflow-auto">
<table className="w-full">
<thead className="sticky top-0 bg-bg-1 border-b border-border z-10">
<tr>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-right text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
<th className="px-4 py-3 text-left text-xs font-bold text-text-3 uppercase tracking-wider"></th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{filteredIncidents.map((incident) => (
<tr
key={incident.acdntSn}
className="hover:bg-bg-2 transition-colors cursor-pointer group"
>
<td className="px-4 py-3 text-sm text-text-2 font-mono">{incident.acdntSn}</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-status-red animate-pulse" />
<span className="text-sm font-semibold text-text-1 group-hover:text-primary-cyan transition-colors">
{incident.acdntNm}
</span>
</div>
</td>
<td className="px-4 py-3 text-sm text-text-2 font-mono">{incident.occrnDtm}</td>
<td className="px-4 py-3 text-sm text-text-2">{incident.vesselTp ?? '—'}</td>
<td className="px-4 py-3 text-sm text-text-2">{incident.oilTpCd ?? '—'}</td>
<td className="px-4 py-3 text-sm text-text-1 font-mono text-right font-semibold">
{incident.spilQty != null ? incident.spilQty.toFixed(2) : '—'}
</td>
<td className="px-4 py-3 text-sm text-text-2">{incident.acdntTpCd}</td>
<td className="px-4 py-3">
<span className="px-2 py-1 text-xs font-semibold rounded-md bg-[rgba(168,85,247,0.15)] text-purple-400">
{incident.phaseCd}
</span>
</td>
<td className="px-4 py-3 text-sm text-text-2">{incident.analystNm ?? '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}