6개 대형 View(AerialView, AssetsView, ReportsView, PreScatView, AdminView, LeftPanel)를 서브탭 단위로 분할하여 모듈 경계를 명확히 함. - AerialView (2,526줄 → 8파일): MediaManagement, OilAreaAnalysis, RealtimeDrone 등 - AssetsView (2,047줄 → 8파일): AssetManagement, AssetMap, ShipInsurance 등 - ReportsView (1,596줄 → 5파일): TemplateFormEditor, ReportGenerator 등 - PreScatView (1,390줄 → 7파일): ScatLeftPanel, ScatMap, ScatPopup 등 - AdminView (1,306줄 → 7파일): UsersPanel, PermissionsPanel, MenusPanel 등 - LeftPanel (1,237줄 → 5파일): PredictionInputSection, InfoLayerSection, OilBoomSection 등 FEATURE_ID 레지스트리(common/constants/featureIds.ts) 및 감사로그 서브탭 추적 훅(useFeatureTracking) 추가. .gitignore의 scat/ → /scat/ 수정 (scat 탭 파일 추적 누락 수정) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
256 lines
12 KiB
TypeScript
256 lines
12 KiB
TypeScript
interface TheoryItem {
|
||
title: string
|
||
source: string
|
||
desc: string
|
||
tags?: { label: string; color: string }[]
|
||
highlight?: boolean
|
||
}
|
||
|
||
interface TheorySection {
|
||
icon: string
|
||
title: string
|
||
color: string
|
||
bgTint: string
|
||
items: TheoryItem[]
|
||
dividerAfter?: number
|
||
dividerLabel?: string
|
||
}
|
||
|
||
const THEORY_SECTIONS: TheorySection[] = [
|
||
{
|
||
icon: '🚢', title: '방제선 성능 기준', color: 'var(--blue)', bgTint: 'rgba(59,130,246,.08)',
|
||
items: [
|
||
{
|
||
title: '해양경찰청 방제선 성능기준 고시',
|
||
source: '해양경찰청 고시 제2022-11호 | 방제선·방제정 등급별 회수용량·속력·펌프사양 기준 정의',
|
||
desc: '1~5등급 방제선 기준 · 회수능력(㎥/h) · 오일펜스 전장 탑재량 · WING 자산 등급 필터링 근거',
|
||
},
|
||
{
|
||
title: 'IMO OPRC 1990 — 방제자원 비축 기준',
|
||
source: 'International Convention on Oil Pollution Preparedness, Response and Co-operation | IMO, 1990',
|
||
desc: '국가 방제역량 비축 최저 기준 · 항만별 Tier 1/2/3 대응자원 분류 · 국내 방제자원 DB 설계 기초',
|
||
},
|
||
{
|
||
title: '해양오염방제업 등록기준 (해양환경관리법 시행규칙)',
|
||
source: '해양수산부령 | 별표 9 — 방제업 종류별 방제선·기자재 보유기준',
|
||
desc: '제1종·제2종 방제업 자산 보유기준 · 오일펜스 전장·회수기 용량 법적 최저기준 · WING 자산현황 적법성 검증 기준',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
icon: '🪢', title: '오일펜스·흡착재 규격', color: 'var(--boom, #f59e0b)', bgTint: 'rgba(245,158,11,.08)',
|
||
items: [
|
||
{
|
||
title: 'ASTM F625 — Standard Guide for Selecting Mechanical Oil Spill Equipment',
|
||
source: 'ASTM International | 오일펜스·회수기·흡착재 성능시험·선정 기준 가이드',
|
||
desc: '오일펜스 인장강도·부력기준 · 흡착포 흡수율(g/g) 측정법 · WING 자산 성능등급 분류 참조 기준',
|
||
},
|
||
{
|
||
title: '기름오염방제시 오일펜스 사용지침 (ITOPF TIP 03 한국어판)',
|
||
source: 'ITOPF | 해양경찰청·해양환경관리공단 번역, 2011',
|
||
desc: '커튼형·펜스형·해안용 규격분류 · 유속별 운용한계(0.7~3.0 kt) · 힘 계산식 F=100·A·V² · 앵커 파지력 기준표',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
icon: '⚙️', title: '방제자원 배치·동원 이론', color: 'var(--purple)', bgTint: 'rgba(168,85,247,.08)',
|
||
dividerAfter: 2, dividerLabel: '📐 최적화 수리모델 참고문헌',
|
||
items: [
|
||
{
|
||
title: 'An Emergency Scheduling Model for Oil Containment Boom in Dynamically Changing Marine Oil Spills',
|
||
source: 'Xu, Y. et al. | Ningbo Univ. | Systems 2025, 13, 716 · DOI: 10.3390/systems13080716',
|
||
desc: 'IMOGWO 다목적 최적화 · 스케줄링 시간+경제·생태손실 동시 최소화 · 동적 오일필름 기반 방제정 라우팅',
|
||
highlight: true,
|
||
},
|
||
{
|
||
title: 'Dynamic Resource Allocation to Support Oil Spill Response Planning',
|
||
source: 'Garrett, R.A. et al. | Eur. J. Oper. Res. 257:272–286, 2017',
|
||
desc: '불확실성 하 방제자원 동적 배분 최적화 · 시나리오별 비축량 산정 · WING 자산 우선순위 배치 알고리즘 이론 기반',
|
||
},
|
||
{
|
||
title: '해양오염방제 국가긴급방제계획 (NOSCP)',
|
||
source: '해양경찰청 | 국가긴급방제계획, 2023년판',
|
||
desc: 'Tier 3급 대형사고 자원 동원체계 · 기관별 역할분담·지휘계통 · WING 방제자산 연계 법적 근거',
|
||
},
|
||
{
|
||
title: 'A Mixed Integer Programming Approach to Improve Oil Spill Response Resource Allocation in the Canadian Arctic',
|
||
source: 'Das, T., Goerlandt, F. & Pelot, R. | Multimodal Transportation Vol.3 No.1, 100110, 2023',
|
||
desc: '혼합정수계획법으로 응급 방제자원 거점 위치 선택 + 자원 할당 동시 최적화. 비용·응답시간 트레이드오프 파레토 분석.',
|
||
highlight: true,
|
||
tags: [
|
||
{ label: 'MIP 수리모델', color: 'var(--purple)' },
|
||
{ label: '자원 위치 선택', color: 'var(--blue)' },
|
||
{ label: '북극해 적용', color: 'var(--cyan)' },
|
||
],
|
||
},
|
||
{
|
||
title: '유전알고리즘을 이용하여 최적화된 방제자원 배치안의 분포도 분석',
|
||
source: '김혜진, 김용혁 | 한국융합학회논문지 Vol.11 No.4, pp.11–16, 2020',
|
||
desc: 'GA(유전알고리즘)로 방제자원 배치 최적화 및 시뮬레이션 분포도 분석. 국내 해역 실정에 맞는 자원 배치 패턴 도출.',
|
||
highlight: true,
|
||
tags: [
|
||
{ label: 'GA 메타휴리스틱', color: 'var(--purple)' },
|
||
{ label: '국내 연구', color: 'var(--green, #22c55e)' },
|
||
{ label: '배치 분포도 분석', color: 'var(--boom, #f59e0b)' },
|
||
],
|
||
},
|
||
{
|
||
title: 'A Two-Stage Stochastic Optimization Framework for Environmentally Sensitive Oil Spill Response Resource Allocation',
|
||
source: 'Rahman, M.A., Kuhel, M.T. & Novoa, C. | arXiv preprint arXiv:2511.22218, 2025',
|
||
desc: '확률적 MILP 2단계 프레임워크로 불확실성 포함 최적 자원 배치. 환경민감구역 가중치 반영.',
|
||
highlight: true,
|
||
tags: [
|
||
{ label: '확률적 MILP', color: 'var(--purple)' },
|
||
{ label: '2단계 최적화', color: 'var(--blue)' },
|
||
{ label: '환경민감구역', color: 'var(--green, #22c55e)' },
|
||
],
|
||
},
|
||
{
|
||
title: 'Mixed-Integer Dynamic Optimization for Oil-Spill Response Planning with Integration of Dynamic Oil Weathering Model',
|
||
source: 'You, F. & Leyffer, S. | Argonne National Laboratory Technical Note, 2008',
|
||
desc: '동적 최적화(MINLP/MILP) 프레임워크로 오일스필 대응 스케줄링 + 오일 풍화·거동 물리모델 통합.',
|
||
highlight: true,
|
||
tags: [
|
||
{ label: 'MINLP 동적 최적화', color: 'var(--purple)' },
|
||
{ label: '오일 풍화 모델 통합', color: 'var(--boom, #f59e0b)' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
icon: '🗄', title: '자산 현행화·데이터 관리', color: 'var(--green, #22c55e)', bgTint: 'rgba(34,197,94,.08)',
|
||
items: [
|
||
{
|
||
title: '해양오염방제자원 현황관리 지침',
|
||
source: '해양경찰청 예규 | 방제자원 등록·현행화·이력관리 절차 규정',
|
||
desc: '분기별 자산 실사 기준 · 자산분류코드 체계 · WING 업로드 양식(xlsx) 필드 정의 근거',
|
||
},
|
||
{
|
||
title: 'ISO 55000 — Asset Management: Overview, Principles and Terminology',
|
||
source: 'International Organization for Standardization | ISO 55000:2014',
|
||
desc: '자산 생애주기 관리 원칙 · 자산가치·상태 평가 프레임워크 · WING 자산 노후도·교체주기 산정 이론 기준',
|
||
},
|
||
],
|
||
},
|
||
]
|
||
|
||
const TAG_COLORS: Record<string, { bg: string; bd: string; fg: string }> = {
|
||
'var(--purple)': { bg: 'rgba(168,85,247,0.08)', bd: 'rgba(168,85,247,0.2)', fg: '#a855f7' },
|
||
'var(--blue)': { bg: 'rgba(59,130,246,0.08)', bd: 'rgba(59,130,246,0.2)', fg: '#3b82f6' },
|
||
'var(--cyan)': { bg: 'rgba(6,182,212,0.08)', bd: 'rgba(6,182,212,0.2)', fg: '#06b6d4' },
|
||
'var(--green, #22c55e)': { bg: 'rgba(34,197,94,0.08)', bd: 'rgba(34,197,94,0.2)', fg: '#22c55e' },
|
||
'var(--boom, #f59e0b)': { bg: 'rgba(245,158,11,0.08)', bd: 'rgba(245,158,11,0.2)', fg: '#f59e0b' },
|
||
}
|
||
|
||
function TheoryCard({ section }: { section: TheorySection }) {
|
||
const badgeBg = section.bgTint.replace(/[\d.]+\)$/, '0.15)')
|
||
return (
|
||
<div style={{
|
||
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
||
borderRadius: 'var(--rM, 10px)', overflow: 'hidden',
|
||
}}>
|
||
{/* Section Header */}
|
||
<div style={{
|
||
padding: '12px 16px', background: section.bgTint,
|
||
borderBottom: '1px solid var(--bd)',
|
||
display: 'flex', alignItems: 'center', gap: '8px',
|
||
}}>
|
||
<span style={{ fontSize: '14px' }}>{section.icon}</span>
|
||
<span style={{ fontSize: '12px', fontWeight: 700, color: section.color, fontFamily: 'var(--fK)' }}>
|
||
{section.title}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Items */}
|
||
<div style={{ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
||
{section.items.map((item, i) => (
|
||
<div key={i}>
|
||
{/* Divider */}
|
||
{section.dividerAfter !== undefined && i === section.dividerAfter + 1 && (
|
||
<div style={{ borderTop: '1px dashed var(--bd)', margin: '4px 0 12px', paddingTop: '8px' }}>
|
||
<div style={{ fontSize: '8px', fontWeight: 700, color: section.color, marginBottom: '6px', opacity: 0.7 }}>
|
||
{section.dividerLabel}
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div style={{
|
||
display: 'grid', gridTemplateColumns: '24px 1fr', gap: '8px',
|
||
padding: '8px 10px', background: 'var(--bg0)', borderRadius: '6px',
|
||
borderLeft: item.highlight ? `2px solid ${section.color}` : undefined,
|
||
}}>
|
||
{/* Number badge */}
|
||
<div style={{
|
||
width: '20px', height: '20px', borderRadius: '4px',
|
||
background: badgeBg,
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
fontSize: '9px', flexShrink: 0,
|
||
fontWeight: item.highlight ? 700 : 400,
|
||
color: item.highlight ? section.color : undefined,
|
||
}}>
|
||
{['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩'][i]}
|
||
</div>
|
||
<div>
|
||
<div style={{ color: 'var(--t1)', fontWeight: 700, marginBottom: '2px' }}>
|
||
{item.title}
|
||
</div>
|
||
<div style={{ color: 'var(--t3)', lineHeight: '1.6' }}>
|
||
{item.source}
|
||
</div>
|
||
{/* Tags */}
|
||
{item.tags && (
|
||
<div style={{ marginTop: '3px', display: 'flex', flexWrap: 'wrap', gap: '3px' }}>
|
||
{item.tags.map((tag, ti) => {
|
||
const tc = TAG_COLORS[tag.color] || { bg: 'rgba(107,114,128,0.08)', bd: 'rgba(107,114,128,0.2)', fg: '#6b7280' }
|
||
return (
|
||
<span key={ti} style={{
|
||
padding: '1px 5px', borderRadius: '3px', fontSize: '8px',
|
||
color: tc.fg, background: tc.bg, border: `1px solid ${tc.bd}`,
|
||
}}>
|
||
{tag.label}
|
||
</span>
|
||
)
|
||
})}
|
||
</div>
|
||
)}
|
||
<div style={{ marginTop: '2px', color: 'var(--t2)' }}>
|
||
{item.desc}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function AssetTheory() {
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
|
||
<div style={{ fontSize: '18px', fontWeight: 700, fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
||
📚 방제자원 이론
|
||
</div>
|
||
<div style={{ fontSize: '12px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '24px' }}>
|
||
방제자산 운용 기준·성능 이론 및 관련 법령·고시 근거 문헌
|
||
</div>
|
||
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '18px', alignItems: 'start' }}>
|
||
{/* Left column */}
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
|
||
{THEORY_SECTIONS.slice(0, 2).map((sec) => (
|
||
<TheoryCard key={sec.title} section={sec} />
|
||
))}
|
||
</div>
|
||
{/* Right column */}
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
|
||
{THEORY_SECTIONS.slice(2).map((sec) => (
|
||
<TheoryCard key={sec.title} section={sec} />
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AssetTheory
|