wing-ops/frontend/src/common/components/ui/UserManualPopup.tsx
Nan Kyung Lee d9a51d2101 feat(manual): 사용자 매뉴얼 팝업 기능 추가
- 퀵 메뉴에 '사용자 매뉴얼' 버튼 추가 (위성영상 아래)
- UserManualPopup 컴포넌트 신규 생성 (77개 화면, 8개 챕터)
- 각 화면별 스크린샷 이미지 77장 포함 (/public/manual/)
- 라이트박스 이미지 확대, 전체 열기/닫기, 챕터 네비게이션
2026-03-12 10:30:14 +09:00

1617 lines
77 KiB
TypeScript

import { useState } from 'react';
interface UserManualPopupProps {
isOpen: boolean;
onClose: () => void;
}
interface InputItem {
label: string;
type: string;
required: boolean;
desc: string;
}
interface ScreenItem {
id: string;
name: string;
menuPath: string;
imageIndex: number;
overview: string;
description?: string;
procedure?: string[];
inputs?: InputItem[];
notes?: string[];
}
interface Chapter {
id: string;
number: string;
title: string;
subtitle: string;
screens: ScreenItem[];
}
const CHAPTERS: Chapter[] = [
{
id: 'ch01',
number: '01',
title: '유출유 확산예측',
subtitle: 'Oil Spill Dispersion Prediction',
screens: [
{
id: '001',
name: '직접입력',
menuPath: '유출유확산예측 > 유출유확산분석',
imageIndex: 1,
overview:
'해양 유출유 사고 발생 시 오염원 위치와 유출 조건을 직접 입력하여 확산 범위를 예측하는 주요 분석 화면이다. KOSPS·POSEIDON·OpenDrift·앙상블의 4종 수치 모델을 선택적으로 적용할 수 있다. 실시간 기상·해양 데이터와 연계하여 즉시 예측 결과를 지도에 표출한다.',
description:
'화면 좌측에 예측정보 입력 패널(사고명, 위치, 유종, 유출량, 예측 시간)이 위치한다. 중앙 지도에서 클릭하여 사고 발생 위치를 직접 지정하거나 위·경도 좌표를 수동 입력할 수 있다. 하단 타임라인 슬라이더로 시간 경과에 따른 확산 경과를 재생할 수 있다. 우측 \'분석 요약\' 패널에서 예측 면적, 이동거리, 이동 방향, 풍화 비율 등을 확인할 수 있다.',
procedure: [
'상단 메뉴에서 \'유출유확산예측 > 유출유확산분석\'을 클릭하여 화면을 이동한다.',
'좌측 패널에서 사고명, 날짜, 유종, 유출량, 예측 시간을 입력한다.',
'지도를 클릭하거나 좌표 입력란에 위·경도를 직접 입력하여 사고 위치를 지정한다.',
'적용할 확산 모델(KOSPS·POSEIDON·OpenDrift·앙상블) 체크박스를 선택한다.',
'\'확산예측 실행\' 버튼을 클릭하여 예측을 시작한다.',
'하단 타임라인 재생 버튼으로 시간별 확산 결과를 확인한다.',
],
inputs: [
{ label: '사고명', type: '텍스트', required: true, desc: '사고 식별 명칭' },
{ label: '날짜/시간', type: '날짜+시간', required: true, desc: '사고 발생 일시' },
{ label: '위도/경도', type: '숫자', required: true, desc: '지도 클릭 자동 입력 가능' },
{ label: '유종', type: '드롭다운', required: true, desc: '벙커C유·경유·연료유 등' },
{ label: '유출량', type: '숫자 kL', required: true, desc: '유출 유류 총량' },
{ label: '예측 시간', type: '숫자 h', required: true, desc: '확산 예측 기간' },
{ label: '적용 모델', type: '체크박스', required: true, desc: 'KOSPS·POSEIDON·OpenDrift·앙상블 중 선택' },
],
notes: [
'좌표 입력 후 반드시 \'적용\' 버튼을 클릭해야 지도에 반영된다.',
'앙상블 모델 선택 시 3개 모델이 동시 실행되어 계산 시간이 증가할 수 있다.',
'유출량이 0 이하이거나 예측 시간이 입력되지 않으면 실행 버튼이 비활성화된다.',
],
},
{
id: '002',
name: '모델종류별 확산예측 실행',
menuPath: '유출유확산예측 > 유출유확산분석',
imageIndex: 2,
overview:
'KOSPS·POSEIDON·OpenDrift 3개 모델의 예측 결과를 동시에 지도에 표출하여 모델 간 비교 분석을 지원한다. 모델별 입자 궤적(파란점·빨간점·하늘점)이 중첩 표시되어 확산 경향의 편차를 한눈에 파악할 수 있다.',
description:
'지도에 모델별 색상 구분 입자가 동시에 표시되며, 각 모델의 확산 방향과 범위 차이를 시각적으로 비교할 수 있다. 우측 \'분석 요약\' 패널에 예측 면적(km2), 이동거리(km), 방향, 이동속도(cm/s)가 표출된다. 풍화 상태 막대그래프(유출량·해면연소·자연분산·수중분산·잔류 비율)를 제공한다. \'다각형 분석수행\' 버튼으로 특정 구역 내 오염 면적을 별도 산정할 수 있다.',
procedure: [
'확산분석 화면에서 적용 모델 체크박스를 2개 이상 선택한다.',
'\'확산예측 실행\' 버튼을 클릭한다.',
'지도에서 각 모델의 입자 분포와 확산 범위를 비교한다.',
'우측 \'분석 요약\' 탭에서 모델별 정량 지표를 확인한다.',
'하단 타임라인으로 시간 경과별 확산 변화를 재생한다.',
],
notes: [
'여러 모델을 동시에 선택하면 계산 자원 소모가 증가하여 결과 표출까지 수 분이 소요될 수 있다.',
'모델별 예측 결과에 차이가 있을 경우, 앙상블 결과 또는 현장 데이터와 교차 검토하여 활용한다.',
],
},
{
id: '003',
name: '사고정보 조회',
menuPath: '유출유확산예측 > 유출유확산분석',
imageIndex: 3,
overview:
'시스템에 등록된 실제 사고 정보를 불러와 확산분석에 자동 연동하는 기능이다. 사고코드·선박명·유종·유출량·발생 좌표 등이 자동으로 예측정보 입력란에 채워진다.',
description:
'좌측 \'사고정보\' 패널에 진행 중인 사고 목록이 표시된다. 사고를 선택하면 지도 중심이 해당 사고 발생 위치로 자동 이동하고 위치 핀이 표시된다. 사고 상태는 \'진행중(빨간 배지)\'과 \'종료(회색 배지)\'로 구분된다.',
procedure: [
'좌측 \'사고정보\' 패널의 펼침 버튼을 클릭하여 패널을 연다.',
'목록에서 분석할 사고를 클릭한다.',
'사고 정보가 예측정보 입력란에 자동으로 입력되었는지 확인한다.',
'필요 시 유출량·예측 시간 등 추가 정보를 수정한다.',
'\'확산예측 실행\'을 클릭하여 분석을 시작한다.',
],
notes: [
'\'진행중\' 상태의 사고만 실시간 확산예측과 연동된다.',
'사고 데이터는 관계 기관으로부터 등록된 정보를 기반으로 하며, 입력 오류 시 관리자에게 문의한다.',
],
},
{
id: '004',
name: '영향 민감자원 레이어 중첩 표시',
menuPath: '유출유확산예측 > 유출유확산분석',
imageIndex: 4,
overview:
'유출유 확산 예측 결과와 해양 민감자원(환경생태·수산자원·관광지 등) 레이어를 지도에 동시 표출하는 기능이다. 잠재적 피해 자원을 사전에 파악하여 방제 우선순위 설정에 활용한다.',
description:
'좌측 하단 \'정보 레이어\' 패널에서 환경생태·사회경제·민감도평가·해경관할구역 등 대분류 토글을 제공한다. 각 항목 우측에 해당 레이어 데이터 건수가 표시된다. \'전체 켜기/끄기\' 버튼으로 모든 레이어를 일괄 제어할 수 있다.',
procedure: [
'좌측 하단 \'정보 레이어\' 패널을 열고 원하는 레이어 항목을 활성화한다.',
'확산예측 실행 후 지도에 표출된 오염 범위와 레이어 분포를 비교한다.',
'오염 영향이 우려되는 자원 레이어를 클릭하여 상세 정보를 확인한다.',
'필요한 레이어 조합을 선택하여 보고서용 지도 캡처에 활용한다.',
],
notes: [
'레이어 수가 많을 경우 지도 로딩 속도가 느려질 수 있으므로 필요한 레이어만 선택적으로 활성화한다.',
],
},
{
id: '005',
name: 'AI자동추천 배치안 적용',
menuPath: '유출유확산예측 > 유출유확산분석 > 오일펜스 배치 가이드',
imageIndex: 5,
overview:
'확산예측 결과를 기반으로 AI(NSGA-II 알고리즘)가 최적 오일펜스 배치안을 자동으로 생성하는 기능이다. 최대 3개 방어선의 위치·방향·총 길이·차단율을 자동 계산하여 지도에 표출한다.',
description:
'\'오일펜스 배치 가이드\' 패널 상단 \'AI자동 추천\' 탭에서 배치 결과를 확인한다. 1차~3차 방어선별 배치 위치(좌표), 방향, 오일펜스 길이, 예상 차단율이 표시된다.',
procedure: [
'확산예측 실행 완료 후 좌측 \'오일펜스 배치 가이드\' 패널을 연다.',
'\'AI자동 추천\' 탭을 선택하여 추천 배치안을 확인한다.',
'방어선별 차단율 및 오일펜스 총 길이를 검토한다.',
'\'추천 배치안 적용하기\' 버튼을 클릭하여 지도에 표출한다.',
'현장 여건(조류·지형·자원 현황)을 고려하여 최종 배치안을 확정한다.',
],
inputs: [
{ label: '배치 수', type: '숫자', required: false, desc: '투입할 오일펜스 방어선 수' },
{ label: '최소 차단율', type: '숫자 %', required: false, desc: '목표 차단율' },
],
notes: [
'AI 추천 기능은 확산예측이 완료된 후에 활성화된다.',
'추천 배치안은 수치 모델 기반 결과로, 현장 조류 변동·수심·접근 가능성 등을 반드시 현장 담당자가 최종 확인해야 한다.',
],
},
{
id: '006',
name: '이미지업로드',
menuPath: '유출유확산예측 > 유출유확산분석',
imageIndex: 6,
overview:
'위성·드론·항공기에서 촬영한 이미지를 업로드하여 실제 오염 현황과 모델 예측 결과를 비교 분석하는 기능이다.',
description:
'예측정보 입력 패널 상단 \'이미지 업로드\' 탭을 선택하면 파일 업로드 영역이 활성화된다. 업로드된 이미지는 지도에 자동으로 오버레이 표출된다.',
procedure: [
'예측정보 입력 패널에서 \'이미지 업로드\' 탭을 선택한다.',
'\'파일 선택\' 버튼을 클릭하거나 파일을 드래그하여 업로드한다.',
'업로드된 이미지가 지도에 올바르게 표출되는지 위치를 확인한다.',
'확산예측 결과와 이미지를 동시에 표출하여 비교한다.',
],
inputs: [
{ label: '업로드 파일', type: '파일 PNG/JPG', required: false, desc: '위성·드론·항공기 촬영 이미지' },
],
notes: [
'지원 파일 형식은 PNG, JPG이다.',
'업로드한 이미지의 좌표 기준(GCP)이 없을 경우 지도 상 위치 정확도가 낮을 수 있다.',
],
},
{
id: '007',
name: '분석 목록',
menuPath: '유출유확산예측',
imageIndex: 7,
overview:
'완료 또는 진행 중인 유출유 확산예측 분석 이력을 목록 형식으로 조회하는 화면이다. 사고명·날짜·유종·유출량·모델 상태 등을 한눈에 파악할 수 있다.',
description:
'목록에는 번호·사고명·사고일시·예측시간·유종·유출량·모델별 상태(KOSPS·POSEIDON·OPENDRIFT)·담당자 등이 표시된다. 모델 상태는 \'완료(녹색 배지)\'와 \'대기(회색 배지)\'로 구분된다.',
procedure: [
'상단 메뉴에서 \'유출유확산예측\'을 클릭하여 분석 목록 화면으로 이동한다.',
'검색창에 사고명을 입력하거나 페이지를 이동하여 원하는 분석을 찾는다.',
'사고명 링크를 클릭하여 해당 분석 결과 상세 화면으로 이동한다.',
'신규 분석이 필요한 경우 우측 상단 \'+ 새 분석\' 버튼을 클릭한다.',
],
notes: [
'분석 결과 삭제 시 복구가 불가하므로, 삭제 전 보고서 출력 또는 데이터 저장 여부를 확인한다.',
],
},
{
id: '008',
name: '분석 조회(test001)',
menuPath: '유출유확산예측 > 분석 목록',
imageIndex: 8,
overview:
'분석 목록에서 특정 분석 건을 선택하면 해당 확산분석 결과 화면으로 이동하여 상세 내용을 조회한다. 이전 분석 결과를 재검토하거나 조건 변경 후 재분석할 수 있다.',
procedure: [
'분석 목록에서 조회할 사고명 링크를 클릭한다.',
'로드된 분석 정보를 확인한다.',
'필요 시 입력 조건을 수정하고 \'확산예측 실행\'을 다시 클릭하여 재분석한다.',
'결과를 보고서로 출력하거나 저장한다.',
],
notes: [
'기존 분석에서 모델 조건 변경 후 재실행 시 이전 결과가 덮어써질 수 있으므로, 원본 결과를 먼저 저장한다.',
],
},
{
id: '009',
name: '유출유확산모델 이론 - 시스템 개요',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 9,
overview:
'Wing 시스템에 탑재된 유출유 확산 수치 모델의 개요와 운용 체계를 안내하는 이론 화면이다. KOSPS·POSEIDON·OpenDrift 3종 모델의 특징·비교·데이터 흐름을 확인할 수 있다.',
procedure: [
'상단 메뉴에서 \'유출유확산예측 > 유출유확산모델 이론\'을 클릭한다.',
'\'시스템 개요\' 탭을 선택하여 전체 모델 체계를 확인한다.',
'각 탭(KOSPS·POSEIDON·OpenDrift 등)을 클릭하여 상세 이론을 열람한다.',
],
},
{
id: '010',
name: '유출유확산모델 이론 - KOSPS',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 10,
overview:
'한국해양과학기술원(KIOST)이 개발한 KOSPS 모델의 상세 이론을 안내한다. 국내 연안 특성에 최적화된 조류예측(CHARRY) 및 풍류 경험식 적용 원리를 설명한다.',
notes: [
'KOSPS는 국내 연안 중심으로 검증된 모델로, 원해·심해 적용 시 정확도 제한이 있을 수 있다.',
],
},
{
id: '011',
name: '유출유확산모델 이론 - POSEIDON',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 11,
overview:
'입자추적 최적화 예측 시스템 POSEIDON의 이론 및 MOHID 3D 해양순환모델 적용 원리를 안내한다. GA·DE·HS·PSO 등 4종 최적화 알고리즘을 통한 파라미터 자동 최적화 방법을 설명한다.',
},
{
id: '012',
name: '유출유확산모델 이론 - OpenDrift',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 12,
overview:
'노르웨이 MET Norway가 개발한 오픈소스 라그랑지안 확산 프레임워크 OpenDrift의 이론을 안내한다. NEMO·ROMS·HYCOM·Copernicus CMEMS 등 다양한 해양 예보 모델과 연동 가능한 범용성을 설명한다.',
},
{
id: '013',
name: '유출유확산모델 이론 - 입자추적법',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 13,
overview:
'유출유 수치 모델에 적용되는 라그랑지안 입자추적법(Lagrangian Particle Tracking Method)의 수학적 이론과 수식을 안내한다.',
},
{
id: '014',
name: '유출유확산모델 이론 - 풍화 프로세스',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 14,
overview:
'유출유가 시간 경과에 따라 겪는 풍화(Weathering) 프로세스(증발·유화·자연분산 등)의 이론과 타임라인을 안내한다.',
notes: [
'풍화 속도는 유종·기온·해수온·파랑 조건에 따라 크게 달라질 수 있으므로 참고용으로 활용한다.',
],
},
{
id: '015',
name: '유출유확산모델 이론 - 해양환경 입력',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 15,
overview:
'유출유 확산 수치 모델에 사용되는 해양환경 입력 데이터(기상·해류·조류·해수면 온도) 체계를 안내한다. KMA RDAPS·ECMWF·NIFS ROMS·HYCOM·TPXO9 등 데이터 소스 설명.',
},
{
id: '016',
name: '유출유확산모델 이론 - 모델 검증',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 16,
overview:
'유출유 확산 모델의 정확도 검증 사례(2007 허베이스피리트, 2014 무이산 등)와 RMSE·Skill Score 통계 지표를 안내한다.',
},
{
id: '017',
name: '유출유확산모델 이론 - 앙상블',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 17,
overview:
'KOSPS·POSEIDON·OpenDrift 3종 모델의 결과를 통합하는 앙상블 예측 방법론과 가중평균 알고리즘을 안내한다. 최악 시나리오(Worst Case) 산출 방법도 설명한다.',
},
{
id: '018',
name: '유출유확산모델 이론 - 발전 방향',
menuPath: '유출유확산예측 > 유출유확산모델 이론',
imageIndex: 18,
overview:
'현재 유출유 확산 모델의 한계와 향후 개선 방향(4단계 로드맵)을 안내한다.',
},
{
id: '019',
name: '오일펜스 배치 알고리즘 이론 - 개요',
menuPath: '유출유확산예측 > 오일펜스 배치 알고리즘 이론',
imageIndex: 19,
overview:
'오일펜스 최적 배치 알고리즘의 전체 개요와 최적화 목표(차단 면적 최대화·도달시간 최소화·자원 효율화)를 안내한다.',
},
{
id: '020',
name: '오일펜스 배치 알고리즘 이론 - 배치 이론',
menuPath: '유출유확산예측 > 오일펜스 배치 알고리즘 이론',
imageIndex: 20,
overview:
'차단 효율 함수(E(theta, U))와 V형·U형·J형 배치 형태별 이론적 차단 원리를 안내한다. 다단계 차단선 배치 시 총 차단 효율 산출 공식도 포함한다.',
},
{
id: '021',
name: '오일펜스 배치 알고리즘 이론 - 최적화 알고리즘',
menuPath: '유출유확산예측 > 오일펜스 배치 알고리즘 이론',
imageIndex: 21,
overview:
'NSGA-II(Non-dominated Sorting Genetic Algorithm II) 기반 다목적 최적화 알고리즘의 원리와 보조 알고리즘(PSO·Greedy) 비교를 안내한다.',
},
{
id: '022',
name: '오일펜스 배치 알고리즘 이론 - 유체역학 모델',
menuPath: '유출유확산예측 > 오일펜스 배치 알고리즘 이론',
imageIndex: 22,
overview:
'오일펜스 차단 성능 평가에 사용되는 유체역학 모델의 이론을 안내한다.',
},
{
id: '023',
name: '오일펜스 배치 알고리즘 이론 - 현장 적용',
menuPath: '유출유확산예측 > 오일펜스 배치 알고리즘 이론',
imageIndex: 23,
overview:
'오일펜스 배치 알고리즘의 실제 사고 현장 적용 사례와 현장 운용 시 고려사항을 안내한다.',
},
{
id: '024',
name: '오일펜스 배치 알고리즘 이론 - 참고문헌',
menuPath: '유출유확산예측 > 오일펜스 배치 알고리즘 이론',
imageIndex: 24,
overview:
'오일펜스 배치 알고리즘 이론에 사용된 학술 논문·기술 보고서·국제 기준 등 참고문헌 목록을 안내한다.',
},
],
},
{
id: 'ch02',
number: '02',
title: 'HNS·대기확산',
subtitle: 'HNS Atmospheric Dispersion',
screens: [
{
id: '025',
name: '대기확산예측 실행',
menuPath: 'HNS대기확산 > 대기확산분석',
imageIndex: 25,
overview:
'HNS(위험유해물질) 해상 유출 시 대기 확산 범위, 위험 농도 구역, 영향 인구를 예측하는 핵심 분석 화면이다. ALOHA(EPA) 또는 이문진박사모델 두 가지 알고리즘 중 선택하여 가우시안 Plume/Puff 모델을 적용한다.',
description:
'화면 좌측에 사고 기본정보·물질 및 유출 조건·기상조건 입력 패널이 위치한다. 중앙 지도에 AEGL-1~3 위험 구역이 색상별 Plume 형태로 표출된다. 우측 \'예측 결과\' 패널에 최대 농도(ppm), AEGL-1 영향 면적, 위험 등급 등이 표시된다.',
procedure: [
'상단 메뉴에서 \'HNS대기확산 > 대기확산분석\'을 클릭한다.',
'사고 기본정보(사고명·날짜·시간·위치)를 입력한다.',
'HNS 물질 종류 및 누출 방식·유출량을 선택·입력한다.',
'기상조건(풍향·풍속·기온·대기안정도·예측 시간)을 입력한다.',
'적용 알고리즘(ALOHA/이문진박사모델)을 선택한다.',
'\'확산예측 실행\' 버튼을 클릭한다.',
'지도의 위험 구역 Plume과 우측 예측 결과 패널을 확인한다.',
],
inputs: [
{ label: '사고명', type: '텍스트', required: true, desc: '사고 식별 명칭' },
{ label: '사고 일시', type: '날짜+시간', required: true, desc: '사고 발생 일시' },
{ label: '위도/경도', type: '숫자', required: true, desc: '사고 발생 지점 좌표' },
{ label: 'HNS 물질', type: '드롭다운', required: true, desc: 'AEGL/ERPG 기준값 자동 로드' },
{ label: '누출 방식', type: '라디오', required: true, desc: '순간 유출 또는 지속 유출' },
{ label: '유출량', type: '숫자', required: true, desc: 'ton 또는 g/s 단위' },
{ label: '풍향', type: '숫자', required: true, desc: '0~360도' },
{ label: '풍속', type: '숫자 m/s', required: true, desc: '최소 0.5 m/s 이상' },
{ label: '기온', type: '숫자', required: true, desc: '현재 기온' },
{ label: '대기안정도', type: '선택 A~F', required: true, desc: 'Pasquill-Gifford 분류' },
{ label: '예측 시간', type: '숫자 h', required: true, desc: '확산 예측 기간' },
{ label: '적용 알고리즘', type: '라디오', required: true, desc: 'ALOHA 또는 이문진박사모델' },
],
notes: [
'풍속은 최소 0.5 m/s 이상 입력해야 하며 0 입력 시 오류 발생.',
'HNS 물질 선택 후 AEGL/ERPGs 기준값이 자동 로드되었는지 반드시 확인.',
],
},
{
id: '026',
name: 'HNS 분석 목록',
menuPath: 'HNS대기확산',
imageIndex: 26,
overview:
'완료된 HNS 대기확산 분석 이력을 목록 형식으로 조회하는 화면이다. AEGL-1~3 거리·위험 등급·피해반경·담당자 등 주요 결과 정보를 한눈에 파악할 수 있다.',
procedure: [
'상단 메뉴에서 \'HNS대기확산\'을 클릭하여 분석 목록으로 이동한다.',
'검색창에서 분석명을 검색하거나 목록을 스크롤하여 원하는 분석을 찾는다.',
'분석명 링크를 클릭하여 해당 분석 결과 지도 화면으로 이동한다.',
],
},
{
id: '027',
name: '시나리오 상세',
menuPath: 'HNS대기확산 > 시나리오 관리',
imageIndex: 27,
overview:
'단일 HNS 사고에 대해 여러 기상·유출 조건 시나리오(S-01~S-05 등)를 생성·비교 관리하는 화면이다. 시나리오별 위험도·위험 구역·대응 권고사항을 한눈에 비교할 수 있다.',
description:
'시나리오 목록 카드에 시나리오명·위험도 배지(CRITICAL/HIGH/MEDIUM/RESOLVED)·최대농도·IDLH 거리·ERPG-2 거리·영향인구 요약이 표시된다.',
procedure: [
'\'HNS대기확산 > 시나리오 관리\'를 클릭하여 이동한다.',
'원하는 사고 건을 선택하면 시나리오 목록이 표시된다.',
'시나리오 카드를 클릭하여 상세 위험 구역과 대응 권고사항을 확인한다.',
'\'비교 차트\' 탭으로 이동하여 시나리오 간 시간별 변화를 비교한다.',
],
},
{
id: '028',
name: '비교 차트',
menuPath: 'HNS대기확산 > 시나리오 관리',
imageIndex: 28,
overview:
'생성된 여러 HNS 시나리오의 최대 지표면 농도·위험 반경·영향 인구를 시간별 변화 차트로 비교하는 화면이다.',
description:
'상단은 시나리오별 최대 지표면 농도(ppm) 시간별 변화 그래프. 중단에 위험 반경(km)과 영향 인구(명) 변화 차트. 하단에 피대농도·IDLH 거리·ERPG-2 반경·영향인구·풍향/풍속·위험등급 항목별 비교표가 제공된다.',
},
{
id: '029',
name: '확산범위 오버레이',
menuPath: 'HNS대기확산 > 시나리오 관리',
imageIndex: 29,
overview:
'HNS 대기확산 시나리오의 시간대별(T+0h~T+4h) 확산 범위를 지도에 오버레이 표출하는 화면이다.',
procedure: [
'시나리오 관리 화면에서 \'확산범위 오버레이\' 탭을 클릭한다.',
'지도에서 시간대별 확산 범위를 확인한다.',
'슬라이더를 이동하여 원하는 시간대의 확산 상태를 조회한다.',
],
},
{
id: '030',
name: '신규 시나리오',
menuPath: 'HNS대기확산 > 시나리오 관리',
imageIndex: 30,
overview:
'기상·유출 조건을 변경하여 새로운 HNS 대기확산 시나리오를 생성하는 입력 모달 화면이다.',
procedure: [
'시나리오 관리 화면 우측 상단 \'신규 시나리오\' 버튼을 클릭한다.',
'시나리오명과 기준 시각을 입력한다.',
'HNS 물질·누출 구분·유출량을 입력한다.',
'기상조건(풍향·풍속·기온·대기안정도)을 입력한다.',
'\'시나리오 생성 및 예측 실행\'을 클릭한다.',
],
inputs: [
{ label: '시나리오명', type: '텍스트', required: true, desc: '시나리오 식별 명칭' },
{ label: 'HNS 물질', type: '드롭다운', required: true, desc: '유출 물질 선택' },
{ label: '누출 구분', type: '라디오', required: true, desc: '순간/지속' },
{ label: '유출량', type: '숫자', required: true, desc: 'ton 또는 g/s' },
{ label: '풍향/풍속', type: '숫자', required: true, desc: '각도와 m/s' },
{ label: '기온', type: '숫자', required: true, desc: '섭씨' },
{ label: '대기안정도', type: '선택 A~F', required: true, desc: 'Pasquill-Gifford' },
],
},
{
id: '031',
name: 'HNS 대응매뉴얼',
menuPath: 'HNS대기확산',
imageIndex: 31,
overview:
'해양 HNS 사고 대응 절차를 정리한 Marine HNS Response Manual(Bonn Agreement·HELCOM·REMPEC 기반)을 시스템 내에서 직접 열람하는 화면이다. 8개 챕터. SEBC 거동 분류 5유형 카드.',
procedure: [
'상단 메뉴에서 \'HNS대기확산 > HNS 대응매뉴얼\'을 클릭한다.',
'원하는 챕터 카드를 클릭하여 세부 내용을 확인한다.',
'하단 SEBC 분류 카드를 클릭하여 거동 유형별 대응 방법을 확인한다.',
],
},
{
id: '032',
name: 'HNS 확산모델 이론 - 시스템 개요',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 32,
overview:
'Wing에 탑재된 HNS 대기확산 모델(WRF-Chem·Gaussian Plume/Puff·ROMS 연동)의 전체 체계와 처리 흐름을 안내한다.',
},
{
id: '033',
name: 'HNS 확산모델 이론 - 가우시안 모델',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 33,
overview:
'HNS 대기확산 예측에 적용되는 Gaussian Plume·Puff 모델의 수학적 이론과 Pasquill-Gifford 대기안정도 분류 체계를 안내한다.',
},
{
id: '034',
name: 'HNS 확산모델 이론 - 물질별 시나리오',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 34,
overview:
'주요 HNS 물질(NH3·CH3OH·H2·LNG 등)의 물질 특성 및 대기확산 시나리오 적용 기준을 안내한다.',
},
{
id: '035',
name: 'HNS 확산모델 이론 - 해양환경 보정',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 35,
overview:
'해양 환경 특성(해풍·육풍 순환·해수면 거칠기·SST 영향·MABL 구조)에 따른 대기확산 모델 보정 인자를 안내한다.',
},
{
id: '036',
name: 'HNS 확산모델 이론 - 모델 검증',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 36,
overview:
'HNS 대기확산 모델의 실측값 대비 예측 정확도 검증 결과(ALOHA·이문진박사모델·실측값 3종 비교)를 안내한다.',
},
{
id: '037',
name: 'HNS 확산모델 이론 - 실시간 비교',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 37,
overview:
'동일 조건에서 ALOHA와 이문진박사모델의 예측 결과를 실시간으로 비교하는 시뮬레이션 화면이다.',
},
{
id: '038',
name: 'HNS 확산모델 이론 - WRF-Chem 발전',
menuPath: 'HNS대기확산 > 확산모델 이론',
imageIndex: 38,
overview:
'WRF-Chem 기상-화학 연계 모델 및 ROMS 해양확산 수치모의 검증 결과와 고도화 방향을 안내한다.',
},
{
id: '039',
name: 'HNS물질정보 - SEBC 거동분류',
menuPath: 'HNS대기확산 > HNS물질정보',
imageIndex: 39,
overview:
'HNS 물질의 해양 거동 특성을 SEBC 기준으로 분류한 정보를 제공한다. G(기체)·E(증발)·F(부유)·D(용해)·S(침강) 5가지 기본 유형과 복합 거동 유형을 안내한다.',
},
{
id: '040',
name: 'HNS물질정보 - 주요 물질 특성',
menuPath: 'HNS대기확산 > HNS물질정보',
imageIndex: 40,
overview:
'NH3·CH4·H2·페놀·톨루엔 등 주요 HNS 물질의 상세 물리화학적 특성을 카드 형식으로 제공하는 화면이다.',
},
{
id: '041',
name: 'HNS물질정보 - 위험도 기준',
menuPath: 'HNS대기확산 > HNS물질정보',
imageIndex: 41,
overview:
'HNS 물질의 위험도 평가에 사용되는 AEGL·ERPG·IDLH·TWA 등 주요 독성 기준값 체계를 안내한다.',
},
{
id: '042',
name: 'HNS물질정보 - 물질 상세검색',
menuPath: 'HNS대기확산 > HNS물질정보',
imageIndex: 42,
overview:
'HNS 6,500여 종의 물질 데이터베이스에서 CAS 번호·물질명·거동 분류 조건으로 물질을 검색하는 화면이다.',
inputs: [
{ label: '검색어', type: '텍스트', required: true, desc: 'CAS 번호 또는 물질명' },
{ label: '거동 분류', type: '체크박스', required: false, desc: 'SEBC 유형 필터' },
],
notes: [
'CAS 번호 입력 시 하이픈(-) 포함 정확한 형식으로 입력해야 검색된다.',
],
},
],
},
{
id: 'ch03',
number: '03',
title: '긴급구난',
subtitle: 'Emergency Rescue',
screens: [
{
id: '043',
name: '긴급구난예측',
menuPath: '긴급구난',
imageIndex: 43,
overview:
'해상 조난자·표류 선박·표류 물체의 위치를 예측하는 긴급구난(SAR) 분석 화면이다. 해류·바람·파랑 데이터 기반 라그랑지안 표류 궤적 시뮬레이션을 수행한다.',
description:
'화면 좌측에 사고 발생 위치·일시·표류체 종류·예측 시간·기상조건 입력 패널이 위치한다. 중앙 지도에 표류 예측 궤적과 탐색 구역(최우선·일반)이 표출된다.',
procedure: [
'상단 메뉴에서 \'긴급구난\'을 클릭한다.',
'사고 발생 위치(위경도)와 일시를 입력한다.',
'표류체 종류(선박/사람/컨테이너 등)를 선택한다.',
'예측 시간과 기상 조건을 입력한다.',
'\'예측 실행\' 버튼을 클릭하여 표류 궤적과 탐색 구역을 확인한다.',
],
inputs: [
{ label: '사고 위치', type: '숫자', required: true, desc: '위·경도' },
{ label: '사고 일시', type: '날짜+시간', required: true, desc: '발생 일시' },
{ label: '표류체 종류', type: '드롭다운', required: true, desc: '선박·사람·컨테이너·구명정 등' },
{ label: '예측 시간', type: '숫자 h', required: true, desc: '표류 예측 기간' },
{ label: '기상조건', type: '숫자', required: false, desc: '미입력 시 실시간 자동 적용' },
],
notes: [
'표류체 종류 선택이 부적절한 경우 예측 정확도가 낮아질 수 있다.',
'신속한 대응을 위해 입력 완료 즉시 실행하고 기상 변화 시 재분석을 수행한다.',
],
},
{
id: '044',
name: '긴급구난 목록',
menuPath: '긴급구난',
imageIndex: 44,
overview:
'완료 또는 진행 중인 긴급구난 예측 분석 이력을 목록 형식으로 조회하는 화면이다.',
procedure: [
'상단 메뉴에서 \'긴급구난\'을 클릭하면 목록 화면이 표시된다.',
'원하는 분석을 검색하거나 목록에서 선택한다.',
'사고명 링크를 클릭하여 상세 분석 결과로 이동한다.',
],
},
{
id: '045',
name: '시나리오 상세',
menuPath: '긴급구난 > 시나리오 관리',
imageIndex: 45,
overview:
'긴급구난 사고에 대해 기상·해양 조건을 달리 설정한 복수 시나리오를 생성·비교 관리하는 화면이다.',
procedure: [
'\'긴급구난 > 시나리오 관리\'를 클릭하여 이동한다.',
'생성된 시나리오 카드를 확인하거나 \'신규 시나리오\' 버튼으로 추가 생성한다.',
'시나리오 카드를 클릭하여 상세 내용을 확인한다.',
],
},
{
id: '046',
name: '비교 차트',
menuPath: '긴급구난 > 시나리오 관리',
imageIndex: 46,
overview:
'긴급구난 시나리오별 탐색 면적·표류 속도·최대 이동거리를 시간별 변화 차트로 비교하는 화면이다.',
},
{
id: '047',
name: '지도 오버레이',
menuPath: '긴급구난 > 시나리오 관리',
imageIndex: 47,
overview:
'긴급구난 시나리오의 시간대별 표류 예측 궤적 및 탐색 구역을 지도에 오버레이 표출하는 화면이다.',
},
{
id: '048',
name: '긴급구난모델 이론',
menuPath: '긴급구난',
imageIndex: 48,
overview:
'긴급구난 표류 예측에 적용되는 라그랑지안 표류 모델·탐색 구역(Search Area) 산출 방법·IMO SAR 기준 적용 이론을 안내한다.',
},
],
},
{
id: 'ch04',
number: '04',
title: '보고자료',
subtitle: 'Reports',
screens: [
{
id: '049',
name: '보고서 목록',
menuPath: '보고자료',
imageIndex: 49,
overview:
'시스템에서 생성된 모든 사고 대응 보고서를 목록 형식으로 관리하는 화면이다. 보고서명·사고명·생성일시·유형·작성자·상태가 표시된다.',
procedure: [
'상단 메뉴에서 \'보고자료\'를 클릭하여 보고서 목록으로 이동한다.',
'검색창에서 원하는 보고서를 검색하거나 목록에서 선택한다.',
'보고서명을 클릭하여 미리보기하거나 PDF를 다운로드한다.',
],
},
{
id: '050',
name: '초기보고서',
menuPath: '보고자료 > 표준보고서 템플릿',
imageIndex: 50,
overview:
'사고 발생 초기 지휘부 보고를 위한 표준 보고서 템플릿을 제공한다. 확산예측 결과와 사고 기본정보가 자동 연동된다.',
procedure: [
'보고자료 메뉴에서 \'표준보고서 템플릿\'을 클릭한다.',
'좌측 사이드바에서 \'초기보고서\'를 선택한다.',
'자동 입력된 항목을 검토하고 필요 내용을 추가 입력한다.',
'\'저장\' 또는 \'PDF 출력\' 버튼을 활용한다.',
],
},
{
id: '051',
name: '지휘부 보고',
menuPath: '보고자료 > 표준보고서 템플릿',
imageIndex: 51,
overview:
'사고 지휘부 보고용 표준 보고서 템플릿. 방제 대응 현황·자원 투입 현황·향후 계획이 포함된다.',
},
{
id: '052',
name: '예측보고서',
menuPath: '보고자료 > 표준보고서 템플릿',
imageIndex: 52,
overview:
'확산예측 결과를 포함한 예측보고서 템플릿. 모델 종류·예측 시간·주요 결과가 자동 연동된다.',
},
{
id: '053',
name: '종합보고서',
menuPath: '보고자료 > 표준보고서 템플릿',
imageIndex: 53,
overview:
'사고 종료 후 방제 대응 전 과정을 정리하는 종합보고서. 사고개요·대응경과·방제실적·환경영향평가·교훈이 포함된다.',
},
{
id: '054',
name: '유출유 보고',
menuPath: '보고자료 > 표준보고서 템플릿',
imageIndex: 54,
overview:
'유출유 사고 전용 표준 보고서 템플릿. 확산예측·오일펜스 배치·풍화 상태가 자동 연동된다.',
},
{
id: '055',
name: '유출유 확산예측 보고서 생성',
menuPath: '보고자료 > 보고서 생성',
imageIndex: 55,
overview:
'유출유 확산예측 분석 결과를 기반으로 보고서를 자동 생성하는 화면이다.',
procedure: [
'보고자료 메뉴에서 \'보고서 생성\'을 클릭한다.',
'\'유출유 확산예측\' 유형을 선택한다.',
'연동할 분석 결과를 드롭다운에서 선택한다.',
'보고 대상을 선택한다.',
'\'보고서 생성\' 버튼을 클릭한다.',
],
inputs: [
{ label: '분석 결과', type: '드롭다운', required: true, desc: '연동할 분석 건 선택' },
{ label: '보고 대상', type: '체크박스', required: true, desc: '지휘부·현장·외부 기관' },
],
},
{
id: '056',
name: 'HNS 대기확산 보고서 생성',
menuPath: '보고자료 > 보고서 생성',
imageIndex: 56,
overview:
'HNS 대기확산 분석 결과를 기반으로 보고서를 자동 생성하는 화면이다.',
},
{
id: '057',
name: '긴급구난 보고서 생성',
menuPath: '보고자료 > 보고서 생성',
imageIndex: 57,
overview:
'긴급구난 예측 분석 결과를 기반으로 구조 현장 지휘부용 보고서를 자동 생성하는 화면이다.',
},
],
},
{
id: 'ch05',
number: '05',
title: '항공탐색',
subtitle: 'Aerial Surveillance',
screens: [
{
id: '058',
name: '영상사진관리',
menuPath: '항공탐색',
imageIndex: 58,
overview:
'드론·항공기·위성에서 수집된 영상 및 사진을 통합 관리하는 화면이다. 촬영 일시·위치·기기 유형별 분류 조회와 유출유 면적분석 연계를 지원한다.',
procedure: [
'상단 메뉴에서 \'항공탐색 > 영상사진관리\'를 클릭한다.',
'목록에서 원하는 영상/사진을 선택하거나 지도 마커를 클릭하여 확인한다.',
'신규 파일 업로드 시 \'파일 업로드\' 버튼을 클릭한다.',
],
inputs: [
{ label: '업로드 파일', type: '파일', required: false, desc: 'JPG·PNG·GeoTIFF·KMZ' },
{ label: '촬영 일시', type: '날짜+시간', required: false, desc: '촬영 시점' },
{ label: '장비 유형', type: '드롭다운', required: false, desc: '드론·항공기·위성·CCTV' },
],
notes: [
'50MB 초과 파일은 업로드 전 압축하거나 관리자에게 문의한다.',
],
},
{
id: '059',
name: '유출유면적분석',
menuPath: '항공탐색',
imageIndex: 59,
overview:
'항공·위성 이미지를 기반으로 유출유 오염 면적을 AI 자동 산출하는 화면이다. 자동 추출된 오염 경계선을 수동 편집하여 정밀 면적을 산정할 수 있다.',
procedure: [
'영상사진관리에서 분석 대상 이미지를 선택하고 \'면적 분석\' 버튼을 클릭한다.',
'\'AI 자동 분석 실행\' 버튼을 클릭하여 오염 경계선을 추출한다.',
'폴리곤 경계선을 검토하고 필요 시 편집 도구로 수동 조정한다.',
'최종 면적·둘레·중심 좌표를 확인하고 저장한다.',
],
},
{
id: '060',
name: '실시간드론',
menuPath: '항공탐색',
imageIndex: 60,
overview:
'현장 드론에서 실시간 전송되는 영상 스트리밍을 시스템 내에서 직접 모니터링하는 화면이다.',
procedure: [
'상단 메뉴에서 \'항공탐색 > 실시간 드론\'을 클릭한다.',
'연결된 드론 목록에서 모니터링할 드론을 선택한다.',
'실시간 영상을 확인하고 필요 시 스냅샷을 저장한다.',
],
notes: [
'드론 스트리밍은 네트워크 연결 상태에 따라 화질 및 지연이 달라질 수 있다.',
],
},
{
id: '061',
name: '오염선박 3D분석',
menuPath: '항공탐색',
imageIndex: 61,
overview:
'항공·위성 이미지를 기반으로 오염 선박의 3D 모델을 생성하고 오염 분포를 입체적으로 분석하는 화면이다.',
},
{
id: '062',
name: '위성요청',
menuPath: '항공탐색',
imageIndex: 62,
overview:
'SAR·광학 위성 촬영 요청 및 수신 결과를 관리하는 화면이다.',
inputs: [
{ label: '요청 위치', type: '숫자', required: true, desc: '위·경도' },
{ label: '촬영 희망 일시', type: '날짜+시간', required: true, desc: '촬영 필요 일시' },
{ label: '위성 종류', type: '라디오', required: true, desc: 'SAR 또는 광학' },
{ label: '해상도', type: '드롭다운', required: false, desc: '요청 이미지 해상도' },
],
notes: [
'위성 촬영 가능 여부는 궤도 조건에 따라 결정되며 요청이 항상 수용되지 않을 수 있다.',
],
},
{
id: '063',
name: 'CCTV 조회',
menuPath: '항공탐색',
imageIndex: 63,
overview:
'해안·항만 CCTV의 실시간 영상을 조회하는 화면이다. 사고 인근 CCTV를 지도에서 선택하여 스트리밍으로 확인할 수 있다.',
procedure: [
'상단 메뉴에서 \'항공탐색 > CCTV조회\'를 클릭한다.',
'지도에서 확인할 CCTV 마커를 클릭한다.',
'실시간 영상을 확인하고 필요 시 스냅샷을 저장한다.',
],
},
{
id: '064',
name: '항공탐색 이론 - 개요',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 64,
overview:
'Wing 항공탐색 기능에 적용되는 원격탐사·ESI 방제지도·면적 산정·확산예측 연계 이론의 전체 개요를 안내한다.',
},
{
id: '065',
name: '항공탐색 이론 - 탐지 장비',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 65,
overview:
'해양 오염 항공 탐지에 사용되는 주요 장비(SAR·적외선 카메라·UV 센서·광학 카메라)의 특성과 적용 원리를 안내한다.',
},
{
id: '066',
name: '항공탐색 이론 - 원격탐사',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 66,
overview:
'위성 및 항공 원격탐사(Remote Sensing)의 기본 원리와 해양 오염 탐지 적용 방법을 안내한다.',
},
{
id: '067',
name: '항공탐색 이론 - ESI 방제지도',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 67,
overview:
'환경민감지수(ESI) 방제지도의 해안선 민감도 등급 분류 체계와 방제 우선순위 결정 방법을 안내한다.',
},
{
id: '068',
name: '항공탐색 이론 - 면적 산정',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 68,
overview:
'항공·위성 이미지 기반 유출유 면적 산정에 적용되는 AI 알고리즘(객체 탐지·영상 분할)의 원리를 안내한다.',
},
{
id: '069',
name: '항공탐색 이론 - 확산예측 연계',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 69,
overview:
'항공 탐색 결과(실측 면적·위치)와 유출유 확산예측 모델 보정 연계 방법을 안내한다.',
},
{
id: '070',
name: '항공탐색 이론 - 논문 특허',
menuPath: '항공탐색 > 항공탐색 이론',
imageIndex: 70,
overview:
'Wing 항공탐색 기능의 기반이 되는 관련 논문과 특허 목록을 안내한다.',
},
],
},
{
id: 'ch06',
number: '06',
title: '게시판',
subtitle: 'Bulletin Board',
screens: [
{
id: '071',
name: '전체 게시판',
menuPath: '게시판',
imageIndex: 71,
overview:
'공지사항·자료실·Q&A·해경매뉴얼 게시물을 통합 조회하는 전체 게시판 화면이다.',
procedure: [
'상단 메뉴에서 \'게시판\'을 클릭하여 전체 게시판으로 이동한다.',
'유형 필터 탭을 선택하거나 검색창에 키워드를 입력한다.',
'원하는 게시물 제목을 클릭하여 상세 내용을 확인한다.',
],
},
{
id: '072',
name: '공지사항',
menuPath: '게시판',
imageIndex: 72,
overview:
'시스템 공지·업데이트·장애 안내 등 운영 공지사항을 게시하는 화면이다. 관리자만 등록·수정 가능.',
},
{
id: '073',
name: '자료실',
menuPath: '게시판',
imageIndex: 73,
overview:
'매뉴얼·지침서·참고자료 등 업무 관련 문서 파일을 공유하는 게시판 화면이다.',
},
{
id: '074',
name: 'QNA',
menuPath: '게시판',
imageIndex: 74,
overview:
'시스템 사용 관련 질문을 등록하고 답변을 확인하는 Q&A 게시판 화면이다.',
inputs: [
{ label: '제목', type: '텍스트', required: true, desc: '질문 제목' },
{ label: '카테고리', type: '드롭다운', required: true, desc: '기능문의·오류신고·개선요청' },
{ label: '내용', type: '텍스트', required: true, desc: '질문 내용' },
{ label: '첨부 파일', type: '파일', required: false, desc: '스크린샷 등' },
],
},
{
id: '075',
name: '해경 매뉴얼',
menuPath: '게시판',
imageIndex: 75,
overview:
'해양경찰청 방제·대응 매뉴얼을 시스템 내에서 바로 조회할 수 있는 화면이다. 관리자만 등록·수정 가능.',
},
],
},
{
id: 'ch07',
number: '07',
title: '기상정보',
subtitle: 'Weather',
screens: [
{
id: '076',
name: '기상정보',
menuPath: '기상정보',
imageIndex: 76,
overview:
'현재 사고 지역 주변의 실시간 기상 정보(풍향·풍속·기온·파고·시정 등)를 조회하는 화면이다. 기상 데이터는 확산예측 모델 입력으로 자동 연동된다.',
description:
'사고 위치 기준 반경 내 기상 관측소 목록과 최신 관측값이 표시된다. 시계열 그래프로 과거 24시간 기상 변화를 확인할 수 있다.',
procedure: [
'상단 메뉴에서 \'기상정보\'를 클릭하여 이동한다.',
'지도에서 관측소 마커를 클릭하거나 목록에서 관측소를 선택한다.',
'최신 기상값과 시계열 그래프를 확인한다.',
],
notes: [
'극한 기상 조건에서는 확산예측 정확도가 낮아질 수 있으므로 현장 기상 관측값과 병행 확인한다.',
],
},
],
},
{
id: 'ch08',
number: '08',
title: '통합조회',
subtitle: 'Integrated Search',
screens: [
{
id: '077',
name: '통합조회',
menuPath: '통합조회',
imageIndex: 77,
overview:
'Wing 시스템 전체 분석 이력(유출유·HNS·긴급구난·보고서)을 통합 조회하는 화면이다. 사고명·날짜·분석 유형·담당자 등 복합 조건으로 검색하고 결과를 지도와 목록으로 표출한다.',
description:
'화면 좌측에 날짜 범위·분석 유형·담당자·사고 지역 복합 필터 패널이 위치한다. 중앙 지도에 검색 결과 사고지점 마커가 표출된다. Excel·CSV 내보내기를 지원한다.',
procedure: [
'상단 메뉴에서 \'통합조회\'를 클릭하여 이동한다.',
'날짜 범위·분석 유형 등 필터 조건을 설정한다.',
'\'검색\' 버튼을 클릭하여 결과를 조회한다.',
'지도 마커 또는 목록 항목을 클릭하여 해당 분석 결과 상세 화면으로 이동한다.',
'Excel·CSV 내보내기 버튼으로 이력 자료를 저장한다.',
],
inputs: [
{ label: '날짜 범위', type: '날짜', required: false, desc: '시작·종료 날짜' },
{ label: '분석 유형', type: '체크박스', required: false, desc: '유출유·HNS·긴급구난·보고서' },
{ label: '담당자', type: '텍스트', required: false, desc: '이름 필터' },
{ label: '사고 지역', type: '텍스트', required: false, desc: '지역명 필터' },
],
},
],
},
];
const UserManualPopup = ({ isOpen, onClose }: UserManualPopupProps) => {
const [selectedChapterId, setSelectedChapterId] = useState<string>('ch01');
const [expandedScreenIds, setExpandedScreenIds] = useState<Set<string>>(new Set());
const [lightboxSrc, setLightboxSrc] = useState<string | null>(null);
if (!isOpen) return null;
const selectedChapter = CHAPTERS.find((ch) => ch.id === selectedChapterId) ?? CHAPTERS[0];
const toggleScreen = (screenId: string) => {
setExpandedScreenIds((prev) => {
const next = new Set(prev);
if (next.has(screenId)) {
next.delete(screenId);
} else {
next.add(screenId);
}
return next;
});
};
const expandAll = () => {
setExpandedScreenIds(new Set(selectedChapter.screens.map((s) => s.id)));
};
const collapseAll = () => {
setExpandedScreenIds(new Set());
};
const allExpanded =
selectedChapter.screens.length > 0 &&
selectedChapter.screens.every((s) => expandedScreenIds.has(s.id));
return (
<>
<div
className='fixed inset-0 z-[9999] flex items-center justify-center'
style={{ background: 'rgba(0,0,0,0.65)' }}
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div
className='flex flex-col rounded-lg overflow-hidden'
style={{
width: '90vw',
height: '85vh',
background: '#0f1729',
border: '1px solid #1e2a45',
boxShadow: '0 24px 64px rgba(0,0,0,0.5)',
}}
>
{/* Header */}
<div
className='flex items-center justify-between px-6 py-4 flex-shrink-0'
style={{
background: '#0b1120',
borderBottom: '1px solid #1e2a45',
}}
>
<div className='flex items-center gap-3'>
<span
className='font-bold text-[15px]'
style={{ color: '#e2e8f0' }}
>
Wing
</span>
<span
className='text-[11px] px-2 py-0.5 rounded font-mono'
style={{
background: 'rgba(6,182,212,0.12)',
color: '#06b6d4',
border: '1px solid rgba(6,182,212,0.25)',
}}
>
v0.5
</span>
</div>
<button
onClick={onClose}
className='flex items-center justify-center w-7 h-7 rounded text-[13px] font-semibold transition-colors'
style={{ color: '#94a3b8', background: 'transparent' }}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#1a2540';
e.currentTarget.style.color = '#e2e8f0';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = '#94a3b8';
}}
>
X
</button>
</div>
{/* Body */}
<div className='flex flex-1 overflow-hidden'>
{/* Left Sidebar */}
<div
className='flex-shrink-0 overflow-y-auto py-3'
style={{
width: '240px',
background: '#0b1120',
borderRight: '1px solid #1e2a45',
}}
>
{CHAPTERS.map((chapter) => {
const isActive = chapter.id === selectedChapterId;
return (
<button
key={chapter.id}
onClick={() => {
setSelectedChapterId(chapter.id);
setExpandedScreenIds(new Set());
}}
className='w-full text-left px-4 py-3 transition-colors'
style={{
background: isActive ? 'rgba(6,182,212,0.08)' : 'transparent',
borderLeft: isActive ? '2px solid #06b6d4' : '2px solid transparent',
}}
onMouseEnter={(e) => {
if (!isActive) {
e.currentTarget.style.background = '#1a2540';
}
}}
onMouseLeave={(e) => {
if (!isActive) {
e.currentTarget.style.background = 'transparent';
}
}}
>
<div className='flex items-center gap-2.5'>
<span
className='flex-shrink-0 w-7 h-7 rounded flex items-center justify-center text-[10px] font-bold font-mono'
style={{
background: isActive ? 'rgba(6,182,212,0.18)' : 'rgba(255,255,255,0.05)',
color: isActive ? '#06b6d4' : '#64748b',
border: isActive ? '1px solid rgba(6,182,212,0.3)' : '1px solid #1e2a45',
}}
>
{chapter.number}
</span>
<div className='min-w-0'>
<div
className='text-[12px] font-medium leading-tight truncate'
style={{ color: isActive ? '#06b6d4' : '#cbd5e1' }}
>
{chapter.title}
</div>
<div
className='text-[10px] leading-tight mt-0.5 truncate'
style={{ color: '#475569' }}
>
{chapter.subtitle}
</div>
</div>
</div>
</button>
);
})}
</div>
{/* Right Content */}
<div className='flex-1 overflow-y-auto p-6'>
{/* Chapter heading */}
<div className='mb-5 pb-4' style={{ borderBottom: '1px solid #1e2a45' }}>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-3'>
<span
className='text-[11px] font-mono px-2 py-0.5 rounded font-bold'
style={{
background: 'rgba(6,182,212,0.12)',
color: '#06b6d4',
border: '1px solid rgba(6,182,212,0.2)',
}}
>
CH {selectedChapter.number}
</span>
<h2 className='text-[16px] font-semibold' style={{ color: '#e2e8f0' }}>
{selectedChapter.title}
</h2>
<span className='text-[12px]' style={{ color: '#475569' }}>
{selectedChapter.subtitle}
</span>
</div>
<div className='flex items-center gap-2'>
<span className='text-[11px] mr-1' style={{ color: '#64748b' }}>
{selectedChapter.screens.length}
</span>
<button
onClick={allExpanded ? collapseAll : expandAll}
className='text-[11px] px-3 py-1 rounded transition-colors'
style={{
background: 'rgba(6,182,212,0.08)',
color: '#06b6d4',
border: '1px solid rgba(6,182,212,0.2)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(6,182,212,0.16)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(6,182,212,0.08)';
}}
>
{allExpanded ? '전체 닫기' : '전체 열기'}
</button>
</div>
</div>
</div>
{/* Screen cards */}
<div className='flex flex-col gap-2'>
{selectedChapter.screens.map((screen) => {
const isExpanded = expandedScreenIds.has(screen.id);
const imageSrc = `/manual/image${screen.imageIndex}.png`;
return (
<div
key={screen.id}
className='rounded-lg overflow-hidden'
style={{
background: '#141d33',
border: '1px solid #1e2a45',
}}
>
{/* Screen header (toggle) */}
<button
onClick={() => toggleScreen(screen.id)}
className='w-full text-left flex items-center gap-3 px-4 py-3 transition-colors'
style={{ background: 'transparent' }}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#1a2540';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
}}
>
<span
className='flex-shrink-0 text-[10px] font-mono font-bold px-1.5 py-0.5 rounded'
style={{
background: 'rgba(6,182,212,0.1)',
color: '#06b6d4',
border: '1px solid rgba(6,182,212,0.2)',
minWidth: '36px',
textAlign: 'center',
}}
>
{screen.id}
</span>
<span
className='flex-1 text-[13px] font-medium'
style={{ color: '#cbd5e1' }}
>
{screen.name}
</span>
<span
className='flex-shrink-0 text-[10px] font-mono'
style={{
color: '#475569',
transition: 'transform 0.2s',
display: 'inline-block',
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
}}
>
v
</span>
</button>
{/* Screen detail (expanded) */}
{isExpanded && (
<div
className='px-4 pb-5'
style={{ borderTop: '1px solid #1e2a45' }}
>
{/* Screenshot image */}
<div className='mt-4 mb-4'>
<img
src={imageSrc}
alt={screen.name}
loading='lazy'
onClick={() => setLightboxSrc(imageSrc)}
style={{
width: '100%',
borderRadius: '6px',
border: '1px solid #1e2a45',
cursor: 'zoom-in',
display: 'block',
}}
/>
<p
className='mt-1 text-[10px] text-right'
style={{ color: '#475569' }}
>
</p>
</div>
{/* Menu path breadcrumb */}
<div
className='mb-3 text-[11px] font-mono px-2 py-1 rounded inline-block'
style={{
background: 'rgba(71,85,105,0.15)',
color: '#64748b',
border: '1px solid #1e2a45',
}}
>
{screen.menuPath}
</div>
{/* Overview */}
<div className='mt-2'>
<p
className='text-[12px] leading-relaxed'
style={{ color: '#94a3b8' }}
>
{screen.overview}
</p>
</div>
{/* Description */}
{screen.description && (
<div
className='mt-3 px-3 py-2.5 rounded'
style={{
background: 'rgba(30,42,69,0.6)',
border: '1px solid #1e2a45',
}}
>
<div
className='text-[11px] font-semibold mb-1.5 uppercase tracking-wide'
style={{ color: '#475569' }}
>
</div>
<p
className='text-[12px] leading-relaxed'
style={{ color: '#7f8ea3' }}
>
{screen.description}
</p>
</div>
)}
{/* Procedure */}
{screen.procedure && screen.procedure.length > 0 && (
<div className='mt-4'>
<div
className='text-[11px] font-semibold mb-2 uppercase tracking-wide'
style={{ color: '#475569' }}
>
</div>
<ol className='flex flex-col gap-1.5'>
{screen.procedure.map((step, idx) => (
<li key={idx} className='flex items-start gap-2.5'>
<span
className='flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold mt-0.5'
style={{
background: 'rgba(6,182,212,0.12)',
color: '#06b6d4',
border: '1px solid rgba(6,182,212,0.2)',
}}
>
{idx + 1}
</span>
<span
className='text-[12px] leading-relaxed'
style={{ color: '#94a3b8' }}
>
{step}
</span>
</li>
))}
</ol>
</div>
)}
{/* Inputs */}
{screen.inputs && screen.inputs.length > 0 && (
<div className='mt-4'>
<div
className='text-[11px] font-semibold mb-2 uppercase tracking-wide'
style={{ color: '#475569' }}
>
</div>
<div
className='rounded overflow-hidden'
style={{ border: '1px solid #1e2a45' }}
>
<table className='w-full text-[12px]'>
<thead>
<tr style={{ background: '#0f1729' }}>
<th
className='text-left px-3 py-2 font-medium'
style={{ color: '#64748b', width: '22%' }}
>
</th>
<th
className='text-left px-3 py-2 font-medium'
style={{ color: '#64748b', width: '18%' }}
>
</th>
<th
className='text-left px-3 py-2 font-medium'
style={{ color: '#64748b', width: '12%' }}
>
</th>
<th
className='text-left px-3 py-2 font-medium'
style={{ color: '#64748b' }}
>
</th>
</tr>
</thead>
<tbody>
{screen.inputs.map((input, idx) => (
<tr
key={idx}
style={{
borderTop: '1px solid #1e2a45',
background:
idx % 2 === 0
? 'transparent'
: 'rgba(255,255,255,0.01)',
}}
>
<td
className='px-3 py-2 font-medium'
style={{ color: '#cbd5e1' }}
>
{input.label}
</td>
<td
className='px-3 py-2'
style={{ color: '#64748b' }}
>
{input.type}
</td>
<td className='px-3 py-2'>
{input.required ? (
<span
className='text-[10px] font-bold px-1.5 py-0.5 rounded'
style={{
background: 'rgba(239,68,68,0.1)',
color: '#f87171',
border: '1px solid rgba(239,68,68,0.2)',
}}
>
</span>
) : (
<span
className='text-[10px] px-1.5 py-0.5 rounded'
style={{
background: 'rgba(100,116,139,0.1)',
color: '#64748b',
border: '1px solid rgba(100,116,139,0.2)',
}}
>
</span>
)}
</td>
<td
className='px-3 py-2'
style={{ color: '#7f8ea3' }}
>
{input.desc}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Notes */}
{screen.notes && screen.notes.length > 0 && (
<div className='mt-4'>
<div
className='text-[11px] font-semibold mb-2 uppercase tracking-wide'
style={{ color: '#475569' }}
>
</div>
<ul className='flex flex-col gap-1.5'>
{screen.notes.map((note, idx) => (
<li key={idx} className='flex items-start gap-2'>
<span
className='flex-shrink-0 mt-1.5 w-1.5 h-1.5 rounded-full'
style={{ background: '#f59e0b' }}
/>
<span
className='text-[12px] leading-relaxed'
style={{ color: '#94a3b8' }}
>
{note}
</span>
</li>
))}
</ul>
</div>
)}
</div>
)}
</div>
);
})}
</div>
</div>
</div>
</div>
</div>
{/* Lightbox */}
{lightboxSrc !== null && (
<div
className='fixed inset-0 z-[10000] flex items-center justify-center'
style={{ background: 'rgba(0,0,0,0.88)' }}
onClick={() => setLightboxSrc(null)}
>
<div
className='relative'
style={{ maxWidth: '92vw', maxHeight: '90vh' }}
onClick={(e) => e.stopPropagation()}
>
<img
src={lightboxSrc}
alt='확대 이미지'
style={{
maxWidth: '92vw',
maxHeight: '88vh',
borderRadius: '8px',
border: '1px solid #1e2a45',
display: 'block',
}}
/>
<button
onClick={() => setLightboxSrc(null)}
className='absolute top-2 right-2 w-8 h-8 rounded flex items-center justify-center text-[13px] font-bold'
style={{
background: 'rgba(15,23,41,0.85)',
color: '#94a3b8',
border: '1px solid #1e2a45',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#e2e8f0';
e.currentTarget.style.background = '#1a2540';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = '#94a3b8';
e.currentTarget.style.background = 'rgba(15,23,41,0.85)';
}}
>
X
</button>
</div>
</div>
)}
</>
);
};
export default UserManualPopup;