- 사고별 이미지 분석 API 및 항공 미디어 조회 연동 - 사고 마커 팝업 디자인 개선, 필터링된 사고만 지도 표시 - 이미지 분석 시 사고명 파라미터 지원, 기본 예측시간 6시간으로 변경 - 유출량 정밀도 NUMERIC(14,10) 확대 (migration 031) - OpenDrift 유종 매핑 수정 (원유, 등유)
245 lines
6.3 KiB
TypeScript
245 lines
6.3 KiB
TypeScript
import { api } from '@common/services/api';
|
|
|
|
// ============================================================
|
|
// 백엔드 API 응답 타입
|
|
// ============================================================
|
|
|
|
export interface IncidentListItem {
|
|
acdntSn: number;
|
|
acdntCd: string;
|
|
acdntNm: string;
|
|
acdntTpCd: string;
|
|
acdntSttsCd: string;
|
|
lat: number;
|
|
lng: number;
|
|
locDc: string;
|
|
occrnDtm: string;
|
|
regionNm: string;
|
|
officeNm: string;
|
|
svrtCd: string | null;
|
|
vesselTp: string | null;
|
|
phaseCd: string;
|
|
analystNm: string | null;
|
|
oilTpCd: string | null;
|
|
spilQty: number | null;
|
|
spilUnitCd: string | null;
|
|
fcstHr: number | null;
|
|
hasPredCompleted: boolean;
|
|
mediaCnt: number;
|
|
hasImgAnalysis: boolean;
|
|
}
|
|
|
|
export interface PredExecItem {
|
|
predExecSn: number;
|
|
algoCd: string;
|
|
execSttsCd: string;
|
|
bgngDtm: string | null;
|
|
cmplDtm: string | null;
|
|
reqdSec: number | null;
|
|
}
|
|
|
|
export interface WeatherInfo {
|
|
locNm: string;
|
|
obsDtm: string;
|
|
icon: string;
|
|
temp: string;
|
|
weatherDc: string;
|
|
wind: string;
|
|
wave: string;
|
|
humid: string;
|
|
vis: string;
|
|
sst: string;
|
|
tide: string;
|
|
highTide: string;
|
|
lowTide: string;
|
|
forecast: Array<{ hour: string; icon: string; temp: string }>;
|
|
impactDc: string;
|
|
}
|
|
|
|
export interface MediaInfo {
|
|
photoCnt: number;
|
|
videoCnt: number;
|
|
satCnt: number;
|
|
cctvCnt: number;
|
|
photoMeta: Record<string, unknown> | null;
|
|
droneMeta: Record<string, unknown> | null;
|
|
satMeta: Record<string, unknown> | null;
|
|
cctvMeta: Record<string, unknown> | null;
|
|
}
|
|
|
|
export interface IncidentDetail extends IncidentListItem {
|
|
predictions: PredExecItem[];
|
|
weather: WeatherInfo | null;
|
|
media: MediaInfo | null;
|
|
}
|
|
|
|
// ============================================================
|
|
// 프론트 호환 타입
|
|
// ============================================================
|
|
|
|
export interface IncidentCompat {
|
|
id: string;
|
|
name: string;
|
|
status: 'active' | 'investigating' | 'closed';
|
|
date: string;
|
|
time: string;
|
|
region: string;
|
|
office: string;
|
|
location: { lat: number; lon: number };
|
|
causeType?: string;
|
|
oilType?: string;
|
|
prediction?: string;
|
|
vesselName?: string;
|
|
mediaCount?: number;
|
|
hasImgAnalysis?: boolean;
|
|
}
|
|
|
|
function toCompat(item: IncidentListItem): IncidentCompat {
|
|
const dt = new Date(item.occrnDtm);
|
|
const statusMap: Record<string, 'active' | 'investigating' | 'closed'> = {
|
|
ACTIVE: 'active',
|
|
INVESTIGATING: 'investigating',
|
|
CLOSED: 'closed',
|
|
};
|
|
return {
|
|
id: String(item.acdntSn),
|
|
name: item.acdntNm,
|
|
status: statusMap[item.acdntSttsCd] ?? 'active',
|
|
date: dt.toISOString().slice(0, 10),
|
|
time: dt.toTimeString().slice(0, 5),
|
|
region: item.regionNm,
|
|
office: item.officeNm,
|
|
location: { lat: item.lat, lon: item.lng },
|
|
causeType: item.acdntTpCd,
|
|
oilType: item.oilTpCd ?? undefined,
|
|
prediction: item.hasPredCompleted ? '예측완료' : undefined,
|
|
mediaCount: item.mediaCnt,
|
|
hasImgAnalysis: item.hasImgAnalysis || undefined,
|
|
};
|
|
}
|
|
|
|
// ============================================================
|
|
// API 호출 함수
|
|
// ============================================================
|
|
|
|
export async function fetchIncidentsRaw(): Promise<IncidentListItem[]> {
|
|
const { data } = await api.get<IncidentListItem[]>('/incidents');
|
|
return data;
|
|
}
|
|
|
|
export async function fetchIncidents(filters?: {
|
|
status?: string;
|
|
region?: string;
|
|
search?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
}): Promise<IncidentCompat[]> {
|
|
const params = new URLSearchParams();
|
|
if (filters?.status) params.set('status', filters.status);
|
|
if (filters?.region) params.set('region', filters.region);
|
|
if (filters?.search) params.set('search', filters.search);
|
|
if (filters?.startDate) params.set('startDate', filters.startDate);
|
|
if (filters?.endDate) params.set('endDate', filters.endDate);
|
|
|
|
const query = params.toString();
|
|
const url = query ? `/incidents?${query}` : '/incidents';
|
|
const { data } = await api.get<IncidentListItem[]>(url);
|
|
return data.map(toCompat);
|
|
}
|
|
|
|
export async function fetchIncidentDetail(sn: number): Promise<IncidentDetail> {
|
|
const { data } = await api.get<IncidentDetail>(`/incidents/${sn}`);
|
|
return data;
|
|
}
|
|
|
|
export async function fetchIncidentWeather(sn: number): Promise<WeatherInfo | null> {
|
|
try {
|
|
const { data } = await api.get<WeatherInfo>(`/incidents/${sn}/weather`);
|
|
return data;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function fetchIncidentMedia(sn: number): Promise<MediaInfo | null> {
|
|
try {
|
|
const { data } = await api.get<MediaInfo>(`/incidents/${sn}/media`);
|
|
return data;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function fetchIncidentPredictions(sn: number): Promise<PredExecItem[]> {
|
|
const { data } = await api.get<PredExecItem[]>(`/incidents/${sn}/predictions`);
|
|
return data;
|
|
}
|
|
|
|
export interface NearbyOrgItem {
|
|
orgSn: number;
|
|
orgTp: string;
|
|
jrsdNm: string;
|
|
areaNm: string;
|
|
orgNm: string;
|
|
addr: string;
|
|
tel: string;
|
|
lat: number;
|
|
lng: number;
|
|
pinSize: string;
|
|
vesselCnt: number;
|
|
skimmerCnt: number;
|
|
pumpCnt: number;
|
|
vehicleCnt: number;
|
|
sprayerCnt: number;
|
|
totalAssets: number;
|
|
distanceNm: number;
|
|
}
|
|
|
|
export async function fetchNearbyOrgs(
|
|
lat: number,
|
|
lng: number,
|
|
radiusNm: number,
|
|
): Promise<NearbyOrgItem[]> {
|
|
const { data } = await api.get<NearbyOrgItem[]>('/assets/orgs/nearby', {
|
|
params: { lat, lng, radius: radiusNm },
|
|
});
|
|
return data;
|
|
}
|
|
|
|
// ============================================================
|
|
// 사고 관련 이미지 (AERIAL_MEDIA)
|
|
// ============================================================
|
|
|
|
export interface AerialMediaItem {
|
|
aerialMediaSn: number;
|
|
acdntSn: number | null;
|
|
fileNm: string;
|
|
orgnlNm: string | null;
|
|
filePath: string | null;
|
|
lon: number | null;
|
|
lat: number | null;
|
|
locDc: string | null;
|
|
equipTpCd: string | null;
|
|
equipNm: string | null;
|
|
mediaTpCd: string | null;
|
|
takngDtm: string | null;
|
|
fileSz: string | null;
|
|
resolution: string | null;
|
|
regDtm: string;
|
|
}
|
|
|
|
export async function fetchIncidentAerialMedia(acdntSn: number): Promise<AerialMediaItem[]> {
|
|
try {
|
|
const { data } = await api.get<AerialMediaItem[]>('/aerial/media', {
|
|
params: { acdntSn },
|
|
});
|
|
return data;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function getMediaImageUrl(aerialMediaSn: number): string {
|
|
return `/api/aerial/media/${aerialMediaSn}/download`;
|
|
}
|