refactor(css): CSS 인프라 구축 + body default 인라인 스타일 1,055건 제거
Phase 0: CSS 인프라 구축
- Tailwind config 색상 불일치 수정 (t1/t2/t3 → CSS 변수 값으로 통일)
- index.css 1,302줄 → @import 엔트리포인트 7줄로 축소
- common/styles/base.css: @layer base 추출 (CSS 변수, 리셋, body 기본값)
- common/styles/components.css: @layer components + utilities 추출
- common/styles/wing.css: wing-* 디자인 시스템 클래스 신규 정의
- common/utils/cn.ts: className 조합 유틸리티
- App.css 삭제 (내용을 components.css로 통합)
Phase 1: body default 인라인 스타일 일괄 제거
- fontFamily: 'var(--fK)' 781건 제거 (body font-family 상속)
- color: 'var(--t1)' 274건 제거 (body color 상속)
- 빈 style={{}} 78건 정리
- 31개 파일, JS 번들 23KB 감소
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
e27cdcdf85
커밋
dec066e8bb
@ -1,175 +0,0 @@
|
|||||||
/* Layer tree 3-level styles */
|
|
||||||
.lyr-sw {
|
|
||||||
width: 28px;
|
|
||||||
height: 16px;
|
|
||||||
background: var(--bg0);
|
|
||||||
border-radius: 8px;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid var(--bd);
|
|
||||||
transition: 0.2s;
|
|
||||||
flex-shrink: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-sw::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: var(--t3);
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-sw.on {
|
|
||||||
background: rgba(6, 182, 212, 0.3);
|
|
||||||
border-color: var(--cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-sw.on::after {
|
|
||||||
left: 14px;
|
|
||||||
background: var(--cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-sw:disabled {
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Level 1 Group */
|
|
||||||
.lyr-g1 {
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h1 {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 7px 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--rS);
|
|
||||||
transition: background 0.15s;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--t1);
|
|
||||||
font-family: var(--fK);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h1:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h1 .lyr-arr {
|
|
||||||
font-size: 8px;
|
|
||||||
color: var(--t3);
|
|
||||||
transition: transform 0.2s;
|
|
||||||
width: 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h1 .lyr-arr.open {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h1-cnt {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--t3);
|
|
||||||
font-family: var(--fM);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-c1 {
|
|
||||||
padding-left: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-c1.collapsed {
|
|
||||||
max-height: 0 !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Level 2 Group */
|
|
||||||
.lyr-g2 {
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h2 {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 5px 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: background 0.15s;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--t2);
|
|
||||||
font-family: var(--fK);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h2:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h2 .lyr-arr {
|
|
||||||
font-size: 7px;
|
|
||||||
color: var(--t3);
|
|
||||||
transition: transform 0.2s;
|
|
||||||
width: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h2 .lyr-arr.open {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-h2-cnt {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--t3);
|
|
||||||
font-family: var(--fM);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-c2 {
|
|
||||||
padding-left: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-c2.collapsed {
|
|
||||||
max-height: 0 !important;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Level 3 Toggle */
|
|
||||||
.lyr-t {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--t2);
|
|
||||||
transition: color 0.15s, background 0.15s;
|
|
||||||
font-family: var(--fK);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-t:hover {
|
|
||||||
color: var(--t1);
|
|
||||||
background: rgba(255, 255, 255, 0.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lyr-cnt {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--t3);
|
|
||||||
font-family: var(--fM);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
@ -107,7 +107,7 @@ export function LoginPage() {
|
|||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<label style={{
|
<label style={{
|
||||||
display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)',
|
display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)',
|
||||||
fontFamily: 'var(--fK)', marginBottom: 6, letterSpacing: '0.3px',
|
marginBottom: 6, letterSpacing: '0.3px',
|
||||||
}}>
|
}}>
|
||||||
아이디
|
아이디
|
||||||
</label>
|
</label>
|
||||||
@ -128,8 +128,8 @@ export function LoginPage() {
|
|||||||
style={{
|
style={{
|
||||||
width: '100%', padding: '11px 14px 11px 38px',
|
width: '100%', padding: '11px 14px 11px 38px',
|
||||||
background: 'var(--bg2)', border: '1px solid var(--bd)',
|
background: 'var(--bg2)', border: '1px solid var(--bd)',
|
||||||
borderRadius: 8, color: 'var(--t1)', fontSize: 13,
|
borderRadius: 8, fontSize: 13,
|
||||||
fontFamily: 'var(--fK)', outline: 'none',
|
outline: 'none',
|
||||||
transition: 'border-color 0.2s, box-shadow 0.2s',
|
transition: 'border-color 0.2s, box-shadow 0.2s',
|
||||||
}}
|
}}
|
||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
@ -148,7 +148,7 @@ export function LoginPage() {
|
|||||||
<div style={{ marginBottom: 20 }}>
|
<div style={{ marginBottom: 20 }}>
|
||||||
<label style={{
|
<label style={{
|
||||||
display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)',
|
display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)',
|
||||||
fontFamily: 'var(--fK)', marginBottom: 6, letterSpacing: '0.3px',
|
marginBottom: 6, letterSpacing: '0.3px',
|
||||||
}}>
|
}}>
|
||||||
비밀번호
|
비밀번호
|
||||||
</label>
|
</label>
|
||||||
@ -168,8 +168,8 @@ export function LoginPage() {
|
|||||||
style={{
|
style={{
|
||||||
width: '100%', padding: '11px 14px 11px 38px',
|
width: '100%', padding: '11px 14px 11px 38px',
|
||||||
background: 'var(--bg2)', border: '1px solid var(--bd)',
|
background: 'var(--bg2)', border: '1px solid var(--bd)',
|
||||||
borderRadius: 8, color: 'var(--t1)', fontSize: 13,
|
borderRadius: 8, fontSize: 13,
|
||||||
fontFamily: 'var(--fK)', outline: 'none',
|
outline: 'none',
|
||||||
transition: 'border-color 0.2s, box-shadow 0.2s',
|
transition: 'border-color 0.2s, box-shadow 0.2s',
|
||||||
}}
|
}}
|
||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
@ -191,7 +191,7 @@ export function LoginPage() {
|
|||||||
}}>
|
}}>
|
||||||
<label style={{
|
<label style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 6,
|
display: 'flex', alignItems: 'center', gap: 6,
|
||||||
fontSize: 11, color: 'var(--t3)', fontFamily: 'var(--fK)', cursor: 'pointer',
|
fontSize: 11, color: 'var(--t3)', cursor: 'pointer',
|
||||||
}}>
|
}}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -202,7 +202,7 @@ export function LoginPage() {
|
|||||||
아이디 저장
|
아이디 저장
|
||||||
</label>
|
</label>
|
||||||
<button type="button" style={{
|
<button type="button" style={{
|
||||||
fontSize: 11, color: 'var(--cyan)', fontFamily: 'var(--fK)',
|
fontSize: 11, color: 'var(--cyan)',
|
||||||
background: 'none', border: 'none', cursor: 'pointer',
|
background: 'none', border: 'none', cursor: 'pointer',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
}}
|
}}
|
||||||
@ -218,7 +218,7 @@ export function LoginPage() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
padding: '10px 12px', marginBottom: 16, borderRadius: 6,
|
padding: '10px 12px', marginBottom: 16, borderRadius: 6,
|
||||||
background: 'rgba(6,182,212,0.08)', border: '1px solid rgba(6,182,212,0.2)',
|
background: 'rgba(6,182,212,0.08)', border: '1px solid rgba(6,182,212,0.2)',
|
||||||
fontSize: 11, color: '#67e8f9', fontFamily: 'var(--fK)',
|
fontSize: 11, color: '#67e8f9',
|
||||||
display: 'flex', alignItems: 'flex-start', gap: 8,
|
display: 'flex', alignItems: 'flex-start', gap: 8,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 14, flexShrink: 0, marginTop: 1 }}>
|
<span style={{ fontSize: 14, flexShrink: 0, marginTop: 1 }}>
|
||||||
@ -233,7 +233,7 @@ export function LoginPage() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
padding: '8px 12px', marginBottom: 16, borderRadius: 6,
|
padding: '8px 12px', marginBottom: 16, borderRadius: 6,
|
||||||
background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.2)',
|
background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.2)',
|
||||||
fontSize: 11, color: '#f87171', fontFamily: 'var(--fK)',
|
fontSize: 11, color: '#f87171',
|
||||||
display: 'flex', alignItems: 'center', gap: 6,
|
display: 'flex', alignItems: 'center', gap: 6,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 13 }}>
|
<span style={{ fontSize: 13 }}>
|
||||||
@ -252,7 +252,6 @@ export function LoginPage() {
|
|||||||
border: '1px solid rgba(6,182,212,0.3)',
|
border: '1px solid rgba(6,182,212,0.3)',
|
||||||
borderRadius: 8, color: 'var(--cyan)',
|
borderRadius: 8, color: 'var(--cyan)',
|
||||||
fontSize: 14, fontWeight: 700, cursor: isLoading ? 'wait' : 'pointer',
|
fontSize: 14, fontWeight: 700, cursor: isLoading ? 'wait' : 'pointer',
|
||||||
fontFamily: 'var(--fK)',
|
|
||||||
transition: 'all 0.2s',
|
transition: 'all 0.2s',
|
||||||
boxShadow: '0 4px 16px rgba(6,182,212,0.1)',
|
boxShadow: '0 4px 16px rgba(6,182,212,0.1)',
|
||||||
}}
|
}}
|
||||||
@ -287,7 +286,7 @@ export function LoginPage() {
|
|||||||
display: 'flex', alignItems: 'center', gap: 12, margin: '24px 0',
|
display: 'flex', alignItems: 'center', gap: 12, margin: '24px 0',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ flex: 1, height: 1, background: 'var(--bd)' }} />
|
<div style={{ flex: 1, height: 1, background: 'var(--bd)' }} />
|
||||||
<span style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>또는</span>
|
<span style={{ fontSize: 9, color: 'var(--t3)' }}>또는</span>
|
||||||
<div style={{ flex: 1, height: 1, background: 'var(--bd)' }} />
|
<div style={{ flex: 1, height: 1, background: 'var(--bd)' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -312,7 +311,7 @@ export function LoginPage() {
|
|||||||
width: '100%', padding: '10px', borderRadius: 8,
|
width: '100%', padding: '10px', borderRadius: 8,
|
||||||
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
||||||
color: 'var(--t2)', fontSize: 11, fontWeight: 600,
|
color: 'var(--t2)', fontSize: 11, fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
|
||||||
transition: 'background 0.15s',
|
transition: 'background 0.15s',
|
||||||
}}
|
}}
|
||||||
@ -330,7 +329,7 @@ export function LoginPage() {
|
|||||||
marginTop: 24, padding: '10px 12px', borderRadius: 8,
|
marginTop: 24, padding: '10px 12px', borderRadius: 8,
|
||||||
background: 'rgba(6,182,212,0.04)', border: '1px solid rgba(6,182,212,0.08)',
|
background: 'rgba(6,182,212,0.04)', border: '1px solid rgba(6,182,212,0.08)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: 6 }}>
|
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--cyan)', marginBottom: 6 }}>
|
||||||
데모 계정
|
데모 계정
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
@ -348,7 +347,7 @@ export function LoginPage() {
|
|||||||
<span style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fM)' }}>
|
<span style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fM)' }}>
|
||||||
{acc.id} / {acc.password}
|
{acc.id} / {acc.password}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 8, color: 'var(--t3)' }}>
|
||||||
{acc.label}
|
{acc.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -361,7 +360,7 @@ export function LoginPage() {
|
|||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: 24, textAlign: 'center', fontSize: 9,
|
marginTop: 24, textAlign: 'center', fontSize: 9,
|
||||||
color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.6,
|
color: 'var(--t3)', lineHeight: 1.6,
|
||||||
}}>
|
}}>
|
||||||
<div>WING V2.0 | 해양경찰청 기동방제과 위기대응 통합시스템</div>
|
<div>WING V2.0 | 해양경찰청 기동방제과 위기대응 통합시스템</div>
|
||||||
<div style={{ marginTop: 2, color: 'rgba(134,144,166,0.6)' }}>
|
<div style={{ marginTop: 2, color: 'rgba(134,144,166,0.6)' }}>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export function LayerTree({ layers, enabledLayers, onToggleLayer, layerColors =
|
|||||||
padding: '4px 8px 8px', marginBottom: '4px',
|
padding: '4px 8px 8px', marginBottom: '4px',
|
||||||
borderBottom: '1px solid var(--bd)',
|
borderBottom: '1px solid var(--bd)',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--t3)' }}>
|
||||||
전체 레이어
|
전체 레이어
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -128,7 +128,7 @@ export function TopBar({ activeTab, onTabChange }: TopBarProps) {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showQuickMenu && (
|
{showQuickMenu && (
|
||||||
<div className="absolute top-[44px] right-0 w-[220px] bg-[rgba(18,25,41,0.97)] backdrop-blur-xl border border-border rounded-lg shadow-2xl z-[200] py-2 font-korean" style={{ fontFamily: 'var(--fK)' }}>
|
<div className="absolute top-[44px] right-0 w-[220px] bg-[rgba(18,25,41,0.97)] backdrop-blur-xl border border-border rounded-lg shadow-2xl z-[200] py-2 font-korean">
|
||||||
{/* 거리·면적 계산 */}
|
{/* 거리·면적 계산 */}
|
||||||
<div className="px-3 py-1.5 flex items-center gap-2 text-[11px] font-bold text-text-3">
|
<div className="px-3 py-1.5 flex items-center gap-2 text-[11px] font-bold text-text-3">
|
||||||
<span>📐</span> 거리·면적 계산
|
<span>📐</span> 거리·면적 계산
|
||||||
|
|||||||
@ -49,8 +49,7 @@ export function BacktrackReplayBar({
|
|||||||
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '12px',
|
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '12px',
|
||||||
padding: '12px 18px', zIndex: 1200,
|
padding: '12px 18px', zIndex: 1200,
|
||||||
display: 'flex', flexDirection: 'column', gap: '10px',
|
display: 'flex', flexDirection: 'column', gap: '10px',
|
||||||
fontFamily: 'var(--fK)',
|
}}>
|
||||||
}}>
|
|
||||||
{/* Header row */}
|
{/* Header row */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
@ -58,7 +57,7 @@ export function BacktrackReplayBar({
|
|||||||
width: '8px', height: '8px', borderRadius: '50%',
|
width: '8px', height: '8px', borderRadius: '50%',
|
||||||
background: 'var(--purple)', boxShadow: '0 0 8px rgba(168,85,247,0.5)',
|
background: 'var(--purple)', boxShadow: '0 0 8px rgba(168,85,247,0.5)',
|
||||||
}} />
|
}} />
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)' }}>
|
<span style={{ fontSize: '12px', fontWeight: 700 }}>
|
||||||
역추적 리플레이
|
역추적 리플레이
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -83,7 +82,7 @@ export function BacktrackReplayBar({
|
|||||||
style={{
|
style={{
|
||||||
padding: '4px 10px', borderRadius: '6px', fontSize: '10px', fontWeight: 700,
|
padding: '4px 10px', borderRadius: '6px', fontSize: '10px', fontWeight: 700,
|
||||||
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
|
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
|
||||||
color: 'var(--red)', cursor: 'pointer', fontFamily: 'var(--fK)',
|
color: 'var(--red)', cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
✕ 닫기
|
✕ 닫기
|
||||||
|
|||||||
@ -284,7 +284,7 @@ export function MapView({
|
|||||||
longitude: info.coordinate?.[0] ?? 0,
|
longitude: info.coordinate?.[0] ?? 0,
|
||||||
latitude: info.coordinate?.[1] ?? 0,
|
latitude: info.coordinate?.[1] ?? 0,
|
||||||
content: (
|
content: (
|
||||||
<div className="text-xs" style={{ fontFamily: 'var(--fK)', minWidth: '140px' }}>
|
<div className="text-xs" style={{ minWidth: '140px' }}>
|
||||||
<strong style={{ color: PRIORITY_COLORS[d.priority] }}>{d.name}</strong>
|
<strong style={{ color: PRIORITY_COLORS[d.priority] }}>{d.name}</strong>
|
||||||
<br />
|
<br />
|
||||||
우선순위: {PRIORITY_LABELS[d.priority] || d.priority}
|
우선순위: {PRIORITY_LABELS[d.priority] || d.priority}
|
||||||
@ -389,7 +389,7 @@ export function MapView({
|
|||||||
longitude: incidentCoord.lon,
|
longitude: incidentCoord.lon,
|
||||||
latitude: incidentCoord.lat,
|
latitude: incidentCoord.lat,
|
||||||
content: (
|
content: (
|
||||||
<div className="text-xs" style={{ fontFamily: 'var(--fK)' }}>
|
<div className="text-xs">
|
||||||
<strong style={{ color: 'var(--orange)' }}>{d.level}</strong>
|
<strong style={{ color: 'var(--orange)' }}>{d.level}</strong>
|
||||||
<br />
|
<br />
|
||||||
물질: {dispersionResult.substance}
|
물질: {dispersionResult.substance}
|
||||||
@ -438,7 +438,7 @@ export function MapView({
|
|||||||
longitude: d.lon,
|
longitude: d.lon,
|
||||||
latitude: d.lat,
|
latitude: d.lat,
|
||||||
content: (
|
content: (
|
||||||
<div className="text-xs" style={{ fontFamily: 'var(--fK)', minWidth: '130px' }}>
|
<div className="text-xs" style={{ minWidth: '130px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '4px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '4px' }}>
|
||||||
<span>{SENSITIVE_ICONS[d.type]}</span>
|
<span>{SENSITIVE_ICONS[d.type]}</span>
|
||||||
<strong style={{ color: SENSITIVE_COLORS[d.type] }}>{d.name}</strong>
|
<strong style={{ color: SENSITIVE_COLORS[d.type] }}>{d.name}</strong>
|
||||||
@ -707,7 +707,7 @@ interface MapLegendProps {
|
|||||||
function MapLegend({ dispersionResult, incidentCoord, oilTrajectory = [], boomLines = [], selectedModels = new Set(['OpenDrift'] as PredictionModel[]) }: MapLegendProps) {
|
function MapLegend({ dispersionResult, incidentCoord, oilTrajectory = [], boomLines = [], selectedModels = new Set(['OpenDrift'] as PredictionModel[]) }: MapLegendProps) {
|
||||||
if (dispersionResult && incidentCoord) {
|
if (dispersionResult && incidentCoord) {
|
||||||
return (
|
return (
|
||||||
<div className="absolute top-4 right-4 bg-[rgba(18,25,41,0.95)] backdrop-blur-xl border border-border rounded-lg p-3.5 min-w-[200px]" style={{ fontFamily: 'var(--fK)', zIndex: 20 }}>
|
<div className="absolute top-4 right-4 bg-[rgba(18,25,41,0.95)] backdrop-blur-xl border border-border rounded-lg p-3.5 min-w-[200px]" style={{ zIndex: 20 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '10px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '10px' }}>
|
||||||
<div style={{ fontSize: '16px' }}>📍</div>
|
<div style={{ fontSize: '16px' }}>📍</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -84,7 +84,6 @@ export function ComboBox({ value, onChange, options, placeholder, className }: C
|
|||||||
background: option.value === String(value) ? 'rgba(6,182,212,0.1)' : 'transparent',
|
background: option.value === String(value) ? 'rgba(6,182,212,0.1)' : 'transparent',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: '0.1s',
|
transition: '0.1s',
|
||||||
fontFamily: 'var(--fK)',
|
|
||||||
borderLeft: option.value === String(value) ? '2px solid var(--cyan)' : '2px solid transparent'
|
borderLeft: option.value === String(value) ? '2px solid var(--cyan)' : '2px solid transparent'
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
|
|||||||
54
frontend/src/common/styles/base.css
Normal file
54
frontend/src/common/styles/base.css
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--bg0: #0a0e1a;
|
||||||
|
--bg1: #0f1524;
|
||||||
|
--bg2: #121929;
|
||||||
|
--bg3: #1a2236;
|
||||||
|
--bgH: #1e2844;
|
||||||
|
--bd: #1e2a42;
|
||||||
|
--bdL: #2a3a5c;
|
||||||
|
--t1: #edf0f7;
|
||||||
|
--t2: #b0b8cc;
|
||||||
|
--t3: #8690a6;
|
||||||
|
--blue: #3b82f6;
|
||||||
|
--cyan: #06b6d4;
|
||||||
|
--red: #ef4444;
|
||||||
|
--orange: #f97316;
|
||||||
|
--yellow: #eab308;
|
||||||
|
--green: #22c55e;
|
||||||
|
--purple: #a855f7;
|
||||||
|
--boom: #f59e0b;
|
||||||
|
--boomH: #fbbf24;
|
||||||
|
--fK: Noto Sans KR, sans-serif;
|
||||||
|
--fM: JetBrains Mono, monospace;
|
||||||
|
--rS: 6px;
|
||||||
|
--rM: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Outfit', 'Noto Sans KR', sans-serif;
|
||||||
|
background: var(--bg0);
|
||||||
|
color: var(--t1);
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Date input calendar icon — white for dark theme */
|
||||||
|
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
|
filter: invert(1);
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
input[type="date"]::-webkit-calendar-picker-indicator:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
1224
frontend/src/common/styles/components.css
Normal file
1224
frontend/src/common/styles/components.css
Normal file
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
242
frontend/src/common/styles/wing.css
Normal file
242
frontend/src/common/styles/wing.css
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/* ═══ WING Design System ═══ */
|
||||||
|
/* 프로젝트 전체에서 공유하는 공통 컴포넌트 클래스 */
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
/* ── Layout ── */
|
||||||
|
.wing-panel {
|
||||||
|
@apply flex flex-col h-full overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-panel-scroll {
|
||||||
|
@apply flex-1 overflow-y-auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--bdL) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-header-bar {
|
||||||
|
@apply flex items-center justify-between shrink-0 px-5 border-b border-border;
|
||||||
|
padding-top: 14px;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-sidebar {
|
||||||
|
@apply flex flex-col border-r border-border;
|
||||||
|
background: var(--bg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Card / Section ── */
|
||||||
|
.wing-card {
|
||||||
|
@apply rounded-md p-4 border border-border;
|
||||||
|
background: var(--bg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-card-sm {
|
||||||
|
@apply rounded-sm p-3 border border-border;
|
||||||
|
background: var(--bg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-section {
|
||||||
|
@apply rounded-md p-4 mb-3 border border-border;
|
||||||
|
background: var(--bg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-section-header {
|
||||||
|
@apply text-[13px] font-bold font-korean mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-section-desc {
|
||||||
|
@apply text-[10px] font-korean leading-relaxed;
|
||||||
|
color: var(--t3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Typography ── */
|
||||||
|
.wing-title {
|
||||||
|
@apply text-[15px] font-bold font-korean;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-subtitle {
|
||||||
|
@apply text-[10px] font-korean mt-0.5;
|
||||||
|
color: var(--t3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-label {
|
||||||
|
@apply text-[11px] font-semibold font-korean;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-value {
|
||||||
|
@apply text-[11px] font-mono font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-meta {
|
||||||
|
@apply text-[9px] font-korean;
|
||||||
|
color: var(--t3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Icon Badge ── */
|
||||||
|
.wing-icon-badge {
|
||||||
|
@apply w-10 h-10 rounded-md flex items-center justify-center text-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-icon-badge-sm {
|
||||||
|
@apply w-[38px] h-[38px] rounded-[9px] flex items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Badge ── */
|
||||||
|
.wing-badge {
|
||||||
|
@apply inline-flex items-center px-2 py-0.5 rounded text-[9px] font-bold font-korean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Button ── */
|
||||||
|
.wing-btn {
|
||||||
|
@apply px-3 py-1.5 rounded-sm text-[11px] font-semibold cursor-pointer font-korean border-none;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--cyan), var(--blue));
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-primary:hover {
|
||||||
|
box-shadow: 0 0 16px rgba(6, 182, 212, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-secondary {
|
||||||
|
@apply border border-border;
|
||||||
|
background: var(--bg3);
|
||||||
|
color: var(--t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-secondary:hover {
|
||||||
|
background: var(--bgH);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-outline {
|
||||||
|
@apply bg-transparent border border-border;
|
||||||
|
color: var(--t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-outline:hover {
|
||||||
|
background: var(--bgH);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-pdf {
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||||
|
background: rgba(59, 130, 246, 0.08);
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-btn-danger {
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Input ── */
|
||||||
|
.wing-input {
|
||||||
|
@apply w-full rounded-sm text-[11px] font-korean outline-none;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--bg0);
|
||||||
|
border: 1px solid var(--bd);
|
||||||
|
color: var(--t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-input:focus {
|
||||||
|
border-color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-input::placeholder {
|
||||||
|
color: var(--t3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Table ── */
|
||||||
|
.wing-table {
|
||||||
|
@apply w-full text-[10px] font-korean;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-th {
|
||||||
|
@apply text-left font-semibold;
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: var(--t3);
|
||||||
|
background: var(--bg2);
|
||||||
|
border-bottom: 1px solid var(--bd);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: var(--t2);
|
||||||
|
border-bottom: 1px solid var(--bd);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-tr-hover:hover {
|
||||||
|
background: var(--bgH);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Tab Bar ── */
|
||||||
|
.wing-tab-bar {
|
||||||
|
@apply flex gap-0.5 rounded-lg p-1 border border-border;
|
||||||
|
background: var(--bg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-tab {
|
||||||
|
@apply flex-1 py-2 px-1 text-xs font-semibold rounded-md text-center cursor-pointer font-korean;
|
||||||
|
transition: all 0.15s;
|
||||||
|
color: var(--t3);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-tab:hover {
|
||||||
|
color: var(--t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-tab.active {
|
||||||
|
border-color: rgba(6, 182, 212, 0.3);
|
||||||
|
background: rgba(6, 182, 212, 0.08);
|
||||||
|
color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Modal ── */
|
||||||
|
.wing-overlay {
|
||||||
|
@apply fixed inset-0 flex items-center justify-center;
|
||||||
|
z-index: 10000;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-modal {
|
||||||
|
@apply rounded-xl overflow-hidden;
|
||||||
|
background: var(--bg1);
|
||||||
|
border: 1px solid var(--bd);
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-modal-header {
|
||||||
|
@apply flex items-center justify-between px-5 border-b border-border;
|
||||||
|
padding-top: 14px;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Utility ── */
|
||||||
|
.wing-divider {
|
||||||
|
@apply w-full;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--bd);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-kv-row {
|
||||||
|
@apply flex items-center justify-between;
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-kv-label {
|
||||||
|
@apply text-[10px] font-korean;
|
||||||
|
color: var(--t3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wing-kv-value {
|
||||||
|
@apply text-[11px] font-semibold font-mono;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
frontend/src/common/utils/cn.ts
Normal file
7
frontend/src/common/utils/cn.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* className 조합 유틸리티
|
||||||
|
* falsy 값을 필터링하여 className 문자열을 생성한다.
|
||||||
|
*/
|
||||||
|
export function cn(...classes: (string | false | null | undefined)[]): string {
|
||||||
|
return classes.filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
@ -35,8 +35,8 @@ export function AerialTheoryView() {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
<div style={{ width: '42px', height: '42px', borderRadius: '10px', background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(234,179,8,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px' }}>📐</div>
|
<div style={{ width: '42px', height: '42px', borderRadius: '10px', background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(234,179,8,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px' }}>📐</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>해양 항공탐색 · 원격탐사 이론</div>
|
<div style={{ fontSize: '16px', fontWeight: 700 }}>해양 항공탐색 · 원격탐사 이론</div>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>유출유 원격탐지 · 항공감시 기법 · ESI 방제정보지도 · 등록특허 10-1567431 기반</div>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>유출유 원격탐지 · 항공감시 기법 · ESI 방제정보지도 · 등록특허 10-1567431 기반</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,7 +48,7 @@ export function AerialTheoryView() {
|
|||||||
key={p.id}
|
key={p.id}
|
||||||
onClick={() => setActivePanel(p.id)}
|
onClick={() => setActivePanel(p.id)}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '8px', fontSize: '10px', fontFamily: 'var(--fK)',
|
flex: 1, padding: '8px', fontSize: '10px',
|
||||||
borderRadius: '6px', border: 'none', cursor: 'pointer',
|
borderRadius: '6px', border: 'none', cursor: 'pointer',
|
||||||
background: activePanel === p.id ? 'rgba(6,182,212,.15)' : 'transparent',
|
background: activePanel === p.id ? 'rgba(6,182,212,.15)' : 'transparent',
|
||||||
color: activePanel === p.id ? 'var(--cyan)' : 'var(--t3)',
|
color: activePanel === p.id ? 'var(--cyan)' : 'var(--t3)',
|
||||||
|
|||||||
@ -209,7 +209,7 @@ export function CctvView() {
|
|||||||
<div className="text-4xl opacity-20">📹</div>
|
<div className="text-4xl opacity-20">📹</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-2 left-2 flex items-center gap-1.5">
|
<div className="absolute top-2 left-2 flex items-center gap-1.5">
|
||||||
<span className="text-[9px] font-bold px-1.5 py-0.5 rounded" style={{ background: 'rgba(0,0,0,.7)', color: 'var(--t1)' }}>{cam.cameraNm}</span>
|
<span className="text-[9px] font-bold px-1.5 py-0.5 rounded" style={{ background: 'rgba(0,0,0,.7)' }}>{cam.cameraNm}</span>
|
||||||
<span className="text-[8px] font-bold px-1 py-0.5 rounded" style={{ background: 'rgba(239,68,68,.3)', color: '#f87171' }}>● REC</span>
|
<span className="text-[8px] font-bold px-1 py-0.5 rounded" style={{ background: 'rgba(239,68,68,.3)', color: '#f87171' }}>● REC</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-2 left-2 text-[9px] font-mono px-1.5 py-0.5 rounded" style={{ background: 'rgba(0,0,0,.7)', color: 'var(--t3)' }}>
|
<div className="absolute bottom-2 left-2 text-[9px] font-mono px-1.5 py-0.5 rounded" style={{ background: 'rgba(0,0,0,.7)', color: 'var(--t3)' }}>
|
||||||
|
|||||||
@ -156,13 +156,13 @@ function TheoryCard({ section }: { section: TheorySection }) {
|
|||||||
display: 'flex', alignItems: 'center', gap: '8px',
|
display: 'flex', alignItems: 'center', gap: '8px',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '14px' }}>{section.icon}</span>
|
<span style={{ fontSize: '14px' }}>{section.icon}</span>
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: section.color, fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '12px', fontWeight: 700, color: section.color }}>
|
||||||
{section.title}
|
{section.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Items */}
|
{/* Items */}
|
||||||
<div style={{ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '9px' }}>
|
||||||
{section.items.map((item, i) => (
|
{section.items.map((item, i) => (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
@ -190,7 +190,7 @@ function TheoryCard({ section }: { section: TheorySection }) {
|
|||||||
{['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩'][i]}
|
{['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩'][i]}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: 'var(--t1)', fontWeight: 700, marginBottom: '2px' }}>
|
<div style={{ fontWeight: 700, marginBottom: '2px' }}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: 'var(--t3)', lineHeight: '1.6' }}>
|
<div style={{ color: 'var(--t3)', lineHeight: '1.6' }}>
|
||||||
@ -227,10 +227,10 @@ function TheoryCard({ section }: { section: TheorySection }) {
|
|||||||
function AssetTheory() {
|
function AssetTheory() {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
|
||||||
<div style={{ fontSize: '18px', fontWeight: 700, fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
<div style={{ fontSize: '18px', fontWeight: 700, marginBottom: '4px' }}>
|
||||||
📚 방제자원 이론
|
📚 방제자원 이론
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '12px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '24px' }}>
|
<div style={{ fontSize: '12px', color: 'var(--t3)', marginBottom: '24px' }}>
|
||||||
방제자산 운용 기준·성능 이론 및 관련 법령·고시 근거 문헌
|
방제자산 운용 기준·성능 이론 및 관련 법령·고시 근거 문헌
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -92,10 +92,10 @@ function ShipInsurance() {
|
|||||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 20 }}>
|
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 20 }}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
|
||||||
<div style={{ fontSize: 18, fontWeight: 700, fontFamily: 'var(--fK)' }}>🛡 선박 보험정보 조회</div>
|
<div style={{ fontSize: 18, fontWeight: 700 }}>🛡 선박 보험정보 조회</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 5, padding: '3px 10px', borderRadius: 10,
|
display: 'flex', alignItems: 'center', gap: 5, padding: '3px 10px', borderRadius: 10,
|
||||||
fontSize: 10, fontWeight: 700, fontFamily: 'var(--fK)',
|
fontSize: 10, fontWeight: 700,
|
||||||
background: apiConnected ? 'rgba(34,197,94,.12)' : 'rgba(239,68,68,.12)',
|
background: apiConnected ? 'rgba(34,197,94,.12)' : 'rgba(239,68,68,.12)',
|
||||||
color: apiConnected ? 'var(--green)' : 'var(--red)',
|
color: apiConnected ? 'var(--green)' : 'var(--red)',
|
||||||
border: `1px solid ${apiConnected ? 'rgba(34,197,94,.25)' : 'rgba(239,68,68,.25)'}`,
|
border: `1px solid ${apiConnected ? 'rgba(34,197,94,.25)' : 'rgba(239,68,68,.25)'}`,
|
||||||
@ -104,31 +104,31 @@ function ShipInsurance() {
|
|||||||
{apiConnected ? 'API 연결됨' : 'API 미연결'}
|
{apiConnected ? 'API 연결됨' : 'API 미연결'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 12, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>한국해운조합(KSA) Open API 연동 · 선박 P&I 보험 및 선주 책임보험 실시간 조회</div>
|
<div style={{ fontSize: 12, color: 'var(--t3)' }}>한국해운조합(KSA) Open API 연동 · 선박 P&I 보험 및 선주 책임보험 실시간 조회</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<button onClick={handleTestConnect} style={{ padding: '8px 16px', background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔌 연결 테스트</button>
|
<button onClick={handleTestConnect} style={{ padding: '8px 16px', background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>🔌 연결 테스트</button>
|
||||||
<button onClick={() => setShowConfig(v => !v)} style={{ padding: '8px 16px', background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>⚙ API 설정</button>
|
<button onClick={() => setShowConfig(v => !v)} style={{ padding: '8px 16px', background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>⚙ API 설정</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── API 설정 패널 ── */}
|
{/* ── API 설정 패널 ── */}
|
||||||
{showConfig && (
|
{showConfig && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', padding: '20px 24px', marginBottom: 20 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', padding: '20px 24px', marginBottom: 20 }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 700, fontFamily: 'var(--fK)', marginBottom: 14, color: 'var(--cyan)' }}>⚙ 한국해운조합 API 연동 설정</div>
|
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 14, color: 'var(--cyan)' }}>⚙ 한국해운조합 API 연동 설정</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 5 }}>API Endpoint URL</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}>API Endpoint URL</label>
|
||||||
<input type="text" value={configEndpoint} onChange={e => setConfigEndpoint(e.target.value)} placeholder="https://api.haewoon.or.kr/v1/..."
|
<input type="text" value={configEndpoint} onChange={e => setConfigEndpoint(e.target.value)} placeholder="https://api.haewoon.or.kr/v1/..."
|
||||||
style={{ width: '100%', padding: '9px 12px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', color: 'var(--t1)', fontFamily: 'var(--fM)', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '9px 12px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 5 }}>API Key</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}>API Key</label>
|
||||||
<input type="password" value={configApiKey} onChange={e => setConfigApiKey(e.target.value)} placeholder="발급받은 API Key 입력"
|
<input type="password" value={configApiKey} onChange={e => setConfigApiKey(e.target.value)} placeholder="발급받은 API Key 입력"
|
||||||
style={{ width: '100%', padding: '9px 12px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', color: 'var(--t1)', fontFamily: 'var(--fM)', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '9px 12px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 5 }}>조회 기본값 — 조회 키 타입</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}>조회 기본값 — 조회 키 타입</label>
|
||||||
<select value={configKeyType} onChange={e => setConfigKeyType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', width: '100%' }}>
|
<select value={configKeyType} onChange={e => setConfigKeyType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', width: '100%' }}>
|
||||||
<option value="mmsi">MMSI</option>
|
<option value="mmsi">MMSI</option>
|
||||||
<option value="imo">IMO 번호</option>
|
<option value="imo">IMO 번호</option>
|
||||||
@ -137,7 +137,7 @@ function ShipInsurance() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 5 }}>응답 형식</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}>응답 형식</label>
|
||||||
<select value={configRespType} onChange={e => setConfigRespType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', width: '100%' }}>
|
<select value={configRespType} onChange={e => setConfigRespType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', width: '100%' }}>
|
||||||
<option value="json">JSON</option>
|
<option value="json">JSON</option>
|
||||||
<option value="xml">XML</option>
|
<option value="xml">XML</option>
|
||||||
@ -145,11 +145,11 @@ function ShipInsurance() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<button onClick={handleSaveConfig} style={{ padding: '9px 20px', background: 'linear-gradient(135deg, var(--cyan), var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 700, cursor: 'pointer', fontFamily: 'var(--fK)' }}>💾 저장</button>
|
<button onClick={handleSaveConfig} style={{ padding: '9px 20px', background: 'linear-gradient(135deg, var(--cyan), var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 700, cursor: 'pointer' }}>💾 저장</button>
|
||||||
<button onClick={() => setShowConfig(false)} style={{ padding: '9px 16px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, cursor: 'pointer', fontFamily: 'var(--fK)' }}>취소</button>
|
<button onClick={() => setShowConfig(false)} style={{ padding: '9px 16px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, cursor: 'pointer' }}>취소</button>
|
||||||
</div>
|
</div>
|
||||||
{/* API 연동 안내 */}
|
{/* API 연동 안내 */}
|
||||||
<div style={{ marginTop: 16, padding: '12px 16px', background: 'rgba(6,182,212,.05)', border: '1px solid rgba(6,182,212,.15)', borderRadius: 'var(--rS)', fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.8 }}>
|
<div style={{ marginTop: 16, padding: '12px 16px', background: 'rgba(6,182,212,.05)', border: '1px solid rgba(6,182,212,.15)', borderRadius: 'var(--rS)', fontSize: 10, color: 'var(--t3)', lineHeight: 1.8 }}>
|
||||||
<span style={{ color: 'var(--cyan)', fontWeight: 700 }}>📋 한국해운조합 API 발급 안내</span><br />
|
<span style={{ color: 'var(--cyan)', fontWeight: 700 }}>📋 한국해운조합 API 발급 안내</span><br />
|
||||||
• 한국해운조합 공공데이터포털 또는 해운조합 IT지원팀에 API 키 신청<br />
|
• 한국해운조합 공공데이터포털 또는 해운조합 IT지원팀에 API 키 신청<br />
|
||||||
• 해양경찰청 기관 계정으로 신청 시 전용 엔드포인트 및 키 발급<br />
|
• 해양경찰청 기관 계정으로 신청 시 전용 엔드포인트 및 키 발급<br />
|
||||||
@ -160,10 +160,10 @@ function ShipInsurance() {
|
|||||||
|
|
||||||
{/* ── 검색 영역 ── */}
|
{/* ── 검색 영역 ── */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', padding: '18px 20px', marginBottom: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', padding: '18px 20px', marginBottom: 16 }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, fontFamily: 'var(--fK)', marginBottom: 12, color: 'var(--t2)' }}>🔍 보험정보 조회</div>
|
<div style={{ fontSize: 12, fontWeight: 700, marginBottom: 12, color: 'var(--t2)' }}>🔍 보험정보 조회</div>
|
||||||
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-end', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-end', flexWrap: 'wrap' }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 4 }}>조회 키 타입</label>
|
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', marginBottom: 4 }}>조회 키 타입</label>
|
||||||
<select value={searchType} onChange={e => setSearchType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', minWidth: 120 }}>
|
<select value={searchType} onChange={e => setSearchType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', minWidth: 120 }}>
|
||||||
<option value="mmsi">MMSI</option>
|
<option value="mmsi">MMSI</option>
|
||||||
<option value="imo">IMO 번호</option>
|
<option value="imo">IMO 번호</option>
|
||||||
@ -172,12 +172,12 @@ function ShipInsurance() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 220 }}>
|
<div style={{ flex: 1, minWidth: 220 }}>
|
||||||
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 4 }}>조회값</label>
|
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', marginBottom: 4 }}>조회값</label>
|
||||||
<input type="text" value={searchVal} onChange={e => setSearchVal(e.target.value)} placeholder={placeholderMap[searchType]}
|
<input type="text" value={searchVal} onChange={e => setSearchVal(e.target.value)} placeholder={placeholderMap[searchType]}
|
||||||
style={{ width: '100%', padding: '9px 14px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', color: 'var(--t1)', fontFamily: 'var(--fM)', fontSize: 13, outline: 'none', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '9px 14px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: 13, outline: 'none', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 4 }}>보험 종류</label>
|
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', marginBottom: 4 }}>보험 종류</label>
|
||||||
<select value={insTypeFilter} onChange={e => setInsTypeFilter(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', minWidth: 140 }}>
|
<select value={insTypeFilter} onChange={e => setInsTypeFilter(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', minWidth: 140 }}>
|
||||||
<option>전체</option>
|
<option>전체</option>
|
||||||
<option>P&I 보험</option>
|
<option>P&I 보험</option>
|
||||||
@ -186,8 +186,8 @@ function ShipInsurance() {
|
|||||||
<option>방제보증보험</option>
|
<option>방제보증보험</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleQuery} style={{ padding: '9px 24px', background: 'linear-gradient(135deg, var(--cyan), var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: 13, fontWeight: 700, cursor: 'pointer', fontFamily: 'var(--fK)', flexShrink: 0 }}>🔍 조회</button>
|
<button onClick={handleQuery} style={{ padding: '9px 24px', background: 'linear-gradient(135deg, var(--cyan), var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: 13, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>🔍 조회</button>
|
||||||
<button onClick={handleBatchQuery} style={{ padding: '9px 18px', background: 'rgba(168,85,247,.12)', color: 'var(--purple)', border: '1px solid rgba(168,85,247,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)', flexShrink: 0 }}>📋 자산목록 일괄조회</button>
|
<button onClick={handleBatchQuery} style={{ padding: '9px 18px', background: 'rgba(168,85,247,.12)', color: 'var(--purple)', border: '1px solid rgba(168,85,247,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', flexShrink: 0 }}>📋 자산목록 일괄조회</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -197,15 +197,15 @@ function ShipInsurance() {
|
|||||||
{viewState === 'empty' && (
|
{viewState === 'empty' && (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '60px 20px', background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '60px 20px', background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)' }}>
|
||||||
<div style={{ fontSize: 48, marginBottom: 16, opacity: 0.3 }}>🛡</div>
|
<div style={{ fontSize: 48, marginBottom: 16, opacity: 0.3 }}>🛡</div>
|
||||||
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 8 }}>한국해운조합 API 연동 대기 중</div>
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--t2)', marginBottom: 8 }}>한국해운조합 API 연동 대기 중</div>
|
||||||
<div style={{ fontSize: 12, color: 'var(--t3)', fontFamily: 'var(--fK)', textAlign: 'center', lineHeight: 1.8 }}>
|
<div style={{ fontSize: 12, color: 'var(--t3)', textAlign: 'center', lineHeight: 1.8 }}>
|
||||||
API 설정에서 한국해운조합 API Key를 등록하거나<br />
|
API 설정에서 한국해운조합 API Key를 등록하거나<br />
|
||||||
MMSI·IMO·선박명으로 직접 조회하세요.<br />
|
MMSI·IMO·선박명으로 직접 조회하세요.<br />
|
||||||
<span style={{ color: 'var(--cyan)' }}>자산목록 일괄조회</span> 시 등록된 방제자산 전체의 보험 현황을 한번에 확인할 수 있습니다.
|
<span style={{ color: 'var(--cyan)' }}>자산목록 일괄조회</span> 시 등록된 방제자산 전체의 보험 현황을 한번에 확인할 수 있습니다.
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 20, display: 'flex', gap: 10 }}>
|
<div style={{ marginTop: 20, display: 'flex', gap: 10 }}>
|
||||||
<button onClick={() => setShowConfig(true)} style={{ padding: '10px 20px', background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>⚙ API 설정</button>
|
<button onClick={() => setShowConfig(true)} style={{ padding: '10px 20px', background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>⚙ API 설정</button>
|
||||||
<button onClick={loadDemoData} style={{ padding: '10px 20px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📊 샘플 데이터 보기</button>
|
<button onClick={loadDemoData} style={{ padding: '10px 20px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>📊 샘플 데이터 보기</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -214,7 +214,7 @@ function ShipInsurance() {
|
|||||||
{viewState === 'loading' && (
|
{viewState === 'loading' && (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 60, background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 60, background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)' }}>
|
||||||
<div style={{ width: 36, height: 36, border: '3px solid var(--bd)', borderTopColor: 'var(--cyan)', borderRadius: '50%', animation: 'spin 0.8s linear infinite', marginBottom: 14 }} />
|
<div style={{ width: 36, height: 36, border: '3px solid var(--bd)', borderTopColor: 'var(--cyan)', borderRadius: '50%', animation: 'spin 0.8s linear infinite', marginBottom: 14 }} />
|
||||||
<div style={{ fontSize: 13, color: 'var(--t2)', fontFamily: 'var(--fK)' }}>한국해운조합 API 조회 중...</div>
|
<div style={{ fontSize: 13, color: 'var(--t2)' }}>한국해운조합 API 조회 중...</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ function ShipInsurance() {
|
|||||||
].map((c, i) => (
|
].map((c, i) => (
|
||||||
<div key={i} style={{ padding: '14px 16px', background: c.bg, border: `1px solid ${c.color}33`, borderRadius: 'var(--rS)', textAlign: 'center' }}>
|
<div key={i} style={{ padding: '14px 16px', background: c.bg, border: `1px solid ${c.color}33`, borderRadius: 'var(--rS)', textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: 22, fontWeight: 800, color: c.color, fontFamily: 'var(--fM)' }}>{c.val}</div>
|
<div style={{ fontSize: 22, fontWeight: 800, color: c.color, fontFamily: 'var(--fM)' }}>{c.val}</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 2 }}>{c.label}</div>
|
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}>{c.label}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -239,14 +239,14 @@ function ShipInsurance() {
|
|||||||
{/* 테이블 */}
|
{/* 테이블 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', overflow: 'hidden', marginBottom: 12 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', overflow: 'hidden', marginBottom: 12 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', borderBottom: '1px solid var(--bd)' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', borderBottom: '1px solid var(--bd)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>조회 결과 <span style={{ color: 'var(--cyan)' }}>{resultData.length}</span>건</div>
|
<div style={{ fontSize: 12, fontWeight: 700 }}>조회 결과 <span style={{ color: 'var(--cyan)' }}>{resultData.length}</span>건</div>
|
||||||
<div style={{ display: 'flex', gap: 6 }}>
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
<button onClick={() => alert('엑셀 내보내기 기능은 실제 API 연동 후 활성화됩니다.')} style={{ padding: '5px 12px', background: 'rgba(34,197,94,.1)', color: 'var(--green)', border: '1px solid rgba(34,197,94,.25)', borderRadius: 'var(--rS)', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📥 엑셀 내보내기</button>
|
<button onClick={() => alert('엑셀 내보내기 기능은 실제 API 연동 후 활성화됩니다.')} style={{ padding: '5px 12px', background: 'rgba(34,197,94,.1)', color: 'var(--green)', border: '1px solid rgba(34,197,94,.25)', borderRadius: 'var(--rS)', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>📥 엑셀 내보내기</button>
|
||||||
<button onClick={handleQuery} style={{ padding: '5px 12px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 11, cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔄 새로고침</button>
|
<button onClick={handleQuery} style={{ padding: '5px 12px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 11, cursor: 'pointer' }}>🔄 새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ overflowX: 'auto' }}>
|
<div style={{ overflowX: 'auto' }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11, fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11 }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'var(--bg0)' }}>
|
<tr style={{ background: 'var(--bg0)' }}>
|
||||||
{[
|
{[
|
||||||
@ -298,7 +298,7 @@ function ShipInsurance() {
|
|||||||
|
|
||||||
{/* 경고 */}
|
{/* 경고 */}
|
||||||
{(expiredList.length > 0 || soonList.length > 0) && (
|
{(expiredList.length > 0 || soonList.length > 0) && (
|
||||||
<div style={{ padding: '12px 16px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.25)', borderRadius: 'var(--rS)', fontSize: 12, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 12 }}>
|
<div style={{ padding: '12px 16px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.25)', borderRadius: 'var(--rS)', fontSize: 12, color: 'var(--t2)', marginBottom: 12 }}>
|
||||||
{expiredList.length > 0 && (
|
{expiredList.length > 0 && (
|
||||||
<><span style={{ color: 'var(--red)', fontWeight: 700 }}>⛔ 만료 {expiredList.length}건:</span> {expiredList.map(r => r.shipName).join(', ')}<br /></>
|
<><span style={{ color: 'var(--red)', fontWeight: 700 }}>⛔ 만료 {expiredList.length}건:</span> {expiredList.map(r => r.shipName).join(', ')}<br /></>
|
||||||
)}
|
)}
|
||||||
@ -312,14 +312,14 @@ function ShipInsurance() {
|
|||||||
|
|
||||||
{/* ── API 연동 정보 푸터 ── */}
|
{/* ── API 연동 정보 푸터 ── */}
|
||||||
<div style={{ marginTop: 16, padding: '12px 16px', background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ marginTop: 16, padding: '12px 16px', background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.7 }}>
|
<div style={{ fontSize: 10, color: 'var(--t3)', lineHeight: 1.7 }}>
|
||||||
<span style={{ color: 'var(--t2)', fontWeight: 700 }}>데이터 출처:</span> 한국해운조합(KSA) · haewoon.or.kr<br />
|
<span style={{ color: 'var(--t2)', fontWeight: 700 }}>데이터 출처:</span> 한국해운조합(KSA) · haewoon.or.kr<br />
|
||||||
<span style={{ color: 'var(--t2)', fontWeight: 700 }}>연동 방식:</span> REST API (JSON) · 실시간 조회 · 캐시 TTL 1시간
|
<span style={{ color: 'var(--t2)', fontWeight: 700 }}>연동 방식:</span> REST API (JSON) · 실시간 조회 · 캐시 TTL 1시간
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
||||||
<span style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>마지막 동기화:</span>
|
<span style={{ fontSize: 10, color: 'var(--t3)' }}>마지막 동기화:</span>
|
||||||
<span style={{ fontSize: 10, color: 'var(--t2)', fontFamily: 'var(--fM)' }}>{lastSync}</span>
|
<span style={{ fontSize: 10, color: 'var(--t2)', fontFamily: 'var(--fM)' }}>{lastSync}</span>
|
||||||
<button onClick={handleFullSync} style={{ padding: '4px 10px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 4, fontSize: 10, cursor: 'pointer', fontFamily: 'var(--fK)' }}>전체 동기화</button>
|
<button onClick={handleFullSync} style={{ padding: '4px 10px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 4, fontSize: 10, cursor: 'pointer' }}>전체 동기화</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -210,16 +210,15 @@ export function BoardView() {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span style={{ fontSize: 18 }}>📘</span>
|
<span style={{ fontSize: 18 }}>📘</span>
|
||||||
<span className="text-[15px] font-bold" style={{ fontFamily: 'var(--fK)', color: 'var(--t1)' }}>해경매뉴얼</span>
|
<span className="text-[15px] font-bold">해경매뉴얼</span>
|
||||||
<span className="text-[10px] ml-1" style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>총 {filteredManuals.length}건</span>
|
<span className="text-[10px] ml-1" style={{ color: 'var(--t3)' }}>총 {filteredManuals.length}건</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1 ml-4">
|
<div className="flex gap-1 ml-4">
|
||||||
{manualCategories.map(cat => (
|
{manualCategories.map(cat => (
|
||||||
<button key={cat} onClick={() => setManualCategory(cat)}
|
<button key={cat} onClick={() => setManualCategory(cat)}
|
||||||
className="px-3 py-1.5 text-[11px] font-semibold rounded-md transition-all"
|
className="px-3 py-1.5 text-[11px] font-semibold rounded-md transition-all"
|
||||||
style={{
|
style={{
|
||||||
fontFamily: 'var(--fK)',
|
background: manualCategory === cat ? 'rgba(6,182,212,.15)' : 'var(--bg3)',
|
||||||
background: manualCategory === cat ? 'rgba(6,182,212,.15)' : 'var(--bg3)',
|
|
||||||
border: manualCategory === cat ? '1px solid rgba(6,182,212,.3)' : '1px solid var(--bd)',
|
border: manualCategory === cat ? '1px solid rgba(6,182,212,.3)' : '1px solid var(--bd)',
|
||||||
color: manualCategory === cat ? 'var(--cyan)' : 'var(--t3)',
|
color: manualCategory === cat ? 'var(--cyan)' : 'var(--t3)',
|
||||||
}}>
|
}}>
|
||||||
@ -230,10 +229,10 @@ export function BoardView() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<input type="text" placeholder="매뉴얼 검색..." value={manualSearch} onChange={e => setManualSearch(e.target.value)}
|
<input type="text" placeholder="매뉴얼 검색..." value={manualSearch} onChange={e => setManualSearch(e.target.value)}
|
||||||
className="px-4 py-2 text-sm rounded w-64" style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', color: 'var(--t1)', fontFamily: 'var(--fK)', outline: 'none' }} />
|
className="px-4 py-2 text-sm rounded w-64" style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', outline: 'none' }} />
|
||||||
<button onClick={() => setShowUploadModal(true)}
|
<button onClick={() => setShowUploadModal(true)}
|
||||||
className="px-5 py-2 text-[12px] font-semibold rounded-md transition-all flex items-center gap-1.5"
|
className="px-5 py-2 text-[12px] font-semibold rounded-md transition-all flex items-center gap-1.5"
|
||||||
style={{ background: 'rgba(6,182,212,.15)', border: '1px solid rgba(6,182,212,.3)', color: '#22d3ee', fontFamily: 'var(--fK)', cursor: 'pointer' }}>
|
style={{ background: 'rgba(6,182,212,.15)', border: '1px solid rgba(6,182,212,.3)', color: '#22d3ee', cursor: 'pointer' }}>
|
||||||
📤 새로 업로드
|
📤 새로 업로드
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -243,7 +242,7 @@ export function BoardView() {
|
|||||||
<div className="flex-1 overflow-auto px-8 py-6">
|
<div className="flex-1 overflow-auto px-8 py-6">
|
||||||
{manualLoading ? (
|
{manualLoading ? (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<p className="text-sm" style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>로딩 중...</p>
|
<p className="text-sm" style={{ color: 'var(--t3)' }}>로딩 중...</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))' }}>
|
<div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))' }}>
|
||||||
@ -265,7 +264,7 @@ export function BoardView() {
|
|||||||
{file.version}
|
{file.version}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[12px] font-bold mb-3 leading-[1.5]" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[12px] font-bold mb-3 leading-[1.5]">
|
||||||
{file.title}
|
{file.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
@ -289,7 +288,7 @@ export function BoardView() {
|
|||||||
setShowUploadModal(true)
|
setShowUploadModal(true)
|
||||||
}}
|
}}
|
||||||
className="px-2 py-0.5 rounded text-[10px] font-semibold transition-all"
|
className="px-2 py-0.5 rounded text-[10px] font-semibold transition-all"
|
||||||
style={{ background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', color: '#3b82f6', fontFamily: 'var(--fK)', cursor: 'pointer' }}
|
style={{ background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', color: '#3b82f6', cursor: 'pointer' }}
|
||||||
title="수정">
|
title="수정">
|
||||||
✏️ 수정
|
✏️ 수정
|
||||||
</button>
|
</button>
|
||||||
@ -305,13 +304,13 @@ export function BoardView() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="px-2 py-0.5 rounded text-[10px] font-semibold transition-all"
|
className="px-2 py-0.5 rounded text-[10px] font-semibold transition-all"
|
||||||
style={{ background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)', color: '#ef4444', fontFamily: 'var(--fK)', cursor: 'pointer' }}
|
style={{ background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)', color: '#ef4444', cursor: 'pointer' }}
|
||||||
title="삭제">
|
title="삭제">
|
||||||
🗑️ 삭제
|
🗑️ 삭제
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between pt-3" style={{ borderTop: '1px solid var(--bd)' }}>
|
<div className="flex items-center justify-between pt-3" style={{ borderTop: '1px solid var(--bd)' }}>
|
||||||
<div className="flex items-center gap-3 text-[10px]" style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div className="flex items-center gap-3 text-[10px]" style={{ color: 'var(--t3)' }}>
|
||||||
<span>{file.authorNm}</span>
|
<span>{file.authorNm}</span>
|
||||||
<span>{new Date(file.regDtm).toLocaleDateString('ko-KR')}</span>
|
<span>{new Date(file.regDtm).toLocaleDateString('ko-KR')}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -353,7 +352,7 @@ export function BoardView() {
|
|||||||
}}
|
}}
|
||||||
className="px-3 py-1 rounded text-[10px] font-semibold transition-all" style={{
|
className="px-3 py-1 rounded text-[10px] font-semibold transition-all" style={{
|
||||||
background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.25)',
|
background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.25)',
|
||||||
color: '#22d3ee', fontFamily: 'var(--fK)', cursor: 'pointer',
|
color: '#22d3ee', cursor: 'pointer',
|
||||||
}}>
|
}}>
|
||||||
📥 다운로드
|
📥 다운로드
|
||||||
</button>
|
</button>
|
||||||
@ -368,7 +367,7 @@ export function BoardView() {
|
|||||||
{!manualLoading && filteredManuals.length === 0 && (
|
{!manualLoading && filteredManuals.length === 0 && (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<div style={{ fontSize: 32, opacity: 0.3, marginBottom: 8 }}>📘</div>
|
<div style={{ fontSize: 32, opacity: 0.3, marginBottom: 8 }}>📘</div>
|
||||||
<p className="text-sm" style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>검색 결과가 없습니다.</p>
|
<p className="text-sm" style={{ color: 'var(--t3)' }}>검색 결과가 없습니다.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -383,14 +382,14 @@ export function BoardView() {
|
|||||||
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
<span style={{ fontSize: 16 }}>{editingManualId ? '✏️' : '📤'}</span>
|
<span style={{ fontSize: 16 }}>{editingManualId ? '✏️' : '📤'}</span>
|
||||||
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>{editingManualId ? '매뉴얼 수정' : '매뉴얼 업로드'}</span>
|
<span style={{ fontSize: 14, fontWeight: 700 }}>{editingManualId ? '매뉴얼 수정' : '매뉴얼 업로드'}</span>
|
||||||
</div>
|
</div>
|
||||||
<span onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}
|
<span onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}
|
||||||
style={{ cursor: 'pointer', color: 'var(--t3)', fontSize: 16, lineHeight: 1 }}>✕</span>
|
style={{ cursor: 'pointer', color: 'var(--t3)', fontSize: 16, lineHeight: 1 }}>✕</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 6 }}>카테고리</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}>카테고리</label>
|
||||||
<div style={{ display: 'flex', gap: 6 }}>
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
{['방제매뉴얼', '대응매뉴얼', '교육자료', '법령·규정'].map(cat => {
|
{['방제매뉴얼', '대응매뉴얼', '교육자료', '법령·규정'].map(cat => {
|
||||||
const cc = catColor(cat)
|
const cc = catColor(cat)
|
||||||
@ -399,7 +398,7 @@ export function BoardView() {
|
|||||||
<button key={cat} onClick={() => setUploadForm(prev => ({ ...prev, category: cat }))}
|
<button key={cat} onClick={() => setUploadForm(prev => ({ ...prev, category: cat }))}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '8px 4px', borderRadius: 6, fontSize: 11, fontWeight: 600,
|
flex: 1, padding: '8px 4px', borderRadius: 6, fontSize: 11, fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: isActive ? cc.bg : 'var(--bg3)',
|
background: isActive ? cc.bg : 'var(--bg3)',
|
||||||
border: isActive ? `1px solid ${cc.text}40` : '1px solid var(--bd)',
|
border: isActive ? `1px solid ${cc.text}40` : '1px solid var(--bd)',
|
||||||
color: isActive ? cc.text : 'var(--t3)',
|
color: isActive ? cc.text : 'var(--t3)',
|
||||||
@ -411,19 +410,19 @@ export function BoardView() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 6 }}>매뉴얼 제목</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}>매뉴얼 제목</label>
|
||||||
<input type="text" placeholder="매뉴얼 제목을 입력하세요" value={uploadForm.title}
|
<input type="text" placeholder="매뉴얼 제목을 입력하세요" value={uploadForm.title}
|
||||||
onChange={e => setUploadForm(prev => ({ ...prev, title: e.target.value }))}
|
onChange={e => setUploadForm(prev => ({ ...prev, title: e.target.value }))}
|
||||||
style={{ width: '100%', padding: '10px 12px', borderRadius: 6, fontSize: 12, background: 'var(--bg2)', border: '1px solid var(--bd)', color: 'var(--t1)', fontFamily: 'var(--fK)', outline: 'none', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '10px 12px', borderRadius: 6, fontSize: 12, background: 'var(--bg2)', border: '1px solid var(--bd)', outline: 'none', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 6 }}>버전</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}>버전</label>
|
||||||
<input type="text" placeholder="예: v1.0" value={uploadForm.version}
|
<input type="text" placeholder="예: v1.0" value={uploadForm.version}
|
||||||
onChange={e => setUploadForm(prev => ({ ...prev, version: e.target.value }))}
|
onChange={e => setUploadForm(prev => ({ ...prev, version: e.target.value }))}
|
||||||
style={{ width: '100%', padding: '10px 12px', borderRadius: 6, fontSize: 12, background: 'var(--bg2)', border: '1px solid var(--bd)', color: 'var(--t1)', fontFamily: 'var(--fK)', outline: 'none', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '10px 12px', borderRadius: 6, fontSize: 12, background: 'var(--bg2)', border: '1px solid var(--bd)', outline: 'none', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: 6 }}>파일 첨부</label>
|
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}>파일 첨부</label>
|
||||||
<div style={{
|
<div style={{
|
||||||
border: '2px dashed var(--bd)', borderRadius: 8, padding: '24px 16px', textAlign: 'center',
|
border: '2px dashed var(--bd)', borderRadius: 8, padding: '24px 16px', textAlign: 'center',
|
||||||
background: 'var(--bg2)', cursor: 'pointer', position: 'relative',
|
background: 'var(--bg2)', cursor: 'pointer', position: 'relative',
|
||||||
@ -445,7 +444,7 @@ export function BoardView() {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
|
||||||
<span style={{ fontSize: 20 }}>📄</span>
|
<span style={{ fontSize: 20 }}>📄</span>
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>{uploadForm.fileName}</div>
|
<div style={{ fontSize: 12, fontWeight: 600 }}>{uploadForm.fileName}</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{uploadForm.fileSize}</div>
|
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{uploadForm.fileSize}</div>
|
||||||
</div>
|
</div>
|
||||||
<span onClick={(e) => { e.stopPropagation(); setUploadForm(prev => ({ ...prev, fileName: '', fileSize: '' })) }}
|
<span onClick={(e) => { e.stopPropagation(); setUploadForm(prev => ({ ...prev, fileName: '', fileSize: '' })) }}
|
||||||
@ -454,7 +453,7 @@ export function BoardView() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={{ fontSize: 28, opacity: 0.3, marginBottom: 6 }}>📁</div>
|
<div style={{ fontSize: 28, opacity: 0.3, marginBottom: 6 }}>📁</div>
|
||||||
<div style={{ fontSize: 11, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>클릭하여 파일을 선택하세요</div>
|
<div style={{ fontSize: 11, color: 'var(--t3)' }}>클릭하여 파일을 선택하세요</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)', marginTop: 4 }}>PDF, DOC, HWP, XLSX (최대 100MB)</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)', marginTop: 4 }}>PDF, DOC, HWP, XLSX (최대 100MB)</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -463,7 +462,7 @@ export function BoardView() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--bd)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--bd)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
||||||
<button onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}
|
<button onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}
|
||||||
style={{ padding: '8px 20px', borderRadius: 6, fontSize: 12, fontWeight: 600, background: 'var(--bg3)', border: '1px solid var(--bd)', color: 'var(--t3)', fontFamily: 'var(--fK)', cursor: 'pointer' }}>
|
style={{ padding: '8px 20px', borderRadius: 6, fontSize: 12, fontWeight: 600, background: 'var(--bg3)', border: '1px solid var(--bd)', color: 'var(--t3)', cursor: 'pointer' }}>
|
||||||
취소
|
취소
|
||||||
</button>
|
</button>
|
||||||
<button onClick={async () => {
|
<button onClick={async () => {
|
||||||
@ -496,7 +495,7 @@ export function BoardView() {
|
|||||||
alert((err as { message?: string })?.message || '저장에 실패했습니다.')
|
alert((err as { message?: string })?.message || '저장에 실패했습니다.')
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{ padding: '8px 24px', borderRadius: 6, fontSize: 12, fontWeight: 600, background: 'rgba(6,182,212,.2)', border: '1px solid rgba(6,182,212,.35)', color: '#22d3ee', fontFamily: 'var(--fK)', cursor: 'pointer' }}>
|
style={{ padding: '8px 24px', borderRadius: 6, fontSize: 12, fontWeight: 600, background: 'rgba(6,182,212,.2)', border: '1px solid rgba(6,182,212,.35)', color: '#22d3ee', cursor: 'pointer' }}>
|
||||||
{editingManualId ? '✏️ 수정' : '📤 업로드'}
|
{editingManualId ? '✏️ 수정' : '📤 업로드'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -60,8 +60,7 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: 'var(--bg0)',
|
background: 'var(--bg0)',
|
||||||
fontFamily: 'var(--fK)'
|
}}>
|
||||||
}}>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '16px 20px',
|
padding: '16px 20px',
|
||||||
@ -75,8 +74,7 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
|
|||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'var(--t1)',
|
display: 'flex',
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '8px'
|
gap: '8px'
|
||||||
}}>
|
}}>
|
||||||
@ -104,10 +102,8 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
|
|||||||
border: '1px solid var(--bd)',
|
border: '1px solid var(--bd)',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
color: 'var(--t1)',
|
width: '200px',
|
||||||
width: '200px',
|
}}
|
||||||
fontFamily: 'var(--fK)'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => onTabChange('analysis')}
|
onClick={() => onTabChange('analysis')}
|
||||||
@ -120,8 +116,7 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
|
|||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontFamily: 'var(--fK)',
|
display: 'flex',
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '4px'
|
gap: '4px'
|
||||||
}}
|
}}
|
||||||
@ -202,7 +197,7 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
|
|||||||
onMouseLeave={(e) => e.currentTarget.style.background = index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)'}
|
onMouseLeave={(e) => e.currentTarget.style.background = index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)'}
|
||||||
>
|
>
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{item.hnsAnlysSn}</td>
|
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{item.hnsAnlysSn}</td>
|
||||||
<td style={{ padding: '12px 16px', color: 'var(--t1)', fontWeight: 500 }}>{item.anlysNm}</td>
|
<td style={{ padding: '12px 16px', fontWeight: 500 }}>{item.anlysNm}</td>
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
|
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
|
||||||
<span style={{
|
<span style={{
|
||||||
padding: '4px 8px',
|
padding: '4px 8px',
|
||||||
|
|||||||
@ -69,10 +69,10 @@ export function HNSLeftPanel({
|
|||||||
fontSize: '18px'
|
fontSize: '18px'
|
||||||
}}>🧪</div>
|
}}>🧪</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>
|
<div style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||||
HNS 대기확산 예측
|
HNS 대기확산 예측
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
|
||||||
ALOHA/CAMEO 기반 대기확산 시뮬레이션
|
ALOHA/CAMEO 기반 대기확산 시뮬레이션
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -84,16 +84,16 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 사고 기본정보 */}
|
{/* 사고 기본정보 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
📋 사고 기본정보
|
📋 사고 기본정보
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
{/* 사고명 */}
|
{/* 사고명 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>사고명</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>사고명</label>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={accidentName}
|
value={accidentName}
|
||||||
onChange={(e) => setAccidentName(e.target.value)}
|
onChange={(e) => setAccidentName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -102,16 +102,16 @@ export function HNSLeftPanel({
|
|||||||
{/* 사고일시 + 예측시간 */}
|
{/* 사고일시 + 예측시간 */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>사고일시</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>사고일시</label>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
defaultValue="2025-02-11T05:02"
|
defaultValue="2025-02-11T05:02"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>예측시간</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>예측시간</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={predictionTime}
|
value={predictionTime}
|
||||||
@ -130,7 +130,7 @@ export function HNSLeftPanel({
|
|||||||
{/* 사고지점 */}
|
{/* 사고지점 */}
|
||||||
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid rgba(6,182,212,0.2)', borderRadius: '8px' }}>
|
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid rgba(6,182,212,0.2)', borderRadius: '8px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 0 }}>📍 사고지점</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: 0 }}>📍 사고지점</label>
|
||||||
<button
|
<button
|
||||||
onClick={onMapSelectClick}
|
onClick={onMapSelectClick}
|
||||||
style={{
|
style={{
|
||||||
@ -142,8 +142,7 @@ export function HNSLeftPanel({
|
|||||||
fontSize: '8px',
|
fontSize: '8px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontFamily: 'var(--fK)',
|
transition: '0.15s'
|
||||||
transition: '0.15s'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
🗺 지도에서 클릭
|
🗺 지도에서 클릭
|
||||||
@ -151,29 +150,29 @@ export function HNSLeftPanel({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>위도</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>위도</label>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={incidentCoord.lat.toFixed(4)}
|
value={incidentCoord.lat.toFixed(4)}
|
||||||
onChange={(e) => onCoordChange({ ...incidentCoord, lat: parseFloat(e.target.value) || 0 })}
|
onChange={(e) => onCoordChange({ ...incidentCoord, lat: parseFloat(e.target.value) || 0 })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>경도</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>경도</label>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={incidentCoord.lon.toFixed(4)}
|
value={incidentCoord.lon.toFixed(4)}
|
||||||
onChange={(e) => onCoordChange({ ...incidentCoord, lon: parseFloat(e.target.value) || 0 })}
|
onChange={(e) => onCoordChange({ ...incidentCoord, lon: parseFloat(e.target.value) || 0 })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>상세 위치</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>상세 위치</label>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={locationName}
|
value={locationName}
|
||||||
onChange={(e) => setLocationName(e.target.value)}
|
onChange={(e) => setLocationName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -185,36 +184,36 @@ export function HNSLeftPanel({
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||||
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--green)' }}></div>
|
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--green)' }}></div>
|
||||||
<span className="hns-lbl" style={{ fontSize: '8px', color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: 0 }}>🌬 기상정보 (자동조회)</span>
|
<span className="hns-lbl" style={{ fontSize: '8px', color: 'var(--purple)', marginBottom: 0 }}>🌬 기상정보 (자동조회)</span>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>KMA API · 울산 AWS</span>
|
<span style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>KMA API · 울산 AWS</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
||||||
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>5.2</div>
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>5.2</div>
|
||||||
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>풍속(m/s)</div>
|
<div style={{ fontSize: '7px', color: 'var(--t3)' }}>풍속(m/s)</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>SW 225°</div>
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>SW 225°</div>
|
||||||
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>풍향</div>
|
<div style={{ fontSize: '7px', color: 'var(--t3)' }}>풍향</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--orange)' }}>8.5°C</div>
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--orange)' }}>8.5°C</div>
|
||||||
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>기온</div>
|
<div style={{ fontSize: '7px', color: 'var(--t3)' }}>기온</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--blue)' }}>62%</div>
|
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--blue)' }}>62%</div>
|
||||||
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>습도</div>
|
<div style={{ fontSize: '7px', color: 'var(--t3)' }}>습도</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>대기안정도</span>
|
<span style={{ color: 'var(--t3)' }}>대기안정도</span>
|
||||||
<span style={{ fontWeight: 600, color: 'var(--t1)' }}>D (중립)</span>
|
<span style={{ fontWeight: 600 }}>D (중립)</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>지표 조도</span>
|
<span style={{ color: 'var(--t3)' }}>지표 조도</span>
|
||||||
<span style={{ fontWeight: 600, color: 'var(--t1)' }}>해안</span>
|
<span style={{ fontWeight: 600 }}>해안</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -222,7 +221,7 @@ export function HNSLeftPanel({
|
|||||||
{/* 알고리즘 선택 */}
|
{/* 알고리즘 선택 */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>예측 알고리즘</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>예측 알고리즘</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={algorithm}
|
value={algorithm}
|
||||||
@ -236,7 +235,7 @@ export function HNSLeftPanel({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>확산 등급 기준</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>확산 등급 기준</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={criteriaModel}
|
value={criteriaModel}
|
||||||
@ -255,13 +254,13 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 물질 정보 */}
|
{/* 물질 정보 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--orange)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
🧪 물질 정보
|
🧪 물질 정보
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
{/* 물질 분류 */}
|
{/* 물질 분류 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>물질 분류</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>물질 분류</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={materialCategory}
|
value={materialCategory}
|
||||||
@ -279,7 +278,7 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 물질명 */}
|
{/* 물질명 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>물질명</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>물질명</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={substance}
|
value={substance}
|
||||||
@ -297,17 +296,17 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* UN번호 / CAS번호 */}
|
{/* UN번호 / CAS번호 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>UN번호 / CAS번호</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>UN번호 / CAS번호</label>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={unNumber}
|
value={unNumber}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={casNumber}
|
value={casNumber}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
@ -317,18 +316,18 @@ export function HNSLeftPanel({
|
|||||||
{/* 유출량 + 단위 */}
|
{/* 유출량 + 단위 */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>유출량</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>유출량</label>
|
||||||
<input
|
<input
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
type="number"
|
type="number"
|
||||||
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}
|
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={(e) => setAmount(e.target.value)}
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
step="0.1"
|
step="0.1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>단위</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>단위</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={unit}
|
value={unit}
|
||||||
@ -345,7 +344,7 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 유출 형태 */}
|
{/* 유출 형태 */}
|
||||||
<div>
|
<div>
|
||||||
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>유출 형태</label>
|
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>유출 형태</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
className="hns-inp"
|
className="hns-inp"
|
||||||
value={releaseType}
|
value={releaseType}
|
||||||
@ -360,21 +359,21 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 물질 위험 특성 */}
|
{/* 물질 위험 특성 */}
|
||||||
<div style={{ padding: '8px', background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: '6px', marginTop: '2px' }}>
|
<div style={{ padding: '8px', background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: '6px', marginTop: '2px' }}>
|
||||||
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--orange)', marginBottom: '4px' }}>
|
||||||
⚠ 물질 위험 특성
|
⚠ 물질 위험 특성
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px', fontSize: '8px' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>인화점</span>
|
<span style={{ color: 'var(--t3)' }}>인화점</span>
|
||||||
<span style={{ color: 'var(--red)', fontWeight: 600, fontFamily: 'var(--fM)' }}>4°C</span>
|
<span style={{ color: 'var(--red)', fontWeight: 600, fontFamily: 'var(--fM)' }}>4°C</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>비중</span>
|
<span style={{ color: 'var(--t3)' }}>비중</span>
|
||||||
<span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.867</span>
|
<span style={{ fontFamily: 'var(--fM)' }}>0.867</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>증기압</span>
|
<span style={{ color: 'var(--t3)' }}>증기압</span>
|
||||||
<span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>22 mmHg</span>
|
<span style={{ fontFamily: 'var(--fM)' }}>22 mmHg</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>IDLH</span>
|
<span style={{ color: 'var(--t3)' }}>IDLH</span>
|
||||||
@ -382,7 +381,7 @@ export function HNSLeftPanel({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>TWA</span>
|
<span style={{ color: 'var(--t3)' }}>TWA</span>
|
||||||
<span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>50 ppm</span>
|
<span style={{ fontFamily: 'var(--fM)' }}>50 ppm</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: 'var(--t3)' }}>AEGL-2(1h)</span>
|
<span style={{ color: 'var(--t3)' }}>AEGL-2(1h)</span>
|
||||||
@ -393,10 +392,10 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* AEGL 등급 범례 */}
|
{/* AEGL 등급 범례 */}
|
||||||
<div style={{ padding: '8px', background: 'rgba(168,85,247,0.05)', border: '1px solid rgba(168,85,247,0.12)', borderRadius: '6px' }}>
|
<div style={{ padding: '8px', background: 'rgba(168,85,247,0.05)', border: '1px solid rgba(168,85,247,0.12)', borderRadius: '6px' }}>
|
||||||
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--purple)', marginBottom: '4px' }}>
|
||||||
📊 확산 등급 기준 (AEGL)
|
📊 확산 등급 기준 (AEGL)
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px', fontSize: '8px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px', fontSize: '8px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(239,68,68,0.7)' }}></div>
|
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(239,68,68,0.7)' }}></div>
|
||||||
<span style={{ color: 'var(--t3)' }}>AEGL-3 (생명위협) — 500 ppm</span>
|
<span style={{ color: 'var(--t3)' }}>AEGL-3 (생명위협) — 500 ppm</span>
|
||||||
@ -431,8 +430,7 @@ export function HNSLeftPanel({
|
|||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: isRunningPrediction ? 'not-allowed' : 'pointer',
|
cursor: isRunningPrediction ? 'not-allowed' : 'pointer',
|
||||||
fontFamily: 'var(--fK)',
|
transition: '0.2s',
|
||||||
transition: '0.2s',
|
|
||||||
boxShadow: isRunningPrediction ? 'none' : '0 4px 16px rgba(249,115,22,0.25)'
|
boxShadow: isRunningPrediction ? 'none' : '0 4px 16px rgba(249,115,22,0.25)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -449,8 +447,7 @@ export function HNSLeftPanel({
|
|||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontFamily: 'var(--fK)'
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
🔄 초기화
|
🔄 초기화
|
||||||
</button>
|
</button>
|
||||||
@ -474,10 +471,10 @@ export function HNSLeftPanel({
|
|||||||
fontSize: '18px'
|
fontSize: '18px'
|
||||||
}}>📋</div>
|
}}>📋</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>
|
<div style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||||
분석 목록
|
분석 목록
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
|
||||||
저장된 대기확산 예측 결과
|
저장된 대기확산 예측 결과
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -485,13 +482,13 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 필터 섹션 */}
|
{/* 필터 섹션 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px', marginBottom: '12px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px', marginBottom: '12px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
🔍 필터
|
🔍 필터
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
{/* 기간 선택 */}
|
{/* 기간 선택 */}
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>기간</label>
|
<label style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>기간</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
value="최근 7일"
|
value="최근 7일"
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
@ -506,7 +503,7 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 물질 분류 */}
|
{/* 물질 분류 */}
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>물질 분류</label>
|
<label style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>물질 분류</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
value="전체"
|
value="전체"
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
@ -522,7 +519,7 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 위험도 */}
|
{/* 위험도 */}
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: '4px' }}>위험도</label>
|
<label style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>위험도</label>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
value="전체"
|
value="전체"
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
@ -539,20 +536,20 @@ export function HNSLeftPanel({
|
|||||||
|
|
||||||
{/* 통계 요약 */}
|
{/* 통계 요약 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--purple)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
📊 통계
|
📊 통계
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
||||||
<span style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>전체 분석</span>
|
<span style={{ fontSize: '10px', color: 'var(--t3)' }}>전체 분석</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>8건</span>
|
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>8건</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
||||||
<span style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>고위험 (AEGL-3)</span>
|
<span style={{ fontSize: '10px', color: 'var(--t3)' }}>고위험 (AEGL-3)</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>3건</span>
|
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>3건</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
|
||||||
<span style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>중위험 (AEGL-2)</span>
|
<span style={{ fontSize: '10px', color: 'var(--t3)' }}>중위험 (AEGL-2)</span>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5건</span>
|
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5건</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -104,10 +104,10 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
|
|||||||
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px',
|
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px',
|
||||||
}}>🔄</div>
|
}}>🔄</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h2 style={{ fontSize: '15px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: 0 }}>
|
<h2 style={{ fontSize: '15px', fontWeight: 700, margin: 0 }}>
|
||||||
HNS 대기확산 재계산
|
HNS 대기확산 재계산
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>
|
||||||
물질·기상조건을 수정하여 대기확산 예측을 재실행합니다
|
물질·기상조건을 수정하여 대기확산 예측을 재실행합니다
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +130,7 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
|
|||||||
padding: '10px 12px', background: 'rgba(249,115,22,0.04)',
|
padding: '10px 12px', background: 'rgba(249,115,22,0.04)',
|
||||||
border: '1px solid rgba(249,115,22,0.15)', borderRadius: '8px',
|
border: '1px solid rgba(249,115,22,0.15)', borderRadius: '8px',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '9px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: '6px' }}>
|
<div style={{ fontSize: '9px', fontWeight: 700, color: 'var(--orange)', marginBottom: '6px' }}>
|
||||||
현재 분석 정보
|
현재 분석 정보
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px', fontSize: '9px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px', fontSize: '9px' }}>
|
||||||
@ -205,11 +205,11 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
|
|||||||
<FG label="유출 위치 (좌표)">
|
<FG label="유출 위치 (좌표)">
|
||||||
<div style={{ display: 'flex', gap: '6px' }}>
|
<div style={{ display: 'flex', gap: '6px' }}>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '3px' }}>위도 (N)</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}>위도 (N)</div>
|
||||||
<input className="prd-i" type="number" value={lat} step={0.0001} onChange={e => setLat(Number(e.target.value))} style={{ fontFamily: 'var(--fM)' }} />
|
<input className="prd-i" type="number" value={lat} step={0.0001} onChange={e => setLat(Number(e.target.value))} style={{ fontFamily: 'var(--fM)' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '3px' }}>경도 (E)</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}>경도 (E)</div>
|
||||||
<input className="prd-i" type="number" value={lon} step={0.0001} onChange={e => setLon(Number(e.target.value))} style={{ fontFamily: 'var(--fM)' }} />
|
<input className="prd-i" type="number" value={lon} step={0.0001} onChange={e => setLon(Number(e.target.value))} style={{ fontFamily: 'var(--fM)' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -223,7 +223,7 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
|
|||||||
disabled={phase !== 'editing'}
|
disabled={phase !== 'editing'}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
|
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: 'pointer',
|
borderRadius: '8px', cursor: 'pointer',
|
||||||
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
||||||
color: 'var(--t2)', opacity: phase !== 'editing' ? 0.5 : 1,
|
color: 'var(--t2)', opacity: phase !== 'editing' ? 0.5 : 1,
|
||||||
}}
|
}}
|
||||||
@ -233,7 +233,7 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
|
|||||||
disabled={phase !== 'editing'}
|
disabled={phase !== 'editing'}
|
||||||
style={{
|
style={{
|
||||||
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
|
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
cursor: phase === 'editing' ? 'pointer' : 'wait',
|
cursor: phase === 'editing' ? 'pointer' : 'wait',
|
||||||
background: phase === 'done'
|
background: phase === 'done'
|
||||||
? 'rgba(34,197,94,0.15)'
|
? 'rgba(34,197,94,0.15)'
|
||||||
@ -263,7 +263,7 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
|
|||||||
function FG({ label, children }: { label: string; children: React.ReactNode }) {
|
function FG({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t2)', fontFamily: 'var(--fK)', marginBottom: '6px' }}>{label}</div>
|
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t2)', marginBottom: '6px' }}>{label}</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -272,8 +272,8 @@ function FG({ label, children }: { label: string; children: React.ReactNode }) {
|
|||||||
function InfoRow({ label, value }: { label: string; value: string }) {
|
function InfoRow({ label, value }: { label: string; value: string }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
|
||||||
<span style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{label}</span>
|
<span style={{ color: 'var(--t3)' }}>{label}</span>
|
||||||
<span style={{ color: 'var(--t1)', fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</span>
|
<span style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
|
|||||||
borderLeft: '1px solid var(--bd)',
|
borderLeft: '1px solid var(--bd)',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
fontFamily: 'var(--fK)'
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -54,7 +53,6 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
|
|||||||
borderLeft: '1px solid var(--bd)',
|
borderLeft: '1px solid var(--bd)',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
fontFamily: 'var(--fK)',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '16px'
|
gap: '16px'
|
||||||
@ -77,7 +75,6 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
|
|||||||
<h3 style={{
|
<h3 style={{
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'var(--t1)',
|
|
||||||
margin: 0
|
margin: 0
|
||||||
}}>
|
}}>
|
||||||
예측 결과
|
예측 결과
|
||||||
@ -157,7 +154,6 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
|
|||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
fontFamily: 'var(--fM)',
|
fontFamily: 'var(--fM)',
|
||||||
color: 'var(--t1)'
|
|
||||||
}}>
|
}}>
|
||||||
5.2 <span style={{ fontSize: '10px', fontWeight: 500 }}>m/s</span>
|
5.2 <span style={{ fontSize: '10px', fontWeight: 500 }}>m/s</span>
|
||||||
</div>
|
</div>
|
||||||
@ -180,7 +176,6 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
|
|||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
fontFamily: 'var(--fM)',
|
fontFamily: 'var(--fM)',
|
||||||
color: 'var(--t1)'
|
|
||||||
}}>
|
}}>
|
||||||
SW <span style={{ fontSize: '10px', fontWeight: 500 }}>225°</span>
|
SW <span style={{ fontSize: '10px', fontWeight: 500 }}>225°</span>
|
||||||
</div>
|
</div>
|
||||||
@ -217,7 +212,6 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
|
|||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: 'var(--t1)'
|
|
||||||
}}>
|
}}>
|
||||||
{zone.level}
|
{zone.level}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -139,10 +139,10 @@ export function HNSScenarioView() {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
<span style={{ fontSize: '16px' }}>📊</span>
|
<span style={{ fontSize: '16px' }}>📊</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>
|
<div style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||||
HNS 대기확산 시나리오 관리
|
HNS 대기확산 시나리오 관리
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '10px', color: 'var(--t3)' }}>
|
||||||
시간·조건별 대기확산 예측 시나리오 비교·검토 및 대응 의사결정 지원
|
시간·조건별 대기확산 예측 시나리오 비교·검토 및 대응 의사결정 지원
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -169,7 +169,7 @@ export function HNSScenarioView() {
|
|||||||
padding: '6px 14px', background: 'rgba(249,115,22,0.12)',
|
padding: '6px 14px', background: 'rgba(249,115,22,0.12)',
|
||||||
border: '1px solid rgba(249,115,22,0.3)', borderRadius: '6px',
|
border: '1px solid rgba(249,115,22,0.3)', borderRadius: '6px',
|
||||||
color: 'var(--orange)', fontSize: '11px', fontWeight: 700,
|
color: 'var(--orange)', fontSize: '11px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer', whiteSpace: 'nowrap',
|
cursor: 'pointer', whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
+ 신규 시나리오
|
+ 신규 시나리오
|
||||||
@ -188,14 +188,14 @@ export function HNSScenarioView() {
|
|||||||
padding: '10px 14px', borderBottom: '1px solid var(--bd)',
|
padding: '10px 14px', borderBottom: '1px solid var(--bd)',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t3)' }}>
|
||||||
시나리오 목록 — 톨루엔 대기확산
|
시나리오 목록 — 톨루엔 대기확산
|
||||||
</span>
|
</span>
|
||||||
<div style={{ display: 'flex', gap: '4px' }}>
|
<div style={{ display: 'flex', gap: '4px' }}>
|
||||||
{['시간순', '위험도순'].map((label, i) => (
|
{['시간순', '위험도순'].map((label, i) => (
|
||||||
<button key={i} style={{
|
<button key={i} style={{
|
||||||
padding: '3px 8px', fontSize: '9px', fontWeight: 600,
|
padding: '3px 8px', fontSize: '9px', fontWeight: 600,
|
||||||
borderRadius: '4px', cursor: 'pointer', fontFamily: 'var(--fK)',
|
borderRadius: '4px', cursor: 'pointer',
|
||||||
border: '1px solid var(--bd)', background: i === 0 ? 'rgba(249,115,22,0.08)' : 'var(--bg3)',
|
border: '1px solid var(--bd)', background: i === 0 ? 'rgba(249,115,22,0.08)' : 'var(--bg3)',
|
||||||
color: i === 0 ? 'var(--orange)' : 'var(--t3)',
|
color: i === 0 ? 'var(--orange)' : 'var(--t3)',
|
||||||
}}>
|
}}>
|
||||||
@ -230,7 +230,7 @@ export function HNSScenarioView() {
|
|||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
style={{ accentColor: 'var(--orange)' }}
|
style={{ accentColor: 'var(--orange)' }}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '12px', fontWeight: 700 }}>
|
||||||
{scn.id} {scn.name}
|
{scn.id} {scn.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -251,7 +251,7 @@ export function HNSScenarioView() {
|
|||||||
{scn.timeStep}
|
{scn.timeStep}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{scn.datetime}</span>
|
<span style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{scn.datetime}</span>
|
||||||
<span style={{ marginLeft: 'auto', fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{scn.wind}</span>
|
<span style={{ marginLeft: 'auto', fontSize: '8px', color: 'var(--t3)' }}>{scn.wind}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Metrics grid */}
|
{/* Metrics grid */}
|
||||||
@ -263,7 +263,7 @@ export function HNSScenarioView() {
|
|||||||
{ label: '영향인구', value: scn.population, color: '#f87171' },
|
{ label: '영향인구', value: scn.population, color: '#f87171' },
|
||||||
].map((m, i) => (
|
].map((m, i) => (
|
||||||
<div key={i} style={{ textAlign: 'center', padding: '3px', background: 'var(--bg0)', borderRadius: '3px' }}>
|
<div key={i} style={{ textAlign: 'center', padding: '3px', background: 'var(--bg0)', borderRadius: '3px' }}>
|
||||||
<div style={{ color: 'var(--t3)', fontFamily: 'var(--fK)', fontSize: '7px' }}>{m.label}</div>
|
<div style={{ color: 'var(--t3)', fontSize: '7px' }}>{m.label}</div>
|
||||||
<div style={{ color: m.color, fontWeight: 700 }}>{m.value}</div>
|
<div style={{ color: m.color, fontWeight: 700 }}>{m.value}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -272,7 +272,7 @@ export function HNSScenarioView() {
|
|||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '6px', fontSize: '8px', color: 'var(--t2)',
|
marginTop: '6px', fontSize: '8px', color: 'var(--t2)',
|
||||||
fontFamily: 'var(--fK)', lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
}}>
|
}}>
|
||||||
{scn.description}
|
{scn.description}
|
||||||
</div>
|
</div>
|
||||||
@ -291,7 +291,7 @@ export function HNSScenarioView() {
|
|||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '8px', borderRadius: '6px', cursor: 'pointer',
|
flex: 1, padding: '8px', borderRadius: '6px', cursor: 'pointer',
|
||||||
background: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.3)',
|
background: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.3)',
|
||||||
color: 'var(--orange)', fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
color: 'var(--orange)', fontSize: '11px', fontWeight: 700,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
📊 선택 시나리오 비교
|
📊 선택 시나리오 비교
|
||||||
@ -299,7 +299,7 @@ export function HNSScenarioView() {
|
|||||||
<button style={{
|
<button style={{
|
||||||
padding: '8px 14px', borderRadius: '6px', cursor: 'pointer',
|
padding: '8px 14px', borderRadius: '6px', cursor: 'pointer',
|
||||||
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
||||||
color: 'var(--t2)', fontSize: '11px', fontWeight: 600, fontFamily: 'var(--fK)',
|
color: 'var(--t2)', fontSize: '11px', fontWeight: 600,
|
||||||
}}>
|
}}>
|
||||||
📄 보고서
|
📄 보고서
|
||||||
</button>
|
</button>
|
||||||
@ -367,7 +367,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, #f97316, #ef4444, #a855f7)' }} />
|
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, #f97316, #ef4444, #a855f7)' }} />
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||||
{scenario.id} {scenario.name}
|
{scenario.id} {scenario.name}
|
||||||
</span>
|
</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
@ -392,7 +392,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
<div key={i} style={{
|
<div key={i} style={{
|
||||||
background: 'rgba(0,0,0,0.15)', borderRadius: '6px', padding: '8px', textAlign: 'center',
|
background: 'rgba(0,0,0,0.15)', borderRadius: '6px', padding: '8px', textAlign: 'center',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{m.label}</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>{m.label}</div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 700, color: m.color, fontFamily: 'var(--fM)', whiteSpace: 'pre-line', lineHeight: 1.2, marginTop: '2px' }}>{m.value}</div>
|
<div style={{ fontSize: '16px', fontWeight: 700, color: m.color, fontFamily: 'var(--fM)', whiteSpace: 'pre-line', lineHeight: 1.2, marginTop: '2px' }}>{m.value}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -403,7 +403,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
||||||
{/* Threat Zones */}
|
{/* Threat Zones */}
|
||||||
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
||||||
<h4 style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<h4 style={{ fontSize: '12px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
⚠️ 위험 구역
|
⚠️ 위험 구역
|
||||||
</h4>
|
</h4>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||||
@ -414,7 +414,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
{ label: 'TWA (작업허용)', value: scenario.zones.twa, color: '#22c55e' },
|
{ label: 'TWA (작업허용)', value: scenario.zones.twa, color: '#22c55e' },
|
||||||
].map((z, i) => (
|
].map((z, i) => (
|
||||||
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px', borderLeft: `3px solid ${z.color}` }}>
|
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px', borderLeft: `3px solid ${z.color}` }}>
|
||||||
<span style={{ fontSize: '10px', color: 'var(--t2)', fontFamily: 'var(--fK)' }}>{z.label}</span>
|
<span style={{ fontSize: '10px', color: 'var(--t2)' }}>{z.label}</span>
|
||||||
<span style={{ fontSize: '11px', fontWeight: 700, color: z.color, fontFamily: 'var(--fM)' }}>{z.value}</span>
|
<span style={{ fontSize: '11px', fontWeight: 700, color: z.color, fontFamily: 'var(--fM)' }}>{z.value}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -423,7 +423,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
||||||
<h4 style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<h4 style={{ fontSize: '12px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
🛡 대응 권고 사항
|
🛡 대응 권고 사항
|
||||||
</h4>
|
</h4>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
|
||||||
@ -431,7 +431,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
<div key={i} style={{
|
<div key={i} style={{
|
||||||
display: 'flex', alignItems: 'flex-start', gap: '6px',
|
display: 'flex', alignItems: 'flex-start', gap: '6px',
|
||||||
padding: '5px 8px', background: 'var(--bg0)', borderRadius: '4px',
|
padding: '5px 8px', background: 'var(--bg0)', borderRadius: '4px',
|
||||||
fontSize: '10px', color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.4,
|
fontSize: '10px', color: 'var(--t2)', lineHeight: 1.4,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ color: 'var(--orange)', fontWeight: 700, flexShrink: 0 }}>•</span>
|
<span style={{ color: 'var(--orange)', fontWeight: 700, flexShrink: 0 }}>•</span>
|
||||||
{action}
|
{action}
|
||||||
@ -443,7 +443,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
|
|
||||||
{/* Weather */}
|
{/* Weather */}
|
||||||
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
||||||
<h4 style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<h4 style={{ fontSize: '12px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
🌊 기상 조건
|
🌊 기상 조건
|
||||||
</h4>
|
</h4>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: '8px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: '8px' }}>
|
||||||
@ -457,8 +457,8 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
|
|||||||
].map((w, i) => (
|
].map((w, i) => (
|
||||||
<div key={i} style={{ textAlign: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: '6px' }}>
|
<div key={i} style={{ textAlign: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: '6px' }}>
|
||||||
<div style={{ fontSize: '14px', marginBottom: '2px' }}>{w.icon}</div>
|
<div style={{ fontSize: '14px', marginBottom: '2px' }}>{w.icon}</div>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>{w.value}</div>
|
<div style={{ fontSize: '12px', fontWeight: 700, fontFamily: 'var(--fM)' }}>{w.value}</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>{w.label}</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginTop: '2px' }}>{w.label}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -502,13 +502,13 @@ function ScenarioComparison() {
|
|||||||
return (
|
return (
|
||||||
<div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: '14px', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
<div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: '14px', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div style={{ fontSize: '13px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)', marginBottom: '2px' }}>
|
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '2px' }}>
|
||||||
📊 시나리오 비교 — 시간대별 대기확산 지표 추이
|
📊 시나리오 비교 — 시간대별 대기확산 지표 추이
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Chart 1: 최대 지표면 농도 추이 (Line + Area) ── */}
|
{/* ── Chart 1: 최대 지표면 농도 추이 (Line + Area) ── */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
최대 지표면 농도 (ppm) 변화 추이
|
최대 지표면 농도 (ppm) 변화 추이
|
||||||
</div>
|
</div>
|
||||||
<svg viewBox="0 0 500 140" style={{ width: '100%', height: '130px' }}>
|
<svg viewBox="0 0 500 140" style={{ width: '100%', height: '130px' }}>
|
||||||
@ -545,7 +545,7 @@ function ScenarioComparison() {
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px' }}>
|
||||||
{/* Chart 2: 위험 반경 변화 (Multi-line) */}
|
{/* Chart 2: 위험 반경 변화 (Multi-line) */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
위험 반경 (km) 변화
|
위험 반경 (km) 변화
|
||||||
</div>
|
</div>
|
||||||
<svg viewBox="0 0 260 100" style={{ width: '100%', height: '85px' }}>
|
<svg viewBox="0 0 260 100" style={{ width: '100%', height: '85px' }}>
|
||||||
@ -575,7 +575,7 @@ function ScenarioComparison() {
|
|||||||
|
|
||||||
{/* Chart 3: 영향 인구 변화 (Bar) */}
|
{/* Chart 3: 영향 인구 변화 (Bar) */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
영향 인구 (명) 변화
|
영향 인구 (명) 변화
|
||||||
</div>
|
</div>
|
||||||
<svg viewBox="0 0 240 100" style={{ width: '100%', height: '85px' }}>
|
<svg viewBox="0 0 240 100" style={{ width: '100%', height: '85px' }}>
|
||||||
@ -602,10 +602,10 @@ function ScenarioComparison() {
|
|||||||
|
|
||||||
{/* ── Chart 4: 시나리오 비교표 ── */}
|
{/* ── Chart 4: 시나리오 비교표 ── */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px', overflowX: 'auto' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px', overflowX: 'auto' }}>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>
|
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
|
||||||
📋 시나리오 비교표
|
📋 시나리오 비교표
|
||||||
</div>
|
</div>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '10px', fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '10px' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'var(--bg0)' }}>
|
<tr style={{ background: 'var(--bg0)' }}>
|
||||||
{['지표', ...D.map(d => `${d.id} (${d.label})`)].map((h, i) => (
|
{['지표', ...D.map(d => `${d.id} (${d.label})`)].map((h, i) => (
|
||||||
@ -676,7 +676,7 @@ function ScenarioMapOverlay() {
|
|||||||
width: '80%', maxWidth: '600px', height: '300px',
|
width: '80%', maxWidth: '600px', height: '300px',
|
||||||
background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '10px',
|
background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '10px',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
color: 'var(--t3)', fontSize: '13px', fontFamily: 'var(--fK)',
|
color: 'var(--t3)', fontSize: '13px',
|
||||||
}}>
|
}}>
|
||||||
[시나리오별 확산범위 오버레이 지도]
|
[시나리오별 확산범위 오버레이 지도]
|
||||||
</div>
|
</div>
|
||||||
@ -689,7 +689,7 @@ function ScenarioMapOverlay() {
|
|||||||
].map((item, i) => (
|
].map((item, i) => (
|
||||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: item.color, opacity: 0.5 }} />
|
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: item.color, opacity: 0.5 }} />
|
||||||
<span style={{ fontSize: '10px', color: 'var(--t2)', fontFamily: 'var(--fK)' }}>{item.label}</span>
|
<span style={{ fontSize: '10px', color: 'var(--t2)' }}>{item.label}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -758,10 +758,10 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
|
|||||||
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px',
|
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px',
|
||||||
}}>🧪</div>
|
}}>🧪</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h2 style={{ fontSize: '15px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: 0 }}>
|
<h2 style={{ fontSize: '15px', fontWeight: 700, margin: 0 }}>
|
||||||
신규 HNS 대기확산 시나리오
|
신규 HNS 대기확산 시나리오
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>
|
||||||
물질·기상·유출조건을 설정하여 새 시나리오를 생성합니다
|
물질·기상·유출조건을 설정하여 새 시나리오를 생성합니다
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -816,7 +816,7 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
|
|||||||
{ label: 'ERPG-2', value: mat.erpg2 },
|
{ label: 'ERPG-2', value: mat.erpg2 },
|
||||||
].map((p, i) => (
|
].map((p, i) => (
|
||||||
<div key={i} style={{ textAlign: 'center' }}>
|
<div key={i} style={{ textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{p.label}</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>{p.label}</div>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{p.value}</div>
|
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{p.value}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -879,12 +879,12 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
|
|||||||
<div style={{ padding: '14px 20px', borderTop: '1px solid var(--bd)', display: 'flex', gap: '8px' }}>
|
<div style={{ padding: '14px 20px', borderTop: '1px solid var(--bd)', display: 'flex', gap: '8px' }}>
|
||||||
<button onClick={onClose} style={{
|
<button onClick={onClose} style={{
|
||||||
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
|
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: 'pointer',
|
borderRadius: '8px', cursor: 'pointer',
|
||||||
background: 'var(--bg3)', border: '1px solid var(--bd)', color: 'var(--t2)',
|
background: 'var(--bg3)', border: '1px solid var(--bd)', color: 'var(--t2)',
|
||||||
}}>취소</button>
|
}}>취소</button>
|
||||||
<button onClick={handleSubmit} style={{
|
<button onClick={handleSubmit} style={{
|
||||||
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
|
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: 'pointer',
|
borderRadius: '8px', cursor: 'pointer',
|
||||||
background: 'linear-gradient(135deg, var(--orange), #ef4444)',
|
background: 'linear-gradient(135deg, var(--orange), #ef4444)',
|
||||||
border: 'none', color: '#fff',
|
border: 'none', color: '#fff',
|
||||||
opacity: name.trim() ? 1 : 0.5,
|
opacity: name.trim() ? 1 : 0.5,
|
||||||
@ -903,7 +903,7 @@ function ModalSection({ title, children }: { title: string; children: React.Reac
|
|||||||
<div>
|
<div>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '11px', fontWeight: 700, color: 'var(--orange)',
|
fontSize: '11px', fontWeight: 700, color: 'var(--orange)',
|
||||||
fontFamily: 'var(--fK)', marginBottom: '8px',
|
marginBottom: '8px',
|
||||||
paddingBottom: '4px', borderBottom: '1px solid rgba(249,115,22,0.15)',
|
paddingBottom: '4px', borderBottom: '1px solid rgba(249,115,22,0.15)',
|
||||||
}}>{title}</div>
|
}}>{title}</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>{children}</div>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>{children}</div>
|
||||||
@ -914,7 +914,7 @@ function ModalSection({ title, children }: { title: string; children: React.Reac
|
|||||||
function ModalField({ label, children }: { label: string; children: React.ReactNode }) {
|
function ModalField({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '9px', fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>{label}</div>
|
<div style={{ fontSize: '9px', fontWeight: 600, color: 'var(--t3)', marginBottom: '4px' }}>{label}</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -187,8 +187,8 @@ ${styles}
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
<div style={{ width: 42, height: 42, borderRadius: 10, background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(239,68,68,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20 }}>🧬</div>
|
<div style={{ width: 42, height: 42, borderRadius: 10, background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(239,68,68,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20 }}>🧬</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 16, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>HNS 물질정보 데이터베이스</div>
|
<div style={{ fontSize: 16, fontWeight: 700 }}>HNS 물질정보 데이터베이스</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 2 }}>SEBC 거동분류 · CHRIS/CAMEO DB · AEGL/ERPG/IDLH 기준 · 6,500+ 물질</div>
|
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}>SEBC 거동분류 · CHRIS/CAMEO DB · AEGL/ERPG/IDLH 기준 · 6,500+ 물질</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 6 }} data-html2pdf-ignore>
|
<div style={{ display: 'flex', gap: 6 }} data-html2pdf-ignore>
|
||||||
@ -197,11 +197,11 @@ ${styles}
|
|||||||
placeholder="물질명 또는 CAS 번호 검색..."
|
placeholder="물질명 또는 CAS 번호 검색..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={e => setSearchQuery(e.target.value)}
|
onChange={e => setSearchQuery(e.target.value)}
|
||||||
style={{ padding: '6px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', width: 200, outline: 'none' }}
|
style={{ padding: '6px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', fontSize: 10, width: 200, outline: 'none' }}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleExportPDF}
|
onClick={handleExportPDF}
|
||||||
style={{ padding: '6px 14px', borderRadius: 6, border: '1px solid rgba(249,115,22,.3)', background: 'rgba(249,115,22,.08)', color: 'var(--orange)', fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}
|
style={{ padding: '6px 14px', borderRadius: 6, border: '1px solid rgba(249,115,22,.3)', background: 'rgba(249,115,22,.08)', color: 'var(--orange)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}
|
||||||
>📥 DB 다운로드</button>
|
>📥 DB 다운로드</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -213,7 +213,7 @@ ${styles}
|
|||||||
key={idx}
|
key={idx}
|
||||||
onClick={() => setActiveTab(idx)}
|
onClick={() => setActiveTab(idx)}
|
||||||
className={activeTab === idx ? 'wx-tbtn on' : 'wx-tbtn'}
|
className={activeTab === idx ? 'wx-tbtn on' : 'wx-tbtn'}
|
||||||
style={{ flex: 1, padding: 8, fontSize: 11, fontFamily: 'var(--fK)' }}
|
style={{ flex: 1, padding: 8, fontSize: 11 }}
|
||||||
>{tab.icon} {tab.label}</button>
|
>{tab.icon} {tab.label}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -224,10 +224,10 @@ ${styles}
|
|||||||
<div style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.05),rgba(6,182,212,.03))', border: '1px solid rgba(249,115,22,.2)', borderRadius: 12, padding: 16, marginBottom: 16, position: 'relative', overflow: 'hidden' }}>
|
<div style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.05),rgba(6,182,212,.03))', border: '1px solid rgba(249,115,22,.2)', borderRadius: 12, padding: 16, marginBottom: 16, position: 'relative', overflow: 'hidden' }}>
|
||||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, background: 'linear-gradient(90deg,var(--orange),var(--cyan),var(--green),var(--purple))' }} />
|
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, background: 'linear-gradient(90deg,var(--orange),var(--cyan),var(--green),var(--purple))' }} />
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||||
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)' }}>SEBC 해양 거동 분류 체계</span>
|
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--orange)' }}>SEBC 해양 거동 분류 체계</span>
|
||||||
<span style={{ padding: '2px 8px', borderRadius: 10, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>Standard European Behaviour Classification</span>
|
<span style={{ padding: '2px 8px', borderRadius: 10, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>Standard European Behaviour Classification</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.7, marginBottom: 14 }}>
|
<div style={{ fontSize: 10, color: 'var(--t2)', lineHeight: 1.7, marginBottom: 14 }}>
|
||||||
HNS 물질이 해양에 유출되었을 때의 <b style={{ color: 'var(--orange)' }}>물리·화학적 거동</b>에 따라 분류하는 국제 표준 체계입니다. 물질의 밀도, 증기압, 용해도에 따라 5개 주요 거동 유형과 혼합 유형으로 구분되며, 각 유형별로 대응 전략이 달라집니다.
|
HNS 물질이 해양에 유출되었을 때의 <b style={{ color: 'var(--orange)' }}>물리·화학적 거동</b>에 따라 분류하는 국제 표준 체계입니다. 물질의 밀도, 증기압, 용해도에 따라 5개 주요 거동 유형과 혼합 유형으로 구분되며, 각 유형별로 대응 전략이 달라집니다.
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 8, marginBottom: 14 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 8, marginBottom: 14 }}>
|
||||||
@ -235,47 +235,47 @@ ${styles}
|
|||||||
<div style={{ padding: 12, background: 'rgba(168,85,247,.06)', border: '1px solid rgba(168,85,247,.2)', borderRadius: 8, textAlign: 'center' }}>
|
<div style={{ padding: 12, background: 'rgba(168,85,247,.06)', border: '1px solid rgba(168,85,247,.2)', borderRadius: 8, textAlign: 'center' }}>
|
||||||
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(168,85,247,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>💨</div>
|
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(168,85,247,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>💨</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>G</div>
|
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>G</div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: '4px 0' }}>Gas</div>
|
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Gas</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>기체 상태로 대기 중 확산. 증기압이 높아 빠르게 증발</div>
|
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>기체 상태로 대기 중 확산. 증기압이 높아 빠르게 증발</div>
|
||||||
<div style={{ marginTop: 6, padding: 3, background: 'rgba(168,85,247,.08)', borderRadius: 3, fontSize: 7, color: 'var(--purple)', fontWeight: 600 }}>대기확산 모델 적용</div>
|
<div style={{ marginTop: 6, padding: 3, background: 'rgba(168,85,247,.08)', borderRadius: 3, fontSize: 7, color: 'var(--purple)', fontWeight: 600 }}>대기확산 모델 적용</div>
|
||||||
</div>
|
</div>
|
||||||
{/* E: Evaporator */}
|
{/* E: Evaporator */}
|
||||||
<div style={{ padding: 12, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, textAlign: 'center' }}>
|
<div style={{ padding: 12, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, textAlign: 'center' }}>
|
||||||
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(239,68,68,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>🔥</div>
|
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(239,68,68,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>🔥</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--red)', fontFamily: 'var(--fM)' }}>E</div>
|
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--red)', fontFamily: 'var(--fM)' }}>E</div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: '4px 0' }}>Evaporator</div>
|
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Evaporator</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>해수면에서 증발. 부유 후 기화하여 독성 가스 생성</div>
|
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>해수면에서 증발. 부유 후 기화하여 독성 가스 생성</div>
|
||||||
<div style={{ marginTop: 6, padding: 3, background: 'rgba(239,68,68,.08)', borderRadius: 3, fontSize: 7, color: 'var(--red)', fontWeight: 600 }}>대기+해양 복합 대응</div>
|
<div style={{ marginTop: 6, padding: 3, background: 'rgba(239,68,68,.08)', borderRadius: 3, fontSize: 7, color: 'var(--red)', fontWeight: 600 }}>대기+해양 복합 대응</div>
|
||||||
</div>
|
</div>
|
||||||
{/* F: Floater */}
|
{/* F: Floater */}
|
||||||
<div style={{ padding: 12, background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.2)', borderRadius: 8, textAlign: 'center' }}>
|
<div style={{ padding: 12, background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.2)', borderRadius: 8, textAlign: 'center' }}>
|
||||||
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(234,179,8,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>🟡</div>
|
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(234,179,8,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>🟡</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>F</div>
|
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>F</div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: '4px 0' }}>Floater</div>
|
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Floater</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}</div>
|
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}</div>
|
||||||
<div style={{ marginTop: 6, padding: 3, background: 'rgba(234,179,8,.08)', borderRadius: 3, fontSize: 7, color: 'var(--yellow)', fontWeight: 600 }}>오일펜스 유사 봉쇄</div>
|
<div style={{ marginTop: 6, padding: 3, background: 'rgba(234,179,8,.08)', borderRadius: 3, fontSize: 7, color: 'var(--yellow)', fontWeight: 600 }}>오일펜스 유사 봉쇄</div>
|
||||||
</div>
|
</div>
|
||||||
{/* D: Dissolver */}
|
{/* D: Dissolver */}
|
||||||
<div style={{ padding: 12, background: 'rgba(6,182,212,.06)', border: '1px solid rgba(6,182,212,.2)', borderRadius: 8, textAlign: 'center' }}>
|
<div style={{ padding: 12, background: 'rgba(6,182,212,.06)', border: '1px solid rgba(6,182,212,.2)', borderRadius: 8, textAlign: 'center' }}>
|
||||||
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(6,182,212,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>💧</div>
|
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(6,182,212,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>💧</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>D</div>
|
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>D</div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: '4px 0' }}>Dissolver</div>
|
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Dissolver</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>해수에 용해. 수중 확산하여 넓은 범위 오염</div>
|
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>해수에 용해. 수중 확산하여 넓은 범위 오염</div>
|
||||||
<div style={{ marginTop: 6, padding: 3, background: 'rgba(6,182,212,.08)', borderRadius: 3, fontSize: 7, color: 'var(--cyan)', fontWeight: 600 }}>해양확산 모델 적용</div>
|
<div style={{ marginTop: 6, padding: 3, background: 'rgba(6,182,212,.08)', borderRadius: 3, fontSize: 7, color: 'var(--cyan)', fontWeight: 600 }}>해양확산 모델 적용</div>
|
||||||
</div>
|
</div>
|
||||||
{/* S: Sinker */}
|
{/* S: Sinker */}
|
||||||
<div style={{ padding: 12, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.2)', borderRadius: 8, textAlign: 'center' }}>
|
<div style={{ padding: 12, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.2)', borderRadius: 8, textAlign: 'center' }}>
|
||||||
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(34,197,94,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>⬇</div>
|
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(34,197,94,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>⬇</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--green)', fontFamily: 'var(--fM)' }}>S</div>
|
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--green)', fontFamily: 'var(--fM)' }}>S</div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', margin: '4px 0' }}>Sinker</div>
|
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Sinker</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}</div>
|
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}</div>
|
||||||
<div style={{ marginTop: 6, padding: 3, background: 'rgba(34,197,94,.08)', borderRadius: 3, fontSize: 7, color: 'var(--green)', fontWeight: 600 }}>저층 3D 모니터링 필수</div>
|
<div style={{ marginTop: 6, padding: 3, background: 'rgba(34,197,94,.08)', borderRadius: 3, fontSize: 7, color: 'var(--green)', fontWeight: 600 }}>저층 3D 모니터링 필수</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* 복합 거동 */}
|
{/* 복합 거동 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 12 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 12 }}>
|
||||||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: 8 }}>🔀 복합 거동 유형</div>
|
<div style={{ fontSize: 10, fontWeight: 700, marginBottom: 8 }}>🔀 복합 거동 유형</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 6, fontSize: 8, fontFamily: 'var(--fK)', textAlign: 'center' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 6, fontSize: 8, textAlign: 'center' }}>
|
||||||
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--purple)' }}>GD</span><br/><span style={{ color: 'var(--t3)' }}>기체+용해</span></div>
|
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--purple)' }}>GD</span><br/><span style={{ color: 'var(--t3)' }}>기체+용해</span></div>
|
||||||
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--red)' }}>ED</span><br/><span style={{ color: 'var(--t3)' }}>증발+용해</span></div>
|
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--red)' }}>ED</span><br/><span style={{ color: 'var(--t3)' }}>증발+용해</span></div>
|
||||||
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--yellow)' }}>FE</span><br/><span style={{ color: 'var(--t3)' }}>부유+증발</span></div>
|
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--yellow)' }}>FE</span><br/><span style={{ color: 'var(--t3)' }}>부유+증발</span></div>
|
||||||
@ -295,7 +295,7 @@ ${styles}
|
|||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
<button key={cat.id} onClick={() => setSelectedCategory(cat.id)}
|
<button key={cat.id} onClick={() => setSelectedCategory(cat.id)}
|
||||||
style={{
|
style={{
|
||||||
padding: '6px 12px', borderRadius: 6, fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)',
|
padding: '6px 12px', borderRadius: 6, fontSize: 10, fontWeight: 600, cursor: 'pointer',
|
||||||
border: selectedCategory === cat.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
|
border: selectedCategory === cat.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
|
||||||
color: selectedCategory === cat.id ? 'var(--orange)' : 'var(--t3)',
|
color: selectedCategory === cat.id ? 'var(--orange)' : 'var(--t3)',
|
||||||
background: selectedCategory === cat.id ? 'rgba(249,115,22,.08)' : 'var(--bg3)',
|
background: selectedCategory === cat.id ? 'rgba(249,115,22,.08)' : 'var(--bg3)',
|
||||||
@ -311,23 +311,23 @@ ${styles}
|
|||||||
{filtered.find(s => s.casNumber === '7664-41-7') && (
|
{filtered.find(s => s.casNumber === '7664-41-7') && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--purple)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--purple)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>NH₃</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>암모니아</span></div>
|
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>NH₃</span> <span style={{ fontSize: 12, fontWeight: 700 }}>암모니아</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G/GD</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}>독성</span></div>
|
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G/GD</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}>독성</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, fontFamily: 'var(--fK)', marginBottom: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>7664-41-7</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>7664-41-7</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>17.03</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ fontFamily: 'var(--fM)' }}>17.03</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-33.4°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-33.4°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.73</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ fontFamily: 'var(--fM)' }}>0.73</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>N/A (불연)</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ fontFamily: 'var(--fM)' }}>N/A (불연)</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>매우 높음</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>매우 높음</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7, fontFamily: 'var(--fK)', textAlign: 'center' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7, textAlign: 'center' }}>
|
||||||
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span style={{ color: 'var(--green)' }}>AEGL-2</span><br/><b style={{ color: 'var(--t1)' }}>160 ppm</b></div>
|
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span style={{ color: 'var(--green)' }}>AEGL-2</span><br/><b>160 ppm</b></div>
|
||||||
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span style={{ color: 'var(--orange)' }}>ERPG-2</span><br/><b style={{ color: 'var(--t1)' }}>150 ppm</b></div>
|
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span style={{ color: 'var(--orange)' }}>ERPG-2</span><br/><b>150 ppm</b></div>
|
||||||
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span style={{ color: 'var(--red)' }}>IDLH</span><br/><b style={{ color: 'var(--t1)' }}>300 ppm</b></div>
|
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span style={{ color: 'var(--red)' }}>IDLH</span><br/><b>300 ppm</b></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>해상 유출 시 급속 기화 → 독성 가스운 형성. 물에 잘 용해되어 수중 독성도 높음. 해풍 환경에서 확산 범위 확대.</div>
|
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>해상 유출 시 급속 기화 → 독성 가스운 형성. 물에 잘 용해되어 수중 독성도 높음. 해풍 환경에서 확산 범위 확대.</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -335,23 +335,23 @@ ${styles}
|
|||||||
{filtered.find(s => s.casNumber === '67-56-1') && (
|
{filtered.find(s => s.casNumber === '67-56-1') && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--cyan)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--cyan)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>CH₃OH</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>메탄올</span></div>
|
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>CH₃OH</span> <span style={{ fontSize: 12, fontWeight: 700 }}>메탄올</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(6,182,212,.1)', fontSize: 8, color: 'var(--cyan)', fontWeight: 600 }}>ED</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>인화성</span></div>
|
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(6,182,212,.1)', fontSize: 8, color: 'var(--cyan)', fontWeight: 600 }}>ED</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>인화성</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, fontFamily: 'var(--fK)', marginBottom: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>67-56-1</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>67-56-1</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>32.04</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ fontFamily: 'var(--fM)' }}>32.04</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>64.7°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>64.7°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.79</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ fontFamily: 'var(--fM)' }}>0.79</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>11°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>11°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>완전 혼화</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>완전 혼화</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7, fontFamily: 'var(--fK)', textAlign: 'center' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7, textAlign: 'center' }}>
|
||||||
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span style={{ color: 'var(--green)' }}>AEGL-2</span><br/><b style={{ color: 'var(--t1)' }}>2,100 ppm</b></div>
|
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span style={{ color: 'var(--green)' }}>AEGL-2</span><br/><b>2,100 ppm</b></div>
|
||||||
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span style={{ color: 'var(--orange)' }}>ERPG-2</span><br/><b style={{ color: 'var(--t1)' }}>1,000 ppm</b></div>
|
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span style={{ color: 'var(--orange)' }}>ERPG-2</span><br/><b>1,000 ppm</b></div>
|
||||||
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span style={{ color: 'var(--red)' }}>IDLH</span><br/><b style={{ color: 'var(--t1)' }}>6,000 ppm</b></div>
|
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span style={{ color: 'var(--red)' }}>IDLH</span><br/><b>6,000 ppm</b></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>해수에 완전 용해 → 수질 오염 장기화. 인화점 낮아 화재 위험. 증발 시 독성 증기 발생. 2007 온산항 FODDANGER호 95만L 유출 사고.</div>
|
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>해수에 완전 용해 → 수질 오염 장기화. 인화점 낮아 화재 위험. 증발 시 독성 증기 발생. 2007 온산항 FODDANGER호 95만L 유출 사고.</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -359,18 +359,18 @@ ${styles}
|
|||||||
{filtered.find(s => s.casNumber === '1333-74-0') && (
|
{filtered.find(s => s.casNumber === '1333-74-0') && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--red)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--red)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--red)', fontFamily: 'var(--fM)' }}>H₂</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>수소</span></div>
|
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--red)', fontFamily: 'var(--fM)' }}>H₂</span> <span style={{ fontSize: 12, fontWeight: 700 }}>수소</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}>폭발</span></div>
|
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}>폭발</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, fontFamily: 'var(--fK)', marginBottom: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>1333-74-0</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>1333-74-0</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>2.016</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ fontFamily: 'var(--fM)' }}>2.016</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-252.9°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-252.9°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중(기체):</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.07</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중(기체):</span> <span style={{ fontFamily: 'var(--fM)' }}>0.07</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>LFL:</span> <span style={{ color: 'var(--red)', fontFamily: 'var(--fM)' }}>4.0%</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>LFL:</span> <span style={{ color: 'var(--red)', fontFamily: 'var(--fM)' }}>4.0%</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>UFL:</span> <span style={{ color: 'var(--red)', fontFamily: 'var(--fM)' }}>75.0%</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>UFL:</span> <span style={{ color: 'var(--red)', fontFamily: 'var(--fM)' }}>75.0%</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>폭발 범위 극히 넓음(4~75%). 무색·무취로 감지 불가. 극저온 액화수소 유출 시 BLEVE 위험. 급속 상승 확산.</div>
|
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>폭발 범위 극히 넓음(4~75%). 무색·무취로 감지 불가. 극저온 액화수소 유출 시 BLEVE 위험. 급속 상승 확산.</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -378,18 +378,18 @@ ${styles}
|
|||||||
{filtered.find(s => s.nameEn === 'LPG (Propane/Butane)' || s.casNumber === '74-82-8') && (
|
{filtered.find(s => s.nameEn === 'LPG (Propane/Butane)' || s.casNumber === '74-82-8') && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--orange)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--orange)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>CH₄</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>LNG (메탄)</span></div>
|
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>CH₄</span> <span style={{ fontSize: 12, fontWeight: 700 }}>LNG (메탄)</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>인화/폭발</span></div>
|
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>인화/폭발</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, fontFamily: 'var(--fK)', marginBottom: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>74-82-8</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>74-82-8</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>16.04</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ fontFamily: 'var(--fM)' }}>16.04</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-161.5°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-161.5°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중(액체):</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.42</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중(액체):</span> <span style={{ fontFamily: 'var(--fM)' }}>0.42</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>LFL:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5.0%</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>LFL:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5.0%</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>UFL:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>15.0%</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>UFL:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>15.0%</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>극저온(-162°C) 유출 시 RPT(급속상변환폭발), Pool Fire 위험. Flash 기화 → 가연성 가스운 형성. 인천·평택항 LNG 물동량 상위.</div>
|
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>극저온(-162°C) 유출 시 RPT(급속상변환폭발), Pool Fire 위험. Flash 기화 → 가연성 가스운 형성. 인천·평택항 LNG 물동량 상위.</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -397,18 +397,18 @@ ${styles}
|
|||||||
{filtered.find(s => s.casNumber === '108-95-2' || s.name === '페놀') && (
|
{filtered.find(s => s.casNumber === '108-95-2' || s.name === '페놀') && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--green)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--green)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--green)', fontFamily: 'var(--fM)' }}>C₆H₅OH</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>페놀</span></div>
|
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--green)', fontFamily: 'var(--fM)' }}>C₆H₅OH</span> <span style={{ fontSize: 12, fontWeight: 700 }}>페놀</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(34,197,94,.1)', fontSize: 8, color: 'var(--green)', fontWeight: 600 }}>S/SD</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}>독성</span></div>
|
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(34,197,94,.1)', fontSize: 8, color: 'var(--green)', fontWeight: 600 }}>S/SD</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}>독성</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, fontFamily: 'var(--fK)', marginBottom: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>108-95-2</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>108-95-2</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>94.11</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ fontFamily: 'var(--fM)' }}>94.11</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>181.7°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ fontFamily: 'var(--fM)' }}>181.7°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ color: 'var(--green)', fontFamily: 'var(--fM)' }}>1.07 (침강)</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ color: 'var(--green)', fontFamily: 'var(--fM)' }}>1.07 (침강)</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>79°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>79°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>84 g/L</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>84 g/L</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>비중 1.07 → <b style={{ color: 'var(--green)' }}>Sinker 특성</b>으로 저층 축적. ROMS 검증 결과 저층 농도가 표층의 3.5배. 해양산업시설 배출 주요 HNS (31.8kg/일).</div>
|
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>비중 1.07 → <b style={{ color: 'var(--green)' }}>Sinker 특성</b>으로 저층 축적. ROMS 검증 결과 저층 농도가 표층의 3.5배. 해양산업시설 배출 주요 HNS (31.8kg/일).</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -416,18 +416,18 @@ ${styles}
|
|||||||
{filtered.find(s => s.casNumber === '108-88-3') && (
|
{filtered.find(s => s.casNumber === '108-88-3') && (
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--yellow)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--yellow)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>C₇H₈</span> <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>톨루엔</span></div>
|
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>C₇H₈</span> <span style={{ fontSize: 12, fontWeight: 700 }}>톨루엔</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(234,179,8,.1)', fontSize: 8, color: 'var(--yellow)', fontWeight: 600 }}>FE</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>인화성</span></div>
|
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(234,179,8,.1)', fontSize: 8, color: 'var(--yellow)', fontWeight: 600 }}>FE</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>인화성</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, fontFamily: 'var(--fK)', marginBottom: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>108-88-3</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>108-88-3</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>92.14</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>분자량:</span> <span style={{ fontFamily: 'var(--fM)' }}>92.14</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>110.6°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>끓는점:</span> <span style={{ fontFamily: 'var(--fM)' }}>110.6°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>0.87 (부유)</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>비중:</span> <span style={{ color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>0.87 (부유)</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>4°C</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>인화점:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>4°C</span></div>
|
||||||
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>0.52 g/L</span></div>
|
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>용해도:</span> <span style={{ fontFamily: 'var(--fM)' }}>0.52 g/L</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>해수면 부유 → 증발. 인화점 극히 낮아(4°C) 화재 위험 상시. 석유화학 산업의 대표적 HNS. 울산항 주요 취급물질.</div>
|
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>해수면 부유 → 증발. 인화점 극히 낮아(4°C) 화재 위험 상시. 석유화학 산업의 대표적 HNS. 울산항 주요 취급물질.</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -440,41 +440,41 @@ ${styles}
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
|
||||||
{/* AEGL 기준 */}
|
{/* AEGL 기준 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, borderTop: '3px solid var(--green)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, borderTop: '3px solid var(--green)' }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--green)', fontFamily: 'var(--fK)', marginBottom: 10 }}>🟢 AEGL (Acute Exposure Guideline Level)</div>
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--green)', marginBottom: 10 }}>🟢 AEGL (Acute Exposure Guideline Level)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 10 }}>미국 EPA — 일반인 급성 노출 기준 (10분, 30분, 60분, 4시간, 8시간)</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)', marginBottom: 10 }}>미국 EPA — 일반인 급성 노출 기준 (10분, 30분, 60분, 4시간, 8시간)</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
<div style={{ padding: '8px 10px', background: 'rgba(34,197,94,.04)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 6, borderLeft: '4px solid var(--green)' }}>
|
<div style={{ padding: '8px 10px', background: 'rgba(34,197,94,.04)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 6, borderLeft: '4px solid var(--green)' }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--green)', fontFamily: 'var(--fK)' }}>AEGL-1 (불쾌감)</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--green)' }}>AEGL-1 (불쾌감)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>눈에 띄는 불쾌감, 자극 또는 비감각적 증상. 노출 중단 시 증상 소멸.</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}>눈에 띄는 불쾌감, 자극 또는 비감각적 증상. 노출 중단 시 증상 소멸.</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '8px 10px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 6, borderLeft: '4px solid var(--orange)' }}>
|
<div style={{ padding: '8px 10px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 6, borderLeft: '4px solid var(--orange)' }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)' }}>AEGL-2 (비가역적 영향)</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)' }}>AEGL-2 (비가역적 영향)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>비가역적·장기적 건강 영향 또는 대피 능력 저하. <b style={{ color: 'var(--orange)' }}>대피 기준으로 사용</b>.</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}>비가역적·장기적 건강 영향 또는 대피 능력 저하. <b style={{ color: 'var(--orange)' }}>대피 기준으로 사용</b>.</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '8px 10px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 6, borderLeft: '4px solid var(--red)' }}>
|
<div style={{ padding: '8px 10px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 6, borderLeft: '4px solid var(--red)' }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)' }}>AEGL-3 (생명 위협)</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)' }}>AEGL-3 (생명 위협)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>생명을 위협하는 건강 영향 또는 사망. <b style={{ color: 'var(--red)' }}>즉시 대피·격리 구역 설정</b>.</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}>생명을 위협하는 건강 영향 또는 사망. <b style={{ color: 'var(--red)' }}>즉시 대피·격리 구역 설정</b>.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ERPG / IDLH */}
|
{/* ERPG / IDLH */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, borderTop: '3px solid var(--red)' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, borderTop: '3px solid var(--red)' }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)', marginBottom: 10 }}>🔴 ERPG & IDLH</div>
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--red)', marginBottom: 10 }}>🔴 ERPG & IDLH</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: 4 }}>ERPG (Emergency Response Planning Guideline)</div>
|
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--orange)', marginBottom: 4 }}>ERPG (Emergency Response Planning Guideline)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 6 }}>AIHA 발행 — 1시간 노출 기준</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)', marginBottom: 6 }}>AIHA 발행 — 1시간 노출 기준</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
<div style={{ padding: '6px 8px', background: 'rgba(34,197,94,.03)', border: '1px solid rgba(34,197,94,.1)', borderRadius: 4, fontSize: 8, fontFamily: 'var(--fK)', borderLeft: '3px solid var(--green)' }}><b style={{ color: 'var(--green)' }}>ERPG-1</b> — 일시적 건강 영향, 냄새 감지</div>
|
<div style={{ padding: '6px 8px', background: 'rgba(34,197,94,.03)', border: '1px solid rgba(34,197,94,.1)', borderRadius: 4, fontSize: 8, borderLeft: '3px solid var(--green)' }}><b style={{ color: 'var(--green)' }}>ERPG-1</b> — 일시적 건강 영향, 냄새 감지</div>
|
||||||
<div style={{ padding: '6px 8px', background: 'rgba(249,115,22,.03)', border: '1px solid rgba(249,115,22,.1)', borderRadius: 4, fontSize: 8, fontFamily: 'var(--fK)', borderLeft: '3px solid var(--orange)' }}><b style={{ color: 'var(--orange)' }}>ERPG-2</b> — 비가역적 영향, 대피 판단 기준</div>
|
<div style={{ padding: '6px 8px', background: 'rgba(249,115,22,.03)', border: '1px solid rgba(249,115,22,.1)', borderRadius: 4, fontSize: 8, borderLeft: '3px solid var(--orange)' }}><b style={{ color: 'var(--orange)' }}>ERPG-2</b> — 비가역적 영향, 대피 판단 기준</div>
|
||||||
<div style={{ padding: '6px 8px', background: 'rgba(239,68,68,.03)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 4, fontSize: 8, fontFamily: 'var(--fK)', borderLeft: '3px solid var(--red)' }}><b style={{ color: 'var(--red)' }}>ERPG-3</b> — 생명 위협 농도</div>
|
<div style={{ padding: '6px 8px', background: 'rgba(239,68,68,.03)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 4, fontSize: 8, borderLeft: '3px solid var(--red)' }}><b style={{ color: 'var(--red)' }}>ERPG-3</b> — 생명 위협 농도</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 4, padding: 10, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.15)', borderRadius: 6 }}>
|
<div style={{ marginTop: 4, padding: 10, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.15)', borderRadius: 6 }}>
|
||||||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)', marginBottom: 4 }}>IDLH (Immediately Dangerous to Life or Health)</div>
|
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--red)', marginBottom: 4 }}>IDLH (Immediately Dangerous to Life or Health)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 4 }}>NIOSH — 30분 노출 시 생명·건강에 즉각적 위험</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)', marginBottom: 4 }}>NIOSH — 30분 노출 시 생명·건강에 즉각적 위험</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}><b style={{ color: 'var(--red)' }}>최대 허용 노출 한계</b>로서 이 농도를 초과하면 자급식 호흡장치(SCBA) 없이는 진입 불가. WING 시스템에서 위험구역 자동 설정의 기준값으로 사용.</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}><b style={{ color: 'var(--red)' }}>최대 허용 노출 한계</b>로서 이 농도를 초과하면 자급식 호흡장치(SCBA) 없이는 진입 불가. WING 시스템에서 위험구역 자동 설정의 기준값으로 사용.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -482,8 +482,8 @@ ${styles}
|
|||||||
|
|
||||||
{/* 물질별 위험도 비교표 */}
|
{/* 물질별 위험도 비교표 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: 12 }}>📊 주요 HNS 물질별 위험도 기준 비교 (ppm)</div>
|
<div style={{ fontSize: 12, fontWeight: 700, marginBottom: 12 }}>📊 주요 HNS 물질별 위험도 기준 비교 (ppm)</div>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'rgba(168,85,247,.06)' }}>
|
<tr style={{ background: 'rgba(168,85,247,.06)' }}>
|
||||||
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--purple)' }}>물질</th>
|
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--purple)' }}>물질</th>
|
||||||
@ -559,7 +559,7 @@ ${styles}
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style={{ marginTop: 8, fontSize: 7, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>※ AEGL: 60분 기준 / ERPG: 1시간 노출 / IDLH: 30분 / LFL: 폭발하한</div>
|
<div style={{ marginTop: 8, fontSize: 7, color: 'var(--t3)' }}>※ AEGL: 60분 기준 / ERPG: 1시간 노출 / IDLH: 30분 / LFL: 폭발하한</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -570,16 +570,16 @@ ${styles}
|
|||||||
{/* ── 검색창 ── */}
|
{/* ── 검색창 ── */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
|
||||||
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🔍 HNS 물질 통합 검색 <span style={{ fontSize: 9, fontWeight: 400, color: 'var(--t3)' }}>(화물약어·제품명·동의어·CAS·UN번호 통합)</span></div>
|
<div style={{ fontSize: 13, fontWeight: 700 }}>🔍 HNS 물질 통합 검색 <span style={{ fontSize: 9, fontWeight: 400, color: 'var(--t3)' }}>(화물약어·제품명·동의어·CAS·UN번호 통합)</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<button style={{ padding: '5px 10px', borderRadius: 4, border: '1px solid rgba(249,115,22,.25)', background: 'rgba(249,115,22,.08)', color: 'var(--orange)', fontSize: 9, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📥 DB 다운로드</button>
|
<button style={{ padding: '5px 10px', borderRadius: 4, border: '1px solid rgba(249,115,22,.25)', background: 'rgba(249,115,22,.08)', color: 'var(--orange)', fontSize: 9, fontWeight: 600, cursor: 'pointer' }}>📥 DB 다운로드</button>
|
||||||
<button style={{ padding: '5px 10px', borderRadius: 4, border: '1px solid rgba(6,182,212,.25)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: 9, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔗 Port-MIS 연동</button>
|
<button style={{ padding: '5px 10px', borderRadius: 4, border: '1px solid rgba(6,182,212,.25)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: 9, fontWeight: 600, cursor: 'pointer' }}>🔗 Port-MIS 연동</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 8, marginBottom: 10, alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: 8, marginBottom: 10, alignItems: 'center' }}>
|
||||||
<div style={{ flexShrink: 0, display: 'flex', alignItems: 'center', gap: 4 }}>
|
<div style={{ flexShrink: 0, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
<span style={{ fontSize: 9, fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>구분:</span>
|
<span style={{ fontSize: 9, fontWeight: 600, color: 'var(--t3)' }}>구분:</span>
|
||||||
<select value={hmsSearchType} onChange={e => { setHmsSearchType(e.target.value as typeof hmsSearchType); setHmsPage(1) }} style={{ padding: '6px 10px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', outline: 'none', minWidth: 130 }}>
|
<select value={hmsSearchType} onChange={e => { setHmsSearchType(e.target.value as typeof hmsSearchType); setHmsPage(1) }} style={{ padding: '6px 10px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none', minWidth: 130 }}>
|
||||||
<option value="abbr">❶ 약자/제품명</option>
|
<option value="abbr">❶ 약자/제품명</option>
|
||||||
<option value="korName">❷ 국문명 (동의어)</option>
|
<option value="korName">❷ 국문명 (동의어)</option>
|
||||||
<option value="engName">❸ 영문명 (동의어)</option>
|
<option value="engName">❸ 영문명 (동의어)</option>
|
||||||
@ -587,10 +587,10 @@ ${styles}
|
|||||||
<option value="un">❺ UN번호</option>
|
<option value="un">❺ UN번호</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" value={hmsSearchInput} onChange={e => { setHmsSearchInput(e.target.value); setHmsPage(1) }} placeholder={hmsSearchType === 'abbr' ? '검색어 입력 (부호·띄어쓰기 제외)' : hmsSearchType === 'cas' ? 'CAS번호 입력 (예: 71-43-2)' : hmsSearchType === 'un' ? 'UN번호 입력 (예: 1114)' : '검색어 입력 (* 동의어 검색)'} style={{ flex: 1, padding: '8px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 11, fontFamily: 'var(--fK)', outline: 'none' }} />
|
<input type="text" value={hmsSearchInput} onChange={e => { setHmsSearchInput(e.target.value); setHmsPage(1) }} placeholder={hmsSearchType === 'abbr' ? '검색어 입력 (부호·띄어쓰기 제외)' : hmsSearchType === 'cas' ? 'CAS번호 입력 (예: 71-43-2)' : hmsSearchType === 'un' ? 'UN번호 입력 (예: 1114)' : '검색어 입력 (* 동의어 검색)'} style={{ flex: 1, padding: '8px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 11, outline: 'none' }} />
|
||||||
<button onClick={() => setHmsPage(1)} style={{ padding: '8px 20px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--orange),var(--red))', color: '#fff', fontSize: 11, fontWeight: 700, cursor: 'pointer', fontFamily: 'var(--fK)', flexShrink: 0 }}>🔎 검색</button>
|
<button onClick={() => setHmsPage(1)} style={{ padding: '8px 20px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--orange),var(--red))', color: '#fff', fontSize: 11, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>🔎 검색</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.6 }}>
|
<div style={{ fontSize: 8, color: 'var(--t3)', lineHeight: 1.6 }}>
|
||||||
※ 국문명·영문명 검색 시 <b style={{ color: 'var(--orange)' }}>동의어까지 검색</b> | 약자/제품명 검색 시 <b style={{ color: 'var(--orange)' }}>부호, 띄어쓰기 제외</b> 후 검색 | 총 <b style={{ color: 'var(--cyan)' }}>1,316종</b> 등록 (화물적부도 277종 / 해양시설 56종 / 용선자 화물코드 983종)
|
※ 국문명·영문명 검색 시 <b style={{ color: 'var(--orange)' }}>동의어까지 검색</b> | 약자/제품명 검색 시 <b style={{ color: 'var(--orange)' }}>부호, 띄어쓰기 제외</b> 후 검색 | 총 <b style={{ color: 'var(--cyan)' }}>1,316종</b> 등록 (화물적부도 277종 / 해양시설 56종 / 용선자 화물코드 983종)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -598,13 +598,13 @@ ${styles}
|
|||||||
{/* ── 검색 결과 테이블 ── */}
|
{/* ── 검색 결과 테이블 ── */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📋 검색 결과 <span style={{ fontSize: 9, fontWeight: 400, color: 'var(--t3)' }}>— {hmsTotal}건 조회</span></div>
|
<div style={{ fontSize: 11, fontWeight: 700 }}>📋 검색 결과 <span style={{ fontSize: 9, fontWeight: 400, color: 'var(--t3)' }}>— {hmsTotal}건 조회</span></div>
|
||||||
<select value={hmsFilterSebc} onChange={e => { setHmsFilterSebc(e.target.value); setHmsPage(1) }} style={{ padding: '3px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, fontFamily: 'var(--fK)', outline: 'none' }}>
|
<select value={hmsFilterSebc} onChange={e => { setHmsFilterSebc(e.target.value); setHmsPage(1) }} style={{ padding: '3px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, outline: 'none' }}>
|
||||||
<option>전체 거동분류</option><option>G (Gas)</option><option>E (Evaporator)</option><option>F (Floater)</option><option>D (Dissolver)</option><option>S (Sinker)</option>
|
<option>전체 거동분류</option><option>G (Gas)</option><option>E (Evaporator)</option><option>F (Floater)</option><option>D (Dissolver)</option><option>S (Sinker)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ overflowX: 'auto' }}>
|
<div style={{ overflowX: 'auto' }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'rgba(249,115,22,.06)' }}>
|
<tr style={{ background: 'rgba(249,115,22,.06)' }}>
|
||||||
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)', width: 28 }}>No.</th>
|
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)', width: 28 }}>No.</th>
|
||||||
@ -630,7 +630,7 @@ ${styles}
|
|||||||
>
|
>
|
||||||
<td style={{ padding: 6, fontFamily: 'var(--fM)', color: 'var(--t3)' }}>{(hmsPage - 1) * HMS_PER_PAGE + idx + 1}</td>
|
<td style={{ padding: 6, fontFamily: 'var(--fM)', color: 'var(--t3)' }}>{(hmsPage - 1) * HMS_PER_PAGE + idx + 1}</td>
|
||||||
<td style={{ padding: 6, fontWeight: 600, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{s.abbreviation}</td>
|
<td style={{ padding: 6, fontWeight: 600, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{s.abbreviation}</td>
|
||||||
<td style={{ padding: 6, color: 'var(--t1)' }}>{s.nameEn}</td>
|
<td style={{ padding: 6 }}>{s.nameEn}</td>
|
||||||
<td style={{ padding: 6, color: 'var(--t3)', fontSize: 8 }}>{s.synonymsEn}</td>
|
<td style={{ padding: 6, color: 'var(--t3)', fontSize: 8 }}>{s.synonymsEn}</td>
|
||||||
<td style={{ padding: 6, fontWeight: 600 }}><span style={{ color: 'var(--cyan)', textDecoration: 'underline', cursor: 'pointer' }} onClick={e => { e.stopPropagation(); setHmsSelectedId(s.id); setHmsDetailTab(0) }}>{s.nameKr}</span></td>
|
<td style={{ padding: 6, fontWeight: 600 }}><span style={{ color: 'var(--cyan)', textDecoration: 'underline', cursor: 'pointer' }} onClick={e => { e.stopPropagation(); setHmsSelectedId(s.id); setHmsDetailTab(0) }}>{s.nameKr}</span></td>
|
||||||
<td style={{ padding: 6, color: 'var(--t3)', fontSize: 8 }}>{s.synonymsKr}</td>
|
<td style={{ padding: 6, color: 'var(--t3)', fontSize: 8 }}>{s.synonymsKr}</td>
|
||||||
@ -644,7 +644,7 @@ ${styles}
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 10, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 9, fontFamily: 'var(--fK)', color: 'var(--t3)' }}>
|
<div style={{ marginTop: 10, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 9, color: 'var(--t3)' }}>
|
||||||
<span>총 <b style={{ color: 'var(--orange)' }}>{hmsTotal.toLocaleString()}</b>종 등록 · Port-MIS 화물적부도 연동 · 해경청 물질정보집 · IBC CODE 692종</span>
|
<span>총 <b style={{ color: 'var(--orange)' }}>{hmsTotal.toLocaleString()}</b>종 등록 · Port-MIS 화물적부도 연동 · 해경청 물질정보집 · IBC CODE 692종</span>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<button onClick={() => setHmsPage(p => Math.max(1, p - 1))} disabled={hmsPage <= 1} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, cursor: 'pointer', opacity: hmsPage <= 1 ? 0.4 : 1 }}>◀</button>
|
<button onClick={() => setHmsPage(p => Math.max(1, p - 1))} disabled={hmsPage <= 1} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, cursor: 'pointer', opacity: hmsPage <= 1 ? 0.4 : 1 }}>◀</button>
|
||||||
@ -678,7 +678,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
<div style={{ display: 'flex', borderBottom: '1px solid var(--bd)', background: 'linear-gradient(90deg,rgba(6,182,212,.06),rgba(249,115,22,.04))' }}>
|
<div style={{ display: 'flex', borderBottom: '1px solid var(--bd)', background: 'linear-gradient(90deg,rgba(6,182,212,.06),rgba(249,115,22,.04))' }}>
|
||||||
{tabLabels.map((label, i) => (
|
{tabLabels.map((label, i) => (
|
||||||
<button key={i} onClick={() => onTabChange(i)} style={{ flex: 1, padding: 10, fontSize: 10, fontWeight: activeTab === i ? 700 : 600, cursor: 'pointer', fontFamily: 'var(--fK)', border: 'none', borderBottom: `2px solid ${activeTab === i ? 'var(--cyan)' : 'transparent'}`, background: activeTab === i ? 'rgba(6,182,212,.06)' : 'transparent', color: activeTab === i ? 'var(--cyan)' : 'var(--t3)' }}>{label}</button>
|
<button key={i} onClick={() => onTabChange(i)} style={{ flex: 1, padding: 10, fontSize: 10, fontWeight: activeTab === i ? 700 : 600, cursor: 'pointer', border: 'none', borderBottom: `2px solid ${activeTab === i ? 'var(--cyan)' : 'transparent'}`, background: activeTab === i ? 'rgba(6,182,212,.06)' : 'transparent', color: activeTab === i ? 'var(--cyan)' : 'var(--t3)' }}>{label}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -689,22 +689,22 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16, paddingBottom: 14, borderBottom: '1px solid var(--bd)' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16, paddingBottom: 14, borderBottom: '1px solid var(--bd)' }}>
|
||||||
<div style={{ width: 52, height: 52, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(249,115,22,.1))', border: '1px solid rgba(6,182,212,.25)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22, flexShrink: 0 }}>🧪</div>
|
<div style={{ width: 52, height: 52, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(249,115,22,.1))', border: '1px solid rgba(6,182,212,.25)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22, flexShrink: 0 }}>🧪</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: 18, fontWeight: 800, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>{s.nameKr} <span style={{ fontSize: 12, fontWeight: 400, color: 'var(--t3)' }}>({s.nameEn})</span></div>
|
<div style={{ fontSize: 18, fontWeight: 800 }}>{s.nameKr} <span style={{ fontSize: 12, fontWeight: 400, color: 'var(--t3)' }}>({s.nameEn})</span></div>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 }}>
|
||||||
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)', fontSize: 9, color: 'var(--cyan)', fontWeight: 600, fontFamily: 'var(--fM)' }}>CAS: {s.casNumber}</span>
|
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)', fontSize: 9, color: 'var(--cyan)', fontWeight: 600, fontFamily: 'var(--fM)' }}>CAS: {s.casNumber}</span>
|
||||||
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(249,115,22,.1)', border: '1px solid rgba(249,115,22,.2)', fontSize: 9, color: 'var(--orange)', fontWeight: 600, fontFamily: 'var(--fM)' }}>UN: {s.unNumber}</span>
|
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(249,115,22,.1)', border: '1px solid rgba(249,115,22,.2)', fontSize: 9, color: 'var(--orange)', fontWeight: 600, fontFamily: 'var(--fM)' }}>UN: {s.unNumber}</span>
|
||||||
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(168,85,247,.1)', border: '1px solid rgba(168,85,247,.2)', fontSize: 9, color: 'var(--purple)', fontWeight: 600, fontFamily: 'var(--fK)' }}>운송방법: {s.transportMethod}</span>
|
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(168,85,247,.1)', border: '1px solid rgba(168,85,247,.2)', fontSize: 9, color: 'var(--purple)', fontWeight: 600 }}>운송방법: {s.transportMethod}</span>
|
||||||
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)', fontSize: 9, color: 'var(--green)', fontWeight: 600, fontFamily: 'var(--fK)' }}>SEBC: {s.sebc}</span>
|
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)', fontSize: 9, color: 'var(--green)', fontWeight: 600 }}>SEBC: {s.sebc}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 4 }}><b>유사명:</b> {s.synonymsKr} | <b>특성:</b> {s.hazardClass}</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)', marginTop: 4 }}><b>유사명:</b> {s.synonymsKr} | <b>특성:</b> {s.hazardClass}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
||||||
{/* Left: 물리·화학적 특성 */}
|
{/* Left: 물리·화학적 특성 */}
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: 8 }}>⚗️ 물리·화학적 특성</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', marginBottom: 8 }}>⚗️ 물리·화학적 특성</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 9 }}>
|
||||||
{([
|
{([
|
||||||
['용도', s.usage, 'var(--cyan)'],
|
['용도', s.usage, 'var(--cyan)'],
|
||||||
['상태', s.state, 'var(--cyan)'],
|
['상태', s.state, 'var(--cyan)'],
|
||||||
@ -729,7 +729,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
|
|
||||||
{/* Right: NFPA + 위험등급 */}
|
{/* Right: NFPA + 위험등급 */}
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)', marginBottom: 8 }}>⚠️ 위험등급·농도기준</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', marginBottom: 8 }}>⚠️ 위험등급·농도기준</div>
|
||||||
<div style={{ display: 'flex', gap: 14, alignItems: 'flex-start', marginBottom: 12 }}>
|
<div style={{ display: 'flex', gap: 14, alignItems: 'flex-start', marginBottom: 12 }}>
|
||||||
<div style={{ width: 80, height: 80, position: 'relative', flexShrink: 0 }}>
|
<div style={{ width: 80, height: 80, position: 'relative', flexShrink: 0 }}>
|
||||||
<svg viewBox="0 0 100 100" width={80} height={80}>
|
<svg viewBox="0 0 100 100" width={80} height={80}>
|
||||||
@ -743,15 +743,15 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
<polygon points="5,50 28,28 50,50 28,72" fill="rgba(59,130,246,.2)" stroke="rgba(59,130,246,.5)" strokeWidth={1} />
|
<polygon points="5,50 28,28 50,50 28,72" fill="rgba(59,130,246,.2)" stroke="rgba(59,130,246,.5)" strokeWidth={1} />
|
||||||
<text x={23} y={55} textAnchor="middle" fill="#60a5fa" fontSize={16} fontWeight={800} fontFamily="monospace">{nfpa.reactivity}</text>
|
<text x={23} y={55} textAnchor="middle" fill="#60a5fa" fontSize={16} fontWeight={800} fontFamily="monospace">{nfpa.reactivity}</text>
|
||||||
</svg>
|
</svg>
|
||||||
<div style={{ textAlign: 'center', fontSize: 7, fontWeight: 600, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 2 }}>NFPA 704</div>
|
<div style={{ textAlign: 'center', fontSize: 7, fontWeight: 600, color: 'var(--t3)', marginTop: 2 }}>NFPA 704</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 4, fontSize: 8, fontFamily: 'var(--fK)' }}>
|
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 4, fontSize: 8 }}>
|
||||||
<div style={{ padding: '4px 8px', background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 4 }}><span style={{ color: 'var(--red)', fontWeight: 700 }}>건강(적) {nfpa.health}</span> — {nfpa.health >= 4 ? '치명적' : nfpa.health >= 3 ? '중상' : nfpa.health >= 2 ? '장해' : nfpa.health >= 1 ? '경미한 손상' : '무해'}</div>
|
<div style={{ padding: '4px 8px', background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 4 }}><span style={{ color: 'var(--red)', fontWeight: 700 }}>건강(적) {nfpa.health}</span> — {nfpa.health >= 4 ? '치명적' : nfpa.health >= 3 ? '중상' : nfpa.health >= 2 ? '장해' : nfpa.health >= 1 ? '경미한 손상' : '무해'}</div>
|
||||||
<div style={{ padding: '4px 8px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.12)', borderRadius: 4 }}><span style={{ color: '#fbbf24', fontWeight: 700 }}>인화성(황) {nfpa.fire}</span> — {nfpa.fire >= 4 ? '93°F 미만' : nfpa.fire >= 3 ? '100°F 미만' : nfpa.fire >= 2 ? '200°F 미만' : nfpa.fire >= 1 ? '200°F 이상' : '비가연'}</div>
|
<div style={{ padding: '4px 8px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.12)', borderRadius: 4 }}><span style={{ color: '#fbbf24', fontWeight: 700 }}>인화성(황) {nfpa.fire}</span> — {nfpa.fire >= 4 ? '93°F 미만' : nfpa.fire >= 3 ? '100°F 미만' : nfpa.fire >= 2 ? '200°F 미만' : nfpa.fire >= 1 ? '200°F 이상' : '비가연'}</div>
|
||||||
<div style={{ padding: '4px 8px', background: 'rgba(59,130,246,.06)', border: '1px solid rgba(59,130,246,.12)', borderRadius: 4 }}><span style={{ color: '#60a5fa', fontWeight: 700 }}>반응성(청) {nfpa.reactivity}</span> — {nfpa.reactivity >= 3 ? '폭발 가능' : nfpa.reactivity >= 2 ? '격렬 반응' : nfpa.reactivity >= 1 ? '불안정 가능' : '안정'}</div>
|
<div style={{ padding: '4px 8px', background: 'rgba(59,130,246,.06)', border: '1px solid rgba(59,130,246,.12)', borderRadius: 4 }}><span style={{ color: '#60a5fa', fontWeight: 700 }}>반응성(청) {nfpa.reactivity}</span> — {nfpa.reactivity >= 3 ? '폭발 가능' : nfpa.reactivity >= 2 ? '격렬 반응' : nfpa.reactivity >= 1 ? '불안정 가능' : '안정'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 9 }}>
|
||||||
<InfoBoxRow label="위험 분류" value={s.hazardClass} bg="rgba(239,68,68,.06)" border="rgba(239,68,68,.12)" labelColor="var(--red)" valueColor="var(--red)" />
|
<InfoBoxRow label="위험 분류" value={s.hazardClass} bg="rgba(239,68,68,.06)" border="rgba(239,68,68,.12)" labelColor="var(--red)" valueColor="var(--red)" />
|
||||||
<InfoBoxRow label="ERG 번호" value={s.ergNumber} bg="rgba(249,115,22,.06)" border="rgba(249,115,22,.12)" labelColor="var(--orange)" valueColor="var(--orange)" />
|
<InfoBoxRow label="ERG 번호" value={s.ergNumber} bg="rgba(249,115,22,.06)" border="rgba(249,115,22,.12)" labelColor="var(--orange)" valueColor="var(--orange)" />
|
||||||
<InfoBoxRow label="IDLH" value={s.idlh} bg="rgba(168,85,247,.06)" border="rgba(168,85,247,.12)" labelColor="var(--purple)" valueColor="var(--red)" />
|
<InfoBoxRow label="IDLH" value={s.idlh} bg="rgba(168,85,247,.06)" border="rgba(168,85,247,.12)" labelColor="var(--purple)" valueColor="var(--red)" />
|
||||||
@ -770,20 +770,20 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* 방제거리 */}
|
{/* 방제거리 */}
|
||||||
<div style={{ border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)' }}>🚧 방제거리 (ERG {s.ergNumber})</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)' }}>🚧 방제거리 (ERG {s.ergNumber})</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||||
<div style={{ padding: 10, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 6 }}>
|
<div style={{ padding: 10, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 6 }}>
|
||||||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: 4 }}>🔥 화재 시</div>
|
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--orange)', marginBottom: 4 }}>🔥 화재 시</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.6 }}>격리거리: <b style={{ color: 'var(--red)' }}>{s.responseDistanceFire}</b> 이상</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.6 }}>격리거리: <b style={{ color: 'var(--red)' }}>{s.responseDistanceFire}</b> 이상</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 10, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.12)', borderRadius: 6 }}>
|
<div style={{ padding: 10, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.12)', borderRadius: 6 }}>
|
||||||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fK)', marginBottom: 4 }}>💨 유출 시 (비화재)</div>
|
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--purple)', marginBottom: 4 }}>💨 유출 시 (비화재)</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.6 }}>주간 방호활동거리: <b style={{ color: 'var(--purple)' }}>{s.responseDistanceSpillDay}</b><br />야간 방호활동거리: <b style={{ color: 'var(--red)' }}>{s.responseDistanceSpillNight}</b></div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.6 }}>주간 방호활동거리: <b style={{ color: 'var(--purple)' }}>{s.responseDistanceSpillDay}</b><br />야간 방호활동거리: <b style={{ color: 'var(--red)' }}>{s.responseDistanceSpillNight}</b></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 10, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.12)', borderRadius: 6 }}>
|
<div style={{ padding: 10, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.12)', borderRadius: 6 }}>
|
||||||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: 4 }}>🌊 해상 유출 시</div>
|
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--cyan)', marginBottom: 4 }}>🌊 해상 유출 시</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.6 }}>{s.marineResponse}</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.6 }}>{s.marineResponse}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -792,9 +792,9 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* PPE */}
|
{/* PPE */}
|
||||||
<div style={{ border: '1px solid rgba(34,197,94,.2)', borderRadius: 8, overflow: 'hidden', marginBottom: 12 }}>
|
<div style={{ border: '1px solid rgba(34,197,94,.2)', borderRadius: 8, overflow: 'hidden', marginBottom: 12 }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(34,197,94,.08),transparent)', borderBottom: '1px solid rgba(34,197,94,.12)' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(34,197,94,.08),transparent)', borderBottom: '1px solid rgba(34,197,94,.12)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--green)', fontFamily: 'var(--fK)' }}>🛡 개인보호장구 (PPE) 추천</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--green)' }}>🛡 개인보호장구 (PPE) 추천</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div style={{ padding: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, fontSize: 9 }}>
|
||||||
<div style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 4, textAlign: 'center' }}>
|
<div style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 4, textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: 16, marginBottom: 3 }}>🧑🚒</div>
|
<div style={{ fontSize: 16, marginBottom: 3 }}>🧑🚒</div>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--red)' }}>근거리</div>
|
<div style={{ fontWeight: 700, color: 'var(--red)' }}>근거리</div>
|
||||||
@ -810,16 +810,16 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* MSDS */}
|
{/* MSDS */}
|
||||||
<div style={{ border: '1px solid rgba(59,130,246,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid rgba(59,130,246,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(59,130,246,.08),transparent)', borderBottom: '1px solid rgba(59,130,246,.12)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(59,130,246,.08),transparent)', borderBottom: '1px solid rgba(59,130,246,.12)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: '#60a5fa', fontFamily: 'var(--fK)' }}>📄 MSDS 주요 정보</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: '#60a5fa' }}>📄 MSDS 주요 정보</div>
|
||||||
<button style={{ padding: '3px 10px', background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', borderRadius: 4, color: '#60a5fa', fontSize: 8, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📥 전문 다운로드</button>
|
<button style={{ padding: '3px 10px', background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', borderRadius: 4, color: '#60a5fa', fontSize: 8, fontWeight: 600, cursor: 'pointer' }}>📥 전문 다운로드</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 10, fontSize: 8, fontFamily: 'var(--fK)', color: 'var(--t2)', lineHeight: 1.7 }}>
|
<div style={{ padding: 10, fontSize: 8, color: 'var(--t2)', lineHeight: 1.7 }}>
|
||||||
<b style={{ color: 'var(--t1)' }}>§2 유해성·위험성:</b> {s.msds.hazard}<br />
|
<b>§2 유해성·위험성:</b> {s.msds.hazard}<br />
|
||||||
<b style={{ color: 'var(--t1)' }}>§4 응급조치:</b> {s.msds.firstAid}<br />
|
<b>§4 응급조치:</b> {s.msds.firstAid}<br />
|
||||||
<b style={{ color: 'var(--t1)' }}>§5 소화방법:</b> {s.msds.fireFighting}<br />
|
<b>§5 소화방법:</b> {s.msds.fireFighting}<br />
|
||||||
<b style={{ color: 'var(--t1)' }}>§6 누출대응:</b> {s.msds.spillResponse}<br />
|
<b>§6 누출대응:</b> {s.msds.spillResponse}<br />
|
||||||
<b style={{ color: 'var(--t1)' }}>§8 노출방지:</b> {s.msds.exposure}<br />
|
<b>§8 노출방지:</b> {s.msds.exposure}<br />
|
||||||
<b style={{ color: 'var(--t1)' }}>§15 법적규제:</b> {s.msds.regulation}
|
<b>§15 법적규제:</b> {s.msds.regulation}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -834,9 +834,9 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* IBC CODE */}
|
{/* IBC CODE */}
|
||||||
<div style={{ border: '1px solid rgba(249,115,22,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid rgba(249,115,22,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(249,115,22,.08),transparent)', borderBottom: '1px solid rgba(249,115,22,.12)' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(249,115,22,.08),transparent)', borderBottom: '1px solid rgba(249,115,22,.12)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)' }}>⚓ IBC CODE 기반 주요 내용</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--orange)' }}>⚓ IBC CODE 기반 주요 내용</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 9 }}>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 4 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 4 }}>
|
||||||
{([
|
{([
|
||||||
['위험성', s.ibcHazard],
|
['위험성', s.ibcHazard],
|
||||||
@ -848,7 +848,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
] as [string, string][]).map(([label, value]) => (
|
] as [string, string][]).map(([label, value]) => (
|
||||||
<React.Fragment key={label}>
|
<React.Fragment key={label}>
|
||||||
<div style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, color: 'var(--t3)' }}>{label}</div>
|
<div style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, color: 'var(--t3)' }}>{label}</div>
|
||||||
<div style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, color: 'var(--t1)', fontWeight: 600 }}>{value}</div>
|
<div style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, fontWeight: 600 }}>{value}</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -873,9 +873,9 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* EmS */}
|
{/* EmS */}
|
||||||
<div style={{ border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)' }}>🆘 비상대응핸드북 (EmS) — ERG {s.ergNumber}</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)' }}>🆘 비상대응핸드북 (EmS) — ERG {s.ergNumber}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 9 }}>
|
||||||
<div style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 6 }}>
|
<div style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 6 }}>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--red)', marginBottom: 3 }}>🔥 화재 대응</div>
|
<div style={{ fontWeight: 700, color: 'var(--red)', marginBottom: 3 }}>🔥 화재 대응</div>
|
||||||
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsFire}</div>
|
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsFire}</div>
|
||||||
@ -889,7 +889,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsFirstAid}</div>
|
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsFirstAid}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<button style={{ padding: '6px 16px', background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)', borderRadius: 6, color: 'var(--red)', fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📋 EmS 전문 보기</button>
|
<button style={{ padding: '6px 16px', background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)', borderRadius: 6, color: 'var(--red)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}>📋 EmS 전문 보기</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -904,11 +904,11 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* 화물적부도 */}
|
{/* 화물적부도 */}
|
||||||
<div style={{ border: '1px solid rgba(168,85,247,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid rgba(168,85,247,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(168,85,247,.08),transparent)', borderBottom: '1px solid rgba(168,85,247,.12)' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(168,85,247,.08),transparent)', borderBottom: '1px solid rgba(168,85,247,.12)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fK)' }}>📋 화물적부도 화물코드</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--purple)' }}>📋 화물적부도 화물코드</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>클릭 시 물질검색창으로 이동</div>
|
<div style={{ fontSize: 8, color: 'var(--t3)' }}>클릭 시 물질검색창으로 이동</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 12 }}>
|
<div style={{ padding: 12 }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
|
||||||
<thead><tr style={{ background: 'rgba(168,85,247,.05)' }}>
|
<thead><tr style={{ background: 'rgba(168,85,247,.05)' }}>
|
||||||
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--purple)' }}>화물코드</th>
|
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--purple)' }}>화물코드</th>
|
||||||
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--t2)' }}>약자/제품명</th>
|
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--t2)' }}>약자/제품명</th>
|
||||||
@ -936,11 +936,11 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
{/* 항구별 코드 */}
|
{/* 항구별 코드 */}
|
||||||
<div style={{ border: '1px solid rgba(6,182,212,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid rgba(6,182,212,.2)', borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(6,182,212,.08),transparent)', borderBottom: '1px solid rgba(6,182,212,.12)' }}>
|
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(6,182,212,.08),transparent)', borderBottom: '1px solid rgba(6,182,212,.12)' }}>
|
||||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)' }}>🏗 항구별 코드</div>
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--cyan)' }}>🏗 항구별 코드</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>Port-MIS 위험물반입신고현황 연동</div>
|
<div style={{ fontSize: 8, color: 'var(--t3)' }}>Port-MIS 위험물반입신고현황 연동</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: 12 }}>
|
<div style={{ padding: 12 }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
|
||||||
<thead><tr style={{ background: 'rgba(6,182,212,.05)' }}>
|
<thead><tr style={{ background: 'rgba(6,182,212,.05)' }}>
|
||||||
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--cyan)' }}>항구</th>
|
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--cyan)' }}>항구</th>
|
||||||
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--t2)' }}>청코드</th>
|
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--bdL)', color: 'var(--t2)' }}>청코드</th>
|
||||||
@ -953,7 +953,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
const freqBg = p.frequency === '높음' ? 'rgba(239,68,68,.1)' : p.frequency === '중간' ? 'rgba(249,115,22,.1)' : 'rgba(34,197,94,.1)'
|
const freqBg = p.frequency === '높음' ? 'rgba(239,68,68,.1)' : p.frequency === '중간' ? 'rgba(249,115,22,.1)' : 'rgba(34,197,94,.1)'
|
||||||
return (
|
return (
|
||||||
<tr key={i} style={{ borderBottom: i < s.portFrequency.length - 1 ? '1px solid rgba(255,255,255,.04)' : undefined }}>
|
<tr key={i} style={{ borderBottom: i < s.portFrequency.length - 1 ? '1px solid rgba(255,255,255,.04)' : undefined }}>
|
||||||
<td style={{ padding: '5px 8px', fontWeight: 600, color: 'var(--t1)' }}>{p.port}</td>
|
<td style={{ padding: '5px 8px', fontWeight: 600 }}>{p.port}</td>
|
||||||
<td style={{ padding: '5px 8px', fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>{p.portCode}</td>
|
<td style={{ padding: '5px 8px', fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>{p.portCode}</td>
|
||||||
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>{p.lastImport}</td>
|
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>{p.lastImport}</td>
|
||||||
<td style={{ textAlign: 'center' }}><span style={{ padding: '1px 6px', borderRadius: 3, background: freqBg, color: freqColor, fontWeight: 600, fontSize: 8 }}>{p.frequency}</span></td>
|
<td style={{ textAlign: 'center' }}><span style={{ padding: '1px 6px', borderRadius: 3, background: freqBg, color: freqColor, fontWeight: 600, fontSize: 8 }}>{p.frequency}</span></td>
|
||||||
@ -963,7 +963,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style={{ marginTop: 10, textAlign: 'center' }}>
|
<div style={{ marginTop: 10, textAlign: 'center' }}>
|
||||||
<button style={{ padding: '6px 16px', background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)', borderRadius: 6, color: 'var(--cyan)', fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔗 Port-MIS 화물적부도 조회</button>
|
<button style={{ padding: '6px 16px', background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)', borderRadius: 6, color: 'var(--cyan)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}>🔗 Port-MIS 화물적부도 조회</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1476,11 +1476,11 @@ ${styles}
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
<div style={{ width: '42px', height: '42px', borderRadius: '10px', background: 'linear-gradient(135deg,rgba(168,85,247,.2),rgba(59,130,246,.15))', border: '1px solid rgba(168,85,247,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px' }}>📐</div>
|
<div style={{ width: '42px', height: '42px', borderRadius: '10px', background: 'linear-gradient(135deg,rgba(168,85,247,.2),rgba(59,130,246,.15))', border: '1px solid rgba(168,85,247,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px' }}>📐</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>HNS 대기확산 모델 이론 및 검증</div>
|
<div style={{ fontSize: '16px', fontWeight: 700 }}>HNS 대기확산 모델 이론 및 검증</div>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>WRF-Chem · Gaussian Plume/Puff · ROMS · 해양환경 보정 — Based on Lee Moon-Jin et al.</div>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>WRF-Chem · Gaussian Plume/Puff · ROMS · 해양환경 보정 — Based on Lee Moon-Jin et al.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleExportPDF} data-html2pdf-ignore style={{ padding: '6px 14px', borderRadius: '6px', border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: '10px', fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📤 PDF 내보내기</button>
|
<button onClick={handleExportPDF} data-html2pdf-ignore style={{ padding: '6px 14px', borderRadius: '6px', border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: '10px', fontWeight: 600, cursor: 'pointer' }}>📤 PDF 내보내기</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sub-tabs */}
|
{/* Sub-tabs */}
|
||||||
@ -1488,7 +1488,7 @@ ${styles}
|
|||||||
{theoryTabs.map((tab, i) => (
|
{theoryTabs.map((tab, i) => (
|
||||||
<button key={i} onClick={() => setActivePanel(i)}
|
<button key={i} onClick={() => setActivePanel(i)}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '8px', fontSize: '11px', fontFamily: 'var(--fK)', fontWeight: 600, cursor: 'pointer',
|
flex: 1, padding: '8px', fontSize: '11px', fontWeight: 600, cursor: 'pointer',
|
||||||
borderRadius: '6px', border: activePanel === i ? '1px solid var(--cyan)' : '1px solid transparent',
|
borderRadius: '6px', border: activePanel === i ? '1px solid var(--cyan)' : '1px solid transparent',
|
||||||
background: activePanel === i ? 'rgba(6,182,212,.08)' : 'transparent',
|
background: activePanel === i ? 'rgba(6,182,212,.08)' : 'transparent',
|
||||||
color: activePanel === i ? 'var(--cyan)' : 'var(--t3)',
|
color: activePanel === i ? 'var(--cyan)' : 'var(--t3)',
|
||||||
|
|||||||
@ -22,8 +22,8 @@ function HNSManualViewer() {
|
|||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📖 해양 HNS 대응 매뉴얼</div>
|
<div style={{ fontSize: '16px', fontWeight: 700 }}>📖 해양 HNS 대응 매뉴얼</div>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024 한국어판)</div>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024 한국어판)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -41,16 +41,16 @@ function HNSManualViewer() {
|
|||||||
].map(ch => (
|
].map(ch => (
|
||||||
<div key={ch.title} className={card} style={{ ...cardBg, cursor: 'pointer', transition: '.2s' }}>
|
<div key={ch.title} className={card} style={{ ...cardBg, cursor: 'pointer', transition: '.2s' }}>
|
||||||
<div style={{ fontSize: '20px', marginBottom: '6px' }}>{ch.icon}</div>
|
<div style={{ fontSize: '20px', marginBottom: '6px' }}>{ch.icon}</div>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>{ch.title}</div>
|
<div style={{ fontSize: '11px', fontWeight: 700 }}>{ch.title}</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '4px', lineHeight: '1.4' }}>{ch.desc}</div>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', marginTop: '4px', lineHeight: '1.4' }}>{ch.desc}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SEBC 거동 분류 */}
|
{/* SEBC 거동 분류 */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '13px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>SEBC 거동 분류 (Standard European Behaviour Classification)</div>
|
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '10px' }}>SEBC 거동 분류 (Standard European Behaviour Classification)</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '10px', lineHeight: '1.5' }}>물질의 물리적·화학적 특성(용해도, 밀도, 증기압, 점도)에 따라 이론적 거동을 5가지 주요 범주 + 7가지 하위 범주로 분류</div>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '10px', lineHeight: '1.5' }}>물질의 물리적·화학적 특성(용해도, 밀도, 증기압, 점도)에 따라 이론적 거동을 5가지 주요 범주 + 7가지 하위 범주로 분류</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: '8px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: '8px' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '💨', label: 'G — 가스', desc: '대기 중 확산\n증기압 > 101.3kPa\n예: 암모니아, 염소', color: 'rgba(139,92,246' },
|
{ icon: '💨', label: 'G — 가스', desc: '대기 중 확산\n증기압 > 101.3kPa\n예: 암모니아, 염소', color: 'rgba(139,92,246' },
|
||||||
@ -61,8 +61,8 @@ function HNSManualViewer() {
|
|||||||
].map(s => (
|
].map(s => (
|
||||||
<div key={s.label} style={{ textAlign: 'center', padding: '10px 6px', background: `${s.color},.08)`, border: `1px solid ${s.color},.2)`, borderRadius: '6px' }}>
|
<div key={s.label} style={{ textAlign: 'center', padding: '10px 6px', background: `${s.color},.08)`, border: `1px solid ${s.color},.2)`, borderRadius: '6px' }}>
|
||||||
<div style={{ fontSize: '22px', marginBottom: '4px' }}>{s.icon}</div>
|
<div style={{ fontSize: '22px', marginBottom: '4px' }}>{s.icon}</div>
|
||||||
<div style={{ fontSize: '11px', fontWeight: 700, color: `${s.color},1)`, fontFamily: 'var(--fK)' }}>{s.label}</div>
|
<div style={{ fontSize: '11px', fontWeight: 700, color: `${s.color},1)` }}>{s.label}</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '3px', lineHeight: '1.3', whiteSpace: 'pre-line' }}>{s.desc}</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginTop: '3px', lineHeight: '1.3', whiteSpace: 'pre-line' }}>{s.desc}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -70,7 +70,7 @@ function HNSManualViewer() {
|
|||||||
|
|
||||||
{/* IMDG Code 위험물 등급 */}
|
{/* IMDG Code 위험물 등급 */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '13px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>IMDG Code 위험물 등급 (Hazard Classification)</div>
|
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '10px' }}>IMDG Code 위험물 등급 (Hazard Classification)</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '6px' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '💥', label: 'Class 1 — 폭발물', sub: 'Explosives', bg: 'rgba(249,115,22,.15)' },
|
{ icon: '💥', label: 'Class 1 — 폭발물', sub: 'Explosives', bg: 'rgba(249,115,22,.15)' },
|
||||||
@ -86,8 +86,8 @@ function HNSManualViewer() {
|
|||||||
<div key={c.label} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '8px', background: 'var(--bg0)', borderRadius: '5px' }}>
|
<div key={c.label} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '8px', background: 'var(--bg0)', borderRadius: '5px' }}>
|
||||||
<div style={{ width: '28px', height: '28px', background: c.bg, borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px', flexShrink: 0 }}>{c.icon}</div>
|
<div style={{ width: '28px', height: '28px', background: c.bg, borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px', flexShrink: 0 }}>{c.icon}</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>{c.label}</div>
|
<div style={{ fontSize: '10px', fontWeight: 700 }}>{c.label}</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{c.sub}</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>{c.sub}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -96,8 +96,8 @@ function HNSManualViewer() {
|
|||||||
|
|
||||||
{/* HNS 사고 대응 프로세스 */}
|
{/* HNS 사고 대응 프로세스 */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '13px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '10px' }}>HNS 사고 대응 프로세스</div>
|
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '10px' }}>HNS 사고 대응 프로세스</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'stretch', gap: '4px', fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', alignItems: 'stretch', gap: '4px', fontSize: '9px' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '🚨', step: '1단계: 사고 통보', desc: 'HNS 유출 감지\nGMDSS/DSC 신호\n관계기관 통보', color: 'rgba(239,68,68', textColor: '#f87171' },
|
{ icon: '🚨', step: '1단계: 사고 통보', desc: 'HNS 유출 감지\nGMDSS/DSC 신호\n관계기관 통보', color: 'rgba(239,68,68', textColor: '#f87171' },
|
||||||
{ icon: '🔍', step: '2단계: 상황 평가', desc: '물질 식별 (UN번호)\nSEBC 거동 판단\n위험 구역 설정', color: 'rgba(249,115,22', textColor: '#fb923c' },
|
{ icon: '🔍', step: '2단계: 상황 평가', desc: '물질 식별 (UN번호)\nSEBC 거동 판단\n위험 구역 설정', color: 'rgba(249,115,22', textColor: '#fb923c' },
|
||||||
@ -121,7 +121,7 @@ function HNSManualViewer() {
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px', marginBottom: '14px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px', marginBottom: '14px' }}>
|
||||||
{/* 선박 중심 조치 */}
|
{/* 선박 중심 조치 */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '8px' }}>🚢 선박 중심 조치</div>
|
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🚢 선박 중심 조치</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '🚁', title: '긴급 승선 (Emergency Boarding)', desc: '전문 대응팀의 사고 선박 접근 및 상황 파악' },
|
{ icon: '🚁', title: '긴급 승선 (Emergency Boarding)', desc: '전문 대응팀의 사고 선박 접근 및 상황 파악' },
|
||||||
@ -132,8 +132,8 @@ function HNSManualViewer() {
|
|||||||
].map(item => (
|
].map(item => (
|
||||||
<div key={item.title} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
|
<div key={item.title} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
|
||||||
<span style={{ fontSize: '12px', width: '20px', textAlign: 'center' }}>{item.icon}</span>
|
<span style={{ fontSize: '12px', width: '20px', textAlign: 'center' }}>{item.icon}</span>
|
||||||
<div style={{ fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '9px' }}>
|
||||||
<b style={{ color: 'var(--t1)' }}>{item.title}</b><br />
|
<b>{item.title}</b><br />
|
||||||
<span style={{ color: 'var(--t3)' }}>{item.desc}</span>
|
<span style={{ color: 'var(--t3)' }}>{item.desc}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -142,7 +142,7 @@ function HNSManualViewer() {
|
|||||||
</div>
|
</div>
|
||||||
{/* 오염물질 중심 조치 */}
|
{/* 오염물질 중심 조치 */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '8px' }}>🧪 오염물질 중심 조치</div>
|
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🧪 오염물질 중심 조치</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '💨', title: '대기 확산 모니터링', desc: '가스/증발 물질의 대기 농도 감시 (ALOHA/CAMEO)' },
|
{ icon: '💨', title: '대기 확산 모니터링', desc: '가스/증발 물질의 대기 농도 감시 (ALOHA/CAMEO)' },
|
||||||
@ -153,8 +153,8 @@ function HNSManualViewer() {
|
|||||||
].map(item => (
|
].map(item => (
|
||||||
<div key={item.title} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
|
<div key={item.title} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
|
||||||
<span style={{ fontSize: '12px', width: '20px', textAlign: 'center' }}>{item.icon}</span>
|
<span style={{ fontSize: '12px', width: '20px', textAlign: 'center' }}>{item.icon}</span>
|
||||||
<div style={{ fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '9px' }}>
|
||||||
<b style={{ color: 'var(--t1)' }}>{item.title}</b><br />
|
<b>{item.title}</b><br />
|
||||||
<span style={{ color: 'var(--t3)' }}>{item.desc}</span>
|
<span style={{ color: 'var(--t3)' }}>{item.desc}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -167,8 +167,8 @@ function HNSManualViewer() {
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '14px', marginBottom: '14px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '14px', marginBottom: '14px' }}>
|
||||||
{/* PPE */}
|
{/* PPE */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '8px' }}>🦺 개인보호장비 (PPE)</div>
|
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🦺 개인보호장비 (PPE)</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '9px' }}>
|
||||||
{[
|
{[
|
||||||
{ level: 'Level A', desc: '완전 밀폐형 화학보호복 + SCBA\n증기/가스 직접 노출 구역', color: '#ef4444' },
|
{ level: 'Level A', desc: '완전 밀폐형 화학보호복 + SCBA\n증기/가스 직접 노출 구역', color: '#ef4444' },
|
||||||
{ level: 'Level B', desc: '비밀폐형 화학보호복 + SCBA\n액체 스플래시 위험 구역', color: '#f97316' },
|
{ level: 'Level B', desc: '비밀폐형 화학보호복 + SCBA\n액체 스플래시 위험 구역', color: '#f97316' },
|
||||||
@ -184,8 +184,8 @@ function HNSManualViewer() {
|
|||||||
</div>
|
</div>
|
||||||
{/* 안전구역 */}
|
{/* 안전구역 */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '8px' }}>🔴 안전 구역 설정</div>
|
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🔴 안전 구역 설정</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', fontSize: '9px' }}>
|
||||||
<div style={{ textAlign: 'center', padding: '10px', background: 'rgba(239,68,68,.08)', border: '2px solid rgba(239,68,68,.3)', borderRadius: '50%', aspectRatio: '1', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
<div style={{ textAlign: 'center', padding: '10px', background: 'rgba(239,68,68,.08)', border: '2px solid rgba(239,68,68,.3)', borderRadius: '50%', aspectRatio: '1', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<b style={{ color: '#f87171', fontSize: '11px' }}>HOT ZONE</b>
|
<b style={{ color: '#f87171', fontSize: '11px' }}>HOT ZONE</b>
|
||||||
<span style={{ color: 'var(--t3)', fontSize: '8px' }}>직접 위험구역<br />Level A/B PPE 필수</span>
|
<span style={{ color: 'var(--t3)', fontSize: '8px' }}>직접 위험구역<br />Level A/B PPE 필수</span>
|
||||||
@ -202,12 +202,12 @@ function HNSManualViewer() {
|
|||||||
</div>
|
</div>
|
||||||
{/* 노출한계 (AEGL) */}
|
{/* 노출한계 (AEGL) */}
|
||||||
<div className={card} style={cardBg}>
|
<div className={card} style={cardBg}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '8px' }}>📊 노출한계 (AEGL 기준)</div>
|
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>📊 노출한계 (AEGL 기준)</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '6px', lineHeight: '1.4' }}>Acute Exposure Guideline Levels (EPA)<br />암모니아(NH₃) 예시 — ppm 기준</div>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '6px', lineHeight: '1.4' }}>Acute Exposure Guideline Levels (EPA)<br />암모니아(NH₃) 예시 — ppm 기준</div>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8px', fontFamily: 'var(--fM)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8px', fontFamily: 'var(--fM)' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
|
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
|
||||||
<th style={{ padding: '4px', textAlign: 'left', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>구분</th>
|
<th style={{ padding: '4px', textAlign: 'left', color: 'var(--t3)' }}>구분</th>
|
||||||
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>10분</th>
|
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>10분</th>
|
||||||
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>30분</th>
|
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>30분</th>
|
||||||
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>60분</th>
|
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>60분</th>
|
||||||
@ -221,7 +221,7 @@ function HNSManualViewer() {
|
|||||||
{ label: 'AEGL-3', color: '#f87171', vals: [2700, 1600, 1100, 550] },
|
{ label: 'AEGL-3', color: '#f87171', vals: [2700, 1600, 1100, 550] },
|
||||||
].map((row, ri) => (
|
].map((row, ri) => (
|
||||||
<tr key={row.label} style={{ borderBottom: ri < 2 ? '1px solid var(--bd)' : undefined }}>
|
<tr key={row.label} style={{ borderBottom: ri < 2 ? '1px solid var(--bd)' : undefined }}>
|
||||||
<td style={{ padding: '4px', color: row.color, fontWeight: 700, fontFamily: 'var(--fK)' }}>{row.label}</td>
|
<td style={{ padding: '4px', color: row.color, fontWeight: 700 }}>{row.label}</td>
|
||||||
{row.vals.map((v, vi) => (
|
{row.vals.map((v, vi) => (
|
||||||
<td key={vi} style={{ padding: '4px', textAlign: 'center', color: 'var(--t2)' }}>{v}</td>
|
<td key={vi} style={{ padding: '4px', textAlign: 'center', color: 'var(--t2)' }}>{v}</td>
|
||||||
))}
|
))}
|
||||||
@ -229,7 +229,7 @@ function HNSManualViewer() {
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '6px', lineHeight: '1.4' }}>
|
<div style={{ fontSize: '7px', color: 'var(--t3)', marginTop: '6px', lineHeight: '1.4' }}>
|
||||||
AEGL-1: 불쾌감 (비장애성)<br />
|
AEGL-1: 불쾌감 (비장애성)<br />
|
||||||
AEGL-2: 심각한 건강 영향 (비가역적)<br />
|
AEGL-2: 심각한 건강 영향 (비가역적)<br />
|
||||||
AEGL-3: 생명 위협 또는 사망
|
AEGL-3: 생명 위협 또는 사망
|
||||||
@ -238,7 +238,7 @@ function HNSManualViewer() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 출처 */}
|
{/* 출처 */}
|
||||||
<div style={{ padding: '10px', background: 'var(--bg3)', borderRadius: '6px', fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: '1.5' }}>
|
<div style={{ padding: '10px', background: 'var(--bg3)', borderRadius: '6px', fontSize: '8px', color: 'var(--t3)', lineHeight: '1.5' }}>
|
||||||
<b>출처:</b> Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo Project, 2024 한국어판)<br />
|
<b>출처:</b> Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo Project, 2024 한국어판)<br />
|
||||||
번역: 원해민, 이시연, 양보경, 강성길, 이성엽 — KRISO 선박해양플랜트연구소 / NOWPAP MERRAC<br />
|
번역: 원해민, 이시연, 양보경, 강성길, 이성엽 — KRISO 선박해양플랜트연구소 / NOWPAP MERRAC<br />
|
||||||
원본: Alcaro L., Brandt J., Giraud W., Mannozzi M., Nicolas-Kopec A. (2021) ISBN: 978-2-87893-147-1
|
원본: Alcaro L., Brandt J., Giraud W., Mannozzi M., Nicolas-Kopec A. (2021) ISBN: 978-2-87893-147-1
|
||||||
|
|||||||
@ -181,8 +181,8 @@ export function IncidentsLeftPanel({
|
|||||||
onChange={(e) => { setSearchTerm(e.target.value); resetPage() }}
|
onChange={(e) => { setSearchTerm(e.target.value); resetPage() }}
|
||||||
style={{
|
style={{
|
||||||
width: '100%', padding: '8px 12px 8px 32px', background: 'var(--bg0)',
|
width: '100%', padding: '8px 12px 8px 32px', background: 'var(--bg0)',
|
||||||
border: '1px solid var(--bd)', borderRadius: 'var(--rS)', color: 'var(--t1)',
|
border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
||||||
fontFamily: 'var(--fK)', fontSize: '12px', outline: 'none',
|
fontSize: '12px', outline: 'none',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -192,14 +192,14 @@ export function IncidentsLeftPanel({
|
|||||||
<div style={{ padding: '8px 16px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 }}>
|
<div style={{ padding: '8px 16px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 }}>
|
||||||
<input type="date" value={dateFrom}
|
<input type="date" value={dateFrom}
|
||||||
onChange={(e) => { setDateFrom(e.target.value); setSelectedPeriod(''); resetPage() }}
|
onChange={(e) => { setDateFrom(e.target.value); setSelectedPeriod(''); resetPage() }}
|
||||||
style={{ padding: '5px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', color: 'var(--t1)', fontFamily: 'var(--fM)', fontSize: '11px', outline: 'none', flex: 1 }}
|
style={{ padding: '5px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: '11px', outline: 'none', flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ color: 'var(--t3)', fontSize: '11px' }}>~</span>
|
<span style={{ color: 'var(--t3)', fontSize: '11px' }}>~</span>
|
||||||
<input type="date" value={dateTo}
|
<input type="date" value={dateTo}
|
||||||
onChange={(e) => { setDateTo(e.target.value); setSelectedPeriod(''); resetPage() }}
|
onChange={(e) => { setDateTo(e.target.value); setSelectedPeriod(''); resetPage() }}
|
||||||
style={{ padding: '5px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', color: 'var(--t1)', fontFamily: 'var(--fM)', fontSize: '11px', outline: 'none', flex: 1 }}
|
style={{ padding: '5px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: '11px', outline: 'none', flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<button onClick={resetPage} style={{ padding: '5px 12px', background: 'linear-gradient(135deg,var(--cyan),var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: '11px', fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)', whiteSpace: 'nowrap' }}>조회</button>
|
<button onClick={resetPage} style={{ padding: '5px 12px', background: 'linear-gradient(135deg,var(--cyan),var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: '11px', fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap' }}>조회</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Period Presets */}
|
{/* Period Presets */}
|
||||||
@ -208,14 +208,14 @@ export function IncidentsLeftPanel({
|
|||||||
<button key={p} onClick={() => handlePeriodClick(p)} style={{
|
<button key={p} onClick={() => handlePeriodClick(p)} style={{
|
||||||
padding: '3px 8px', borderRadius: '14px', border: selectedPeriod === p ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
padding: '3px 8px', borderRadius: '14px', border: selectedPeriod === p ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
||||||
background: selectedPeriod === p ? 'rgba(6,182,212,0.1)' : 'transparent',
|
background: selectedPeriod === p ? 'rgba(6,182,212,0.1)' : 'transparent',
|
||||||
color: selectedPeriod === p ? 'var(--cyan)' : 'var(--t3)', fontSize: '10px', fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)',
|
color: selectedPeriod === p ? 'var(--cyan)' : 'var(--t3)', fontSize: '10px', fontWeight: 600, cursor: 'pointer',
|
||||||
}}>{p}</button>
|
}}>{p}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Today Summary */}
|
{/* Today Summary */}
|
||||||
<div style={{ padding: '10px 16px', borderBottom: '1px solid var(--bd)', background: 'rgba(6,182,212,0.03)', flexShrink: 0 }}>
|
<div style={{ padding: '10px 16px', borderBottom: '1px solid var(--bd)', background: 'rgba(6,182,212,0.03)', flexShrink: 0 }}>
|
||||||
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t3)', letterSpacing: '0.8px', marginBottom: '8px', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t3)', letterSpacing: '0.8px', marginBottom: '8px' }}>
|
||||||
📅 오늘 ({todayLabel}) 사고 현황
|
📅 오늘 ({todayLabel}) 사고 현황
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
|
||||||
@ -224,13 +224,13 @@ export function IncidentsLeftPanel({
|
|||||||
const isActive = selectedRegion === r
|
const isActive = selectedRegion === r
|
||||||
return (
|
return (
|
||||||
<button key={r} onClick={() => { setSelectedRegion(r); resetPage() }} style={{
|
<button key={r} onClick={() => { setSelectedRegion(r); resetPage() }} style={{
|
||||||
padding: '4px 10px', borderRadius: 'var(--rS)', fontSize: '11px', fontFamily: 'var(--fK)', cursor: 'pointer',
|
padding: '4px 10px', borderRadius: 'var(--rS)', fontSize: '11px', cursor: 'pointer',
|
||||||
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
|
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
|
||||||
border: isActive ? '1px solid rgba(6,182,212,0.25)' : '1px solid var(--bd)',
|
border: isActive ? '1px solid rgba(6,182,212,0.25)' : '1px solid var(--bd)',
|
||||||
color: isActive ? 'var(--cyan)' : 'var(--t2)', fontWeight: isActive ? 700 : 400,
|
color: isActive ? 'var(--cyan)' : 'var(--t2)', fontWeight: isActive ? 700 : 400,
|
||||||
}}>
|
}}>
|
||||||
{r === '전체' ? '전체 ' : `${r} `}
|
{r === '전체' ? '전체 ' : `${r} `}
|
||||||
<span style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: isActive ? 'var(--cyan)' : 'var(--t1)' }}>
|
<span style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: isActive ? 'var(--cyan)' : undefined }}>
|
||||||
{r === '전체' ? count : `(${count})`}
|
{r === '전체' ? count : `(${count})`}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@ -250,7 +250,7 @@ export function IncidentsLeftPanel({
|
|||||||
<button key={s.id} onClick={() => { setSelectedStatus(s.id); resetPage() }} style={{
|
<button key={s.id} onClick={() => { setSelectedStatus(s.id); resetPage() }} style={{
|
||||||
padding: '4px 10px', borderRadius: '12px', border: selectedStatus === s.id ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
padding: '4px 10px', borderRadius: '12px', border: selectedStatus === s.id ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
||||||
background: 'transparent', color: selectedStatus === s.id ? 'var(--t2)' : 'var(--t3)',
|
background: 'transparent', color: selectedStatus === s.id ? 'var(--t2)' : 'var(--t3)',
|
||||||
fontSize: '10px', fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)',
|
fontSize: '10px', fontWeight: 600, cursor: 'pointer',
|
||||||
display: 'flex', alignItems: 'center', gap: '4px',
|
display: 'flex', alignItems: 'center', gap: '4px',
|
||||||
}}>
|
}}>
|
||||||
{s.dot && <span style={{ width: '6px', height: '6px', borderRadius: '50%', background: s.dot }} />}
|
{s.dot && <span style={{ width: '6px', height: '6px', borderRadius: '50%', background: s.dot }} />}
|
||||||
@ -260,14 +260,14 @@ export function IncidentsLeftPanel({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Count */}
|
{/* Count */}
|
||||||
<div style={{ padding: '6px 16px', fontSize: '11px', color: 'var(--t3)', fontFamily: 'var(--fK)', borderBottom: '1px solid rgba(30,42,74,0.3)', flexShrink: 0 }}>
|
<div style={{ padding: '6px 16px', fontSize: '11px', color: 'var(--t3)', borderBottom: '1px solid rgba(30,42,74,0.3)', flexShrink: 0 }}>
|
||||||
총 {filteredIncidents.length}건
|
총 {filteredIncidents.length}건
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Incident List */}
|
{/* Incident List */}
|
||||||
<div style={{ flex: 1, overflowY: 'auto', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
<div style={{ flex: 1, overflowY: 'auto', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
||||||
{pagedIncidents.length === 0 ? (
|
{pagedIncidents.length === 0 ? (
|
||||||
<div style={{ padding: '40px 16px', textAlign: 'center', color: 'var(--t3)', fontSize: '11px', fontFamily: 'var(--fK)' }}>
|
<div style={{ padding: '40px 16px', textAlign: 'center', color: 'var(--t3)', fontSize: '11px' }}>
|
||||||
검색 결과가 없습니다.
|
검색 결과가 없습니다.
|
||||||
</div>
|
</div>
|
||||||
) : pagedIncidents.map(inc => {
|
) : pagedIncidents.map(inc => {
|
||||||
@ -299,7 +299,7 @@ export function IncidentsLeftPanel({
|
|||||||
>
|
>
|
||||||
{/* Row 1: name + status */}
|
{/* Row 1: name + status */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '5px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '5px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', fontWeight: 700 }}>
|
||||||
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: dotStyle[inc.status], boxShadow: dotShadow[inc.status], flexShrink: 0 }} />
|
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: dotStyle[inc.status], boxShadow: dotShadow[inc.status], flexShrink: 0 }} />
|
||||||
{inc.name}
|
{inc.name}
|
||||||
</div>
|
</div>
|
||||||
@ -308,7 +308,7 @@ export function IncidentsLeftPanel({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Row 2: meta */}
|
{/* Row 2: meta */}
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', marginBottom: '5px', fontFamily: 'var(--fK)', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginBottom: '5px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
<span>📅 {inc.date} {inc.time}</span>
|
<span>📅 {inc.date} {inc.time}</span>
|
||||||
<span>🏛 {inc.office}</span>
|
<span>🏛 {inc.office}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -316,17 +316,17 @@ export function IncidentsLeftPanel({
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
|
||||||
{inc.causeType && (
|
{inc.causeType && (
|
||||||
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, fontFamily: 'var(--fK)', background: 'rgba(100,116,139,0.08)', border: '1px solid rgba(100,116,139,0.2)', color: 'var(--t2)' }}>
|
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, background: 'rgba(100,116,139,0.08)', border: '1px solid rgba(100,116,139,0.2)', color: 'var(--t2)' }}>
|
||||||
{inc.causeType}
|
{inc.causeType}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{inc.oilType && (
|
{inc.oilType && (
|
||||||
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, fontFamily: 'var(--fK)', background: 'rgba(249,115,22,0.08)', border: '1px solid rgba(249,115,22,0.2)', color: 'var(--orange)' }}>
|
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, background: 'rgba(249,115,22,0.08)', border: '1px solid rgba(249,115,22,0.2)', color: 'var(--orange)' }}>
|
||||||
{inc.oilType}
|
{inc.oilType}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{inc.prediction && (
|
{inc.prediction && (
|
||||||
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, fontFamily: 'var(--fK)', background: 'rgba(34,197,94,0.08)', border: '1px solid rgba(34,197,94,0.2)', color: 'var(--green)' }}>
|
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, background: 'rgba(34,197,94,0.08)', border: '1px solid rgba(34,197,94,0.2)', color: 'var(--green)' }}>
|
||||||
{inc.prediction}
|
{inc.prediction}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -370,8 +370,8 @@ export function IncidentsLeftPanel({
|
|||||||
<div style={{
|
<div style={{
|
||||||
padding: '8px 12px', borderTop: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0, background: 'var(--bg1)',
|
padding: '8px 12px', borderTop: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0, background: 'var(--bg1)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
|
||||||
총 <b style={{ color: 'var(--t1)' }}>{filteredIncidents.length}</b>건 중 {(safePage - 1) * pageSize + 1}-{Math.min(safePage * pageSize, filteredIncidents.length)}
|
총 <b>{filteredIncidents.length}</b>건 중 {(safePage - 1) * pageSize + 1}-{Math.min(safePage * pageSize, filteredIncidents.length)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '3px' }}>
|
||||||
<PgBtn label="⏮" disabled={safePage <= 1} onClick={() => setCurrentPage(1)} />
|
<PgBtn label="⏮" disabled={safePage <= 1} onClick={() => setCurrentPage(1)} />
|
||||||
@ -383,7 +383,7 @@ export function IncidentsLeftPanel({
|
|||||||
<PgBtn label="⏭" disabled={safePage >= totalPages} onClick={() => setCurrentPage(totalPages)} />
|
<PgBtn label="⏭" disabled={safePage >= totalPages} onClick={() => setCurrentPage(totalPages)} />
|
||||||
</div>
|
</div>
|
||||||
<select onChange={(e) => { /* page size change placeholder */ void e }} style={{
|
<select onChange={(e) => { /* page size change placeholder */ void e }} style={{
|
||||||
padding: '3px 6px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: '4px', color: 'var(--t2)', fontSize: '9px', fontFamily: 'var(--fK)', outline: 'none',
|
padding: '3px 6px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: '4px', color: 'var(--t2)', fontSize: '9px', outline: 'none',
|
||||||
}}>
|
}}>
|
||||||
<option>6건</option><option>10건</option><option>20건</option>
|
<option>6건</option><option>10건</option><option>20건</option>
|
||||||
</select>
|
</select>
|
||||||
@ -429,7 +429,7 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span style={{ fontSize: 14 }}>🌤</span>
|
<span style={{ fontSize: 14 }}>🌤</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>{data.locNm}</div>
|
<div style={{ fontSize: 11, fontWeight: 700 }}>{data.locNm}</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{data.obsDtm}</div>
|
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{data.obsDtm}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -442,13 +442,13 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
|
||||||
<div style={{ fontSize: 28 }}>{data.icon}</div>
|
<div style={{ fontSize: 28 }}>{data.icon}</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 20, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>{data.temp}</div>
|
<div style={{ fontSize: 20, fontWeight: 700, fontFamily: 'var(--fM)' }}>{data.temp}</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{data.weatherDc}</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)' }}>{data.weatherDc}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Detail grid */}
|
{/* Detail grid */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, fontSize: 9 }}>
|
||||||
<WxCell icon="💨" label="풍향/풍속" value={data.wind} />
|
<WxCell icon="💨" label="풍향/풍속" value={data.wind} />
|
||||||
<WxCell icon="🌊" label="파고" value={data.wave} />
|
<WxCell icon="🌊" label="파고" value={data.wave} />
|
||||||
<WxCell icon="💧" label="습도" value={data.humid} />
|
<WxCell icon="💧" label="습도" value={data.humid} />
|
||||||
@ -466,7 +466,7 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
|
|||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 12 }}>⬆</span>
|
<span style={{ fontSize: 12 }}>⬆</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: 'var(--t3)', fontSize: 7, fontFamily: 'var(--fK)' }}>고조 (만조)</div>
|
<div style={{ color: 'var(--t3)', fontSize: 7 }}>고조 (만조)</div>
|
||||||
<div style={{ color: '#60a5fa', fontWeight: 700, fontFamily: 'var(--fM)', fontSize: 10 }}>{data.highTide}</div>
|
<div style={{ color: '#60a5fa', fontWeight: 700, fontFamily: 'var(--fM)', fontSize: 10 }}>{data.highTide}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -477,7 +477,7 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
|
|||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 12 }}>⬇</span>
|
<span style={{ fontSize: 12 }}>⬇</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: 'var(--t3)', fontSize: 7, fontFamily: 'var(--fK)' }}>저조 (간조)</div>
|
<div style={{ color: 'var(--t3)', fontSize: 7 }}>저조 (간조)</div>
|
||||||
<div style={{ color: 'var(--cyan)', fontWeight: 700, fontFamily: 'var(--fM)', fontSize: 10 }}>{data.lowTide}</div>
|
<div style={{ color: 'var(--cyan)', fontWeight: 700, fontFamily: 'var(--fM)', fontSize: 10 }}>{data.lowTide}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -485,7 +485,7 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
|
|||||||
|
|
||||||
{/* 24h Forecast */}
|
{/* 24h Forecast */}
|
||||||
<div style={{ marginTop: 10, padding: '8px 10px', background: 'var(--bg0)', borderRadius: 6 }}>
|
<div style={{ marginTop: 10, padding: '8px 10px', background: 'var(--bg0)', borderRadius: 6 }}>
|
||||||
<div style={{ fontSize: 8, fontWeight: 700, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: 6 }}>24h 예보</div>
|
<div style={{ fontSize: 8, fontWeight: 700, color: 'var(--t3)', marginBottom: 6 }}>24h 예보</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 8, fontFamily: 'var(--fM)', color: 'var(--t2)' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 8, fontFamily: 'var(--fM)', color: 'var(--t2)' }}>
|
||||||
{data.forecast.map((f, i) => (
|
{data.forecast.map((f, i) => (
|
||||||
<div key={i} style={{ textAlign: 'center' }}>
|
<div key={i} style={{ textAlign: 'center' }}>
|
||||||
@ -502,8 +502,8 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
|
|||||||
marginTop: 8, padding: '6px 10px',
|
marginTop: 8, padding: '6px 10px',
|
||||||
background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: 6,
|
background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: 6,
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: 8, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: 3 }}>⚠ 방제 작업 영향</div>
|
<div style={{ fontSize: 8, fontWeight: 700, color: 'var(--orange)', marginBottom: 3 }}>⚠ 방제 작업 영향</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>{data.impactDc}</div>
|
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>{data.impactDc}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -520,7 +520,7 @@ function WxCell({ icon, label, value }: { icon: string; label: string; value: st
|
|||||||
<span style={{ fontSize: 12 }}>{icon}</span>
|
<span style={{ fontSize: 12 }}>{icon}</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: 'var(--t3)', fontSize: 7 }}>{label}</div>
|
<div style={{ color: 'var(--t3)', fontSize: 7 }}>{label}</div>
|
||||||
<div style={{ color: 'var(--t1)', fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</div>
|
<div style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -129,7 +129,7 @@ export function IncidentsRightPanel({
|
|||||||
borderLeft: '1px solid var(--bd)', display: 'flex', flexDirection: 'column',
|
borderLeft: '1px solid var(--bd)', display: 'flex', flexDirection: 'column',
|
||||||
alignItems: 'center', justifyContent: 'center',
|
alignItems: 'center', justifyContent: 'center',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ textAlign: 'center', color: 'var(--t3)', fontSize: '11px', fontFamily: 'var(--fK)' }}>
|
<div style={{ textAlign: 'center', color: 'var(--t3)', fontSize: '11px' }}>
|
||||||
<div style={{ fontSize: '32px', marginBottom: '8px', opacity: 0.3 }}>📊</div>
|
<div style={{ fontSize: '32px', marginBottom: '8px', opacity: 0.3 }}>📊</div>
|
||||||
좌측에서 사고를 선택하면<br />통합분석 조회가 표시됩니다
|
좌측에서 사고를 선택하면<br />통합분석 조회가 표시됩니다
|
||||||
</div>
|
</div>
|
||||||
@ -145,10 +145,10 @@ export function IncidentsRightPanel({
|
|||||||
}}>
|
}}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
|
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '2px' }}>
|
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '2px' }}>
|
||||||
🔬 통합분석 조회
|
🔬 통합분석 조회
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
|
||||||
선택: <b style={{ color: 'var(--cyan)' }}>{incident.name}</b>
|
선택: <b style={{ color: 'var(--cyan)' }}>{incident.name}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -171,13 +171,13 @@ export function IncidentsRightPanel({
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
<span style={{ fontSize: '14px' }}>{sec.icon}</span>
|
<span style={{ fontSize: '14px' }}>{sec.icon}</span>
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: sec.color, fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '12px', fontWeight: 700, color: sec.color }}>
|
||||||
{sec.title}
|
{sec.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button style={{
|
<button style={{
|
||||||
padding: '3px 10px', borderRadius: '4px', fontSize: '10px', fontWeight: 600,
|
padding: '3px 10px', borderRadius: '4px', fontSize: '10px', fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: `rgba(${sec.colorRgb},0.1)`,
|
background: `rgba(${sec.colorRgb},0.1)`,
|
||||||
border: `1px solid rgba(${sec.colorRgb},0.25)`,
|
border: `1px solid rgba(${sec.colorRgb},0.25)`,
|
||||||
color: sec.color,
|
color: sec.color,
|
||||||
@ -204,9 +204,8 @@ export function IncidentsRightPanel({
|
|||||||
/>
|
/>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '10px', fontWeight: 600, color: 'var(--t1)',
|
fontSize: '10px', fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
||||||
}}>
|
}}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</div>
|
</div>
|
||||||
@ -227,7 +226,7 @@ export function IncidentsRightPanel({
|
|||||||
|
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '6px', fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)',
|
marginTop: '6px', fontSize: '9px', color: 'var(--t3)',
|
||||||
display: 'flex', alignItems: 'center', gap: '6px',
|
display: 'flex', alignItems: 'center', gap: '6px',
|
||||||
}}>
|
}}>
|
||||||
선택: <b style={{ color: sec.color }}>{checkedCount}건</b> · {sec.totalLabel}
|
선택: <b style={{ color: sec.color }}>{checkedCount}건</b> · {sec.totalLabel}
|
||||||
@ -243,7 +242,7 @@ export function IncidentsRightPanel({
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
|
||||||
<span style={{ fontSize: '14px' }}>🐟</span>
|
<span style={{ fontSize: '14px' }}>🐟</span>
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: '#22c55e', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '12px', fontWeight: 700, color: '#22c55e' }}>
|
||||||
민감자원
|
민감자원
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -252,8 +251,8 @@ export function IncidentsRightPanel({
|
|||||||
<label key={res.id} style={{
|
<label key={res.id} style={{
|
||||||
display: 'flex', alignItems: 'center', gap: '5px',
|
display: 'flex', alignItems: 'center', gap: '5px',
|
||||||
padding: '4px 6px', background: 'rgba(34,197,94,0.06)',
|
padding: '4px 6px', background: 'rgba(34,197,94,0.06)',
|
||||||
borderRadius: '3px', fontSize: '9px', color: 'var(--t1)',
|
borderRadius: '3px', fontSize: '9px',
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}>
|
}}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -277,7 +276,7 @@ export function IncidentsRightPanel({
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
|
||||||
<span style={{ fontSize: '14px' }}>🛡</span>
|
<span style={{ fontSize: '14px' }}>🛡</span>
|
||||||
<span style={{ fontSize: '12px', fontWeight: 700, color: '#f59e0b', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '12px', fontWeight: 700, color: '#f59e0b' }}>
|
||||||
근처 방제자원
|
근처 방제자원
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -285,7 +284,7 @@ export function IncidentsRightPanel({
|
|||||||
{/* Empty state */}
|
{/* Empty state */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '10px 0', textAlign: 'center', color: 'var(--t3)',
|
padding: '10px 0', textAlign: 'center', color: 'var(--t3)',
|
||||||
fontSize: '10px', fontFamily: 'var(--fK)', lineHeight: 1.7,
|
fontSize: '10px', lineHeight: 1.7,
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '20px', marginBottom: '4px', opacity: 0.4 }}>🚢</div>
|
<div style={{ fontSize: '20px', marginBottom: '4px', opacity: 0.4 }}>🚢</div>
|
||||||
지도에서 선박을 클릭하면<br />부근 방제자원이 표시됩니다
|
지도에서 선박을 클릭하면<br />부근 방제자원이 표시됩니다
|
||||||
@ -300,7 +299,7 @@ export function IncidentsRightPanel({
|
|||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
marginBottom: '5px',
|
marginBottom: '5px',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>탐색 반경</span>
|
<span style={{ fontSize: '9px', color: 'var(--t3)' }}>탐색 반경</span>
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, fontFamily: 'var(--fM)', color: '#f59e0b' }}>
|
<span style={{ fontSize: '10px', fontWeight: 700, fontFamily: 'var(--fM)', color: '#f59e0b' }}>
|
||||||
{nearbyRadius} nm
|
{nearbyRadius} nm
|
||||||
</span>
|
</span>
|
||||||
@ -340,7 +339,7 @@ export function IncidentsRightPanel({
|
|||||||
<button key={v.mode} onClick={() => onViewModeChange(v.mode)} style={{
|
<button key={v.mode} onClick={() => onViewModeChange(v.mode)} style={{
|
||||||
flex: 1, padding: '6px', borderRadius: 'var(--rS)',
|
flex: 1, padding: '6px', borderRadius: 'var(--rS)',
|
||||||
fontSize: '10px', fontWeight: isActive ? 700 : 600,
|
fontSize: '10px', fontWeight: isActive ? 700 : 600,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
|
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
|
||||||
border: isActive ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
border: isActive ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
||||||
color: isActive ? 'var(--cyan)' : 'var(--t3)',
|
color: isActive ? 'var(--cyan)' : 'var(--t3)',
|
||||||
@ -366,8 +365,7 @@ export function IncidentsRightPanel({
|
|||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
color: analysisActive ? '#f87171' : 'var(--cyan)',
|
color: analysisActive ? '#f87171' : 'var(--cyan)',
|
||||||
fontSize: '11px', fontWeight: 700, cursor: 'pointer',
|
fontSize: '11px', fontWeight: 700, cursor: 'pointer',
|
||||||
fontFamily: 'var(--fK)',
|
}}>
|
||||||
}}>
|
|
||||||
{analysisActive ? '✕ 분석 닫기' : '🔬 통합 분석 비교 실행'}
|
{analysisActive ? '✕ 분석 닫기' : '🔬 통합 분석 비교 실행'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -255,10 +255,10 @@ export function IncidentsView() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
<span style={{ fontSize: 10, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 10, fontWeight: 700 }}>
|
||||||
🔬 통합 분석 비교
|
🔬 통합 분석 비교
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 9, color: 'var(--t3)' }}>
|
||||||
{selectedIncident?.name}
|
{selectedIncident?.name}
|
||||||
</span>
|
</span>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
@ -270,8 +270,7 @@ export function IncidentsView() {
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
background: `${t.color}18`,
|
||||||
background: `${t.color}18`,
|
|
||||||
border: `1px solid ${t.color}40`,
|
border: `1px solid ${t.color}40`,
|
||||||
color: t.color,
|
color: t.color,
|
||||||
}}
|
}}
|
||||||
@ -297,8 +296,7 @@ export function IncidentsView() {
|
|||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
cursor: 'pointer',
|
||||||
cursor: 'pointer',
|
|
||||||
background: viewMode === v.mode ? 'rgba(6,182,212,0.12)' : 'var(--bg3)',
|
background: viewMode === v.mode ? 'rgba(6,182,212,0.12)' : 'var(--bg3)',
|
||||||
border: viewMode === v.mode ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
border: viewMode === v.mode ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
|
||||||
color: viewMode === v.mode ? 'var(--cyan)' : 'var(--t3)',
|
color: viewMode === v.mode ? 'var(--cyan)' : 'var(--t3)',
|
||||||
@ -314,8 +312,7 @@ export function IncidentsView() {
|
|||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
cursor: 'pointer',
|
||||||
cursor: 'pointer',
|
|
||||||
background: 'rgba(239,68,68,0.06)',
|
background: 'rgba(239,68,68,0.06)',
|
||||||
border: '1px solid rgba(239,68,68,0.2)',
|
border: '1px solid rgba(239,68,68,0.2)',
|
||||||
color: '#f87171',
|
color: '#f87171',
|
||||||
@ -352,7 +349,7 @@ export function IncidentsView() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="text-center min-w-[180px]"
|
className="text-center min-w-[180px]"
|
||||||
style={{ fontFamily: 'var(--fK)', fontSize: 12 }}
|
style={{ fontSize: 12 }}
|
||||||
>
|
>
|
||||||
<div style={{ fontWeight: 600, marginBottom: 6, color: '#1a1a2e' }}>
|
<div style={{ fontWeight: 600, marginBottom: 6, color: '#1a1a2e' }}>
|
||||||
{incidentPopup.incident.name}
|
{incidentPopup.incident.name}
|
||||||
@ -389,8 +386,7 @@ export function IncidentsView() {
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: '8px 12px',
|
padding: '8px 12px',
|
||||||
boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
|
boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
|
||||||
fontFamily: 'var(--fK)',
|
minWidth: 150,
|
||||||
minWidth: 150,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hoverInfo.type === 'vessel' ? (
|
{hoverInfo.type === 'vessel' ? (
|
||||||
@ -481,7 +477,7 @@ export function IncidentsView() {
|
|||||||
animation: 'pd 1.5s infinite',
|
animation: 'pd 1.5s infinite',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 10, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 10, fontWeight: 700 }}>
|
||||||
AIS Live
|
AIS Live
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>MarineTraffic</span>
|
<span style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>MarineTraffic</span>
|
||||||
@ -516,7 +512,7 @@ export function IncidentsView() {
|
|||||||
gap: 6,
|
gap: 6,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--t2)' }}>
|
||||||
사고 상태
|
사고 상태
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 10 }}>
|
<div style={{ display: 'flex', gap: 10 }}>
|
||||||
@ -527,7 +523,7 @@ export function IncidentsView() {
|
|||||||
].map(s => (
|
].map(s => (
|
||||||
<div key={s.l} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<div key={s.l} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
<div style={{ width: 8, height: 8, borderRadius: '50%', background: s.c }} />
|
<div style={{ width: 8, height: 8, borderRadius: '50%', background: s.c }} />
|
||||||
<span style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{s.l}</span>
|
<span style={{ fontSize: 8, color: 'var(--t3)' }}>{s.l}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -536,8 +532,7 @@ export function IncidentsView() {
|
|||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'var(--t2)',
|
color: 'var(--t2)',
|
||||||
fontFamily: 'var(--fK)',
|
marginTop: 2,
|
||||||
marginTop: 2,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
AIS 선박
|
AIS 선박
|
||||||
@ -554,7 +549,7 @@ export function IncidentsView() {
|
|||||||
borderBottom: `7px solid ${vl.color}`,
|
borderBottom: `7px solid ${vl.color}`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{vl.type}</span>
|
<span style={{ fontSize: 8, color: 'var(--t3)' }}>{vl.type}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -604,7 +599,7 @@ export function IncidentsView() {
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--cyan)' }}>
|
<span style={{ fontSize: 9, fontWeight: 700, color: 'var(--cyan)' }}>
|
||||||
{analysisTags[0]
|
{analysisTags[0]
|
||||||
? `${analysisTags[0].icon} ${analysisTags[0].label}`
|
? `${analysisTags[0].icon} ${analysisTags[0].label}`
|
||||||
: '— 분석 결과를 선택하세요 —'}
|
: '— 분석 결과를 선택하세요 —'}
|
||||||
@ -636,7 +631,7 @@ export function IncidentsView() {
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--cyan)' }}>
|
<span style={{ fontSize: 9, fontWeight: 700, color: 'var(--cyan)' }}>
|
||||||
{analysisTags[1]
|
{analysisTags[1]
|
||||||
? `${analysisTags[1].icon} ${analysisTags[1].label}`
|
? `${analysisTags[1].icon} ${analysisTags[1].label}`
|
||||||
: '— 분석 결과를 선택하세요 —'}
|
: '— 분석 결과를 선택하세요 —'}
|
||||||
@ -682,7 +677,7 @@ export function IncidentsView() {
|
|||||||
padding: '0 10px',
|
padding: '0 10px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--fK)', color: '#f97316' }}>
|
<span style={{ fontSize: 9, fontWeight: 700, color: '#f97316' }}>
|
||||||
🛢 유출유 확산예측
|
🛢 유출유 확산예측
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -723,7 +718,7 @@ export function IncidentsView() {
|
|||||||
padding: '0 10px',
|
padding: '0 10px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--fK)', color: '#a855f7' }}>
|
<span style={{ fontSize: 9, fontWeight: 700, color: '#a855f7' }}>
|
||||||
🧪 HNS 대기확산
|
🧪 HNS 대기확산
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -756,7 +751,7 @@ export function IncidentsView() {
|
|||||||
padding: '0 10px',
|
padding: '0 10px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 9, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--cyan)' }}>
|
<span style={{ fontSize: 9, fontWeight: 700, color: 'var(--cyan)' }}>
|
||||||
🚨 긴급구난
|
🚨 긴급구난
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -794,7 +789,7 @@ export function IncidentsView() {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 9, color: 'var(--t3)' }}>
|
||||||
📊 {selectedIncident?.name} · {analysisTags.map(t => t.label).join(' + ')} 분석 결과 비교
|
📊 {selectedIncident?.name} · {analysisTags.map(t => t.label).join(' + ')} 분석 결과 비교
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 6 }}>
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
@ -804,8 +799,7 @@ export function IncidentsView() {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
cursor: 'pointer',
|
||||||
cursor: 'pointer',
|
|
||||||
background: 'rgba(59,130,246,0.1)',
|
background: 'rgba(59,130,246,0.1)',
|
||||||
border: '1px solid rgba(59,130,246,0.2)',
|
border: '1px solid rgba(59,130,246,0.2)',
|
||||||
color: '#58a6ff',
|
color: '#58a6ff',
|
||||||
@ -819,8 +813,7 @@ export function IncidentsView() {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
cursor: 'pointer',
|
||||||
cursor: 'pointer',
|
|
||||||
background: 'rgba(168,85,247,0.1)',
|
background: 'rgba(168,85,247,0.1)',
|
||||||
border: '1px solid rgba(168,85,247,0.2)',
|
border: '1px solid rgba(168,85,247,0.2)',
|
||||||
color: '#a78bfa',
|
color: '#a78bfa',
|
||||||
@ -866,8 +859,7 @@ function SplitPanelContent({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
color: 'var(--t3)',
|
color: 'var(--t3)',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: 'var(--fK)',
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
R&D 분석 결과를 선택하세요
|
R&D 분석 결과를 선택하세요
|
||||||
</div>
|
</div>
|
||||||
@ -937,13 +929,13 @@ function SplitPanelContent({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{ fontSize: 11, fontWeight: 700, color: tag.color, fontFamily: 'var(--fK)', marginBottom: 4 }}
|
style={{ fontSize: 11, fontWeight: 700, color: tag.color, marginBottom: 4 }}
|
||||||
>
|
>
|
||||||
{tag.icon} {data.title}
|
{tag.icon} {data.title}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{data.model}</div>
|
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{data.model}</div>
|
||||||
{incident && (
|
{incident && (
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 2 }}>
|
<div style={{ fontSize: 8, color: 'var(--t3)', marginTop: 2 }}>
|
||||||
사고: {incident.name} · {incident.date} {incident.time}
|
사고: {incident.name} · {incident.date} {incident.time}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -962,7 +954,7 @@ function SplitPanelContent({
|
|||||||
background: i % 2 === 0 ? 'var(--bg1)' : 'var(--bg2)',
|
background: i % 2 === 0 ? 'var(--bg1)' : 'var(--bg2)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{item.label}</span>
|
<span style={{ fontSize: 9, color: 'var(--t3)' }}>{item.label}</span>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
@ -985,8 +977,7 @@ function SplitPanelContent({
|
|||||||
border: `1px solid ${tag.color}15`,
|
border: `1px solid ${tag.color}15`,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: 'var(--t2)',
|
color: 'var(--t2)',
|
||||||
fontFamily: 'var(--fK)',
|
lineHeight: 1.6,
|
||||||
lineHeight: 1.6,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
💡 {data.summary}
|
💡 {data.summary}
|
||||||
@ -1006,7 +997,7 @@ function SplitPanelContent({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: 32, opacity: 0.3 }}>{tag.icon}</div>
|
<div style={{ fontSize: 32, opacity: 0.3 }}>{tag.icon}</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>시각화 영역</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)' }}>시각화 영역</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -1041,8 +1032,7 @@ function VesselPopupPanel({
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
boxShadow: '0 16px 48px rgba(0,0,0,0.6)',
|
boxShadow: '0 16px 48px rgba(0,0,0,0.6)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
fontFamily: 'var(--fK)',
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div
|
<div
|
||||||
@ -1115,8 +1105,7 @@ function VesselPopupPanel({
|
|||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: '#58a6ff',
|
color: '#58a6ff',
|
||||||
fontFamily: 'var(--fK)',
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{v.typS}
|
{v.typS}
|
||||||
</span>
|
</span>
|
||||||
@ -1129,8 +1118,7 @@ function VesselPopupPanel({
|
|||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: statusColor,
|
color: statusColor,
|
||||||
fontFamily: 'var(--fK)',
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{v.status}
|
{v.status}
|
||||||
</span>
|
</span>
|
||||||
@ -1177,8 +1165,7 @@ function VesselPopupPanel({
|
|||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontFamily: 'var(--fK)',
|
background: 'rgba(59,130,246,0.12)',
|
||||||
background: 'rgba(59,130,246,0.12)',
|
|
||||||
border: '1px solid rgba(59,130,246,0.3)',
|
border: '1px solid rgba(59,130,246,0.3)',
|
||||||
color: '#58a6ff',
|
color: '#58a6ff',
|
||||||
}}
|
}}
|
||||||
@ -1194,8 +1181,7 @@ function VesselPopupPanel({
|
|||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontFamily: 'var(--fK)',
|
background: 'rgba(168,85,247,0.1)',
|
||||||
background: 'rgba(168,85,247,0.1)',
|
|
||||||
border: '1px solid rgba(168,85,247,0.25)',
|
border: '1px solid rgba(168,85,247,0.25)',
|
||||||
color: '#a78bfa',
|
color: '#a78bfa',
|
||||||
}}
|
}}
|
||||||
@ -1211,8 +1197,7 @@ function VesselPopupPanel({
|
|||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontFamily: 'var(--fK)',
|
background: 'rgba(6,182,212,0.1)',
|
||||||
background: 'rgba(6,182,212,0.1)',
|
|
||||||
border: '1px solid rgba(6,182,212,0.25)',
|
border: '1px solid rgba(6,182,212,0.25)',
|
||||||
color: '#22d3ee',
|
color: '#22d3ee',
|
||||||
}}
|
}}
|
||||||
@ -1353,8 +1338,7 @@ function VesselDetailModal({ vessel: v, onClose }: { vessel: Vessel; onClose: ()
|
|||||||
color: tab === t.key ? '#58a6ff' : '#8b949e',
|
color: tab === t.key ? '#58a6ff' : '#8b949e',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
borderBottom: tab === t.key ? '2px solid #58a6ff' : '2px solid transparent',
|
borderBottom: tab === t.key ? '2px solid #58a6ff' : '2px solid transparent',
|
||||||
fontFamily: 'var(--fK)',
|
background: 'none',
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
border: 'none',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
transition: '0.15s',
|
transition: '0.15s',
|
||||||
@ -1738,8 +1722,7 @@ function TabInsurance(_props: { v: Vessel }) {
|
|||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: '#8b949e',
|
color: '#8b949e',
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.6,
|
||||||
fontFamily: 'var(--fK)',
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
💡 보험정보는 한국해운조합(KSA) Open API 및 해양수산부 선박정보시스템 연동 데이터입니다. 실시간 갱신 주기: 24시간
|
💡 보험정보는 한국해운조합(KSA) Open API 및 해양수산부 선박정보시스템 연동 데이터입니다. 실시간 갱신 주기: 24시간
|
||||||
</div>
|
</div>
|
||||||
@ -1800,8 +1783,7 @@ function TabDangerous({ v }: { v: Vessel }) {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: 'var(--fK)',
|
whiteSpace: 'nowrap',
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ color: '#8b949e' }}>화물창 2개이상 여부</span>
|
<span style={{ color: '#8b949e' }}>화물창 2개이상 여부</span>
|
||||||
@ -1834,8 +1816,7 @@ function TabDangerous({ v }: { v: Vessel }) {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: '#58a6ff',
|
color: '#58a6ff',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontFamily: 'var(--fK)',
|
whiteSpace: 'nowrap',
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -1898,8 +1879,7 @@ function TabDangerous({ v }: { v: Vessel }) {
|
|||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: '#8b949e',
|
color: '#8b949e',
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.6,
|
||||||
fontFamily: 'var(--fK)',
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
💡 위험물정보는 PORT-MIS(항만운영정보시스템) 위험물 반입신고 데이터 연동입니다. IMDG Code 최신 개정판(Amendment
|
💡 위험물정보는 PORT-MIS(항만운영정보시스템) 위험물 반입신고 데이터 연동입니다. IMDG Code 최신 개정판(Amendment
|
||||||
42-24) 기준.
|
42-24) 기준.
|
||||||
@ -1936,7 +1916,7 @@ function EmsRow({
|
|||||||
<span style={{ fontSize: 13 }}>{icon}</span>
|
<span style={{ fontSize: 13 }}>{icon}</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 9, color: '#8b949e' }}>{label}</div>
|
<div style={{ fontSize: 9, color: '#8b949e' }}>{label}</div>
|
||||||
<div style={{ fontSize: 10, fontWeight: 600, color: '#f0f6fc', fontFamily: 'var(--fK)' }}>{value}</div>
|
<div style={{ fontSize: 10, fontWeight: 600, color: '#f0f6fc' }}>{value}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -1965,8 +1945,7 @@ function ActionBtn({
|
|||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontFamily: 'var(--fK)',
|
background: bg,
|
||||||
background: bg,
|
|
||||||
border: `1px solid ${bd}`,
|
border: `1px solid ${bd}`,
|
||||||
color: fg,
|
color: fg,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 300, padding: 40, background: '#0d1117', border: '1px solid #30363d',
|
width: 300, padding: 40, background: '#0d1117', border: '1px solid #30363d',
|
||||||
borderRadius: 14, textAlign: 'center', color: '#8b949e', fontFamily: 'var(--fK)', fontSize: 12,
|
borderRadius: 14, textAlign: 'center', color: '#8b949e', fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
현장정보를 불러오는 중...
|
현장정보를 불러오는 중...
|
||||||
</div>
|
</div>
|
||||||
@ -96,7 +96,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
<span style={{ fontSize: 18 }}>📋</span>
|
<span style={{ fontSize: 18 }}>📋</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 14, fontWeight: 800, color: '#f0f6fc', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 14, fontWeight: 800, color: '#f0f6fc' }}>
|
||||||
현장정보 — {incident.name}
|
현장정보 — {incident.name}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 10, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: 10, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
||||||
@ -110,7 +110,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
{MEDIA_TABS.map(t => (
|
{MEDIA_TABS.map(t => (
|
||||||
<button key={t.id} onClick={() => setActiveTab(t.id)} style={{
|
<button key={t.id} onClick={() => setActiveTab(t.id)} style={{
|
||||||
padding: '5px 12px', borderRadius: 6, fontSize: 11, fontWeight: activeTab === t.id ? 700 : 400,
|
padding: '5px 12px', borderRadius: 6, fontSize: 11, fontWeight: activeTab === t.id ? 700 : 400,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer', border: 'none',
|
cursor: 'pointer', border: 'none',
|
||||||
background: activeTab === t.id ? 'rgba(168,85,247,0.15)' : 'transparent',
|
background: activeTab === t.id ? 'rgba(168,85,247,0.15)' : 'transparent',
|
||||||
color: activeTab === t.id ? '#c084fc' : '#8b949e',
|
color: activeTab === t.id ? '#c084fc' : '#8b949e',
|
||||||
}}>
|
}}>
|
||||||
@ -121,7 +121,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
{/* Upload */}
|
{/* Upload */}
|
||||||
<button style={{
|
<button style={{
|
||||||
padding: '5px 14px', borderRadius: 6, fontSize: 11, fontWeight: 700,
|
padding: '5px 14px', borderRadius: 6, fontSize: 11, fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer', border: 'none',
|
cursor: 'pointer', border: 'none',
|
||||||
background: 'linear-gradient(135deg,rgba(168,85,247,0.3),rgba(168,85,247,0.15))',
|
background: 'linear-gradient(135deg,rgba(168,85,247,0.3),rgba(168,85,247,0.15))',
|
||||||
color: '#c084fc',
|
color: '#c084fc',
|
||||||
}}>📤 업로드</button>
|
}}>📤 업로드</button>
|
||||||
@ -138,7 +138,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
flexShrink: 0, padding: '6px 20px', borderBottom: '1px solid #21262d',
|
flexShrink: 0, padding: '6px 20px', borderBottom: '1px solid #21262d',
|
||||||
display: 'flex', alignItems: 'center', gap: 10,
|
display: 'flex', alignItems: 'center', gap: 10,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fK)', whiteSpace: 'nowrap' }}>TIMELINE</span>
|
<span style={{ fontSize: 9, color: '#8b949e', whiteSpace: 'nowrap' }}>TIMELINE</span>
|
||||||
<div style={{ flex: 1, position: 'relative', height: 16 }}>
|
<div style={{ flex: 1, position: 'relative', height: 16 }}>
|
||||||
<div style={{ position: 'absolute', top: 7, left: 0, right: 0, height: 2, background: '#21262d', borderRadius: 1 }} />
|
<div style={{ position: 'absolute', top: 7, left: 0, right: 0, height: 2, background: '#21262d', borderRadius: 1 }} />
|
||||||
{timelineDots.map((d, i) => (
|
{timelineDots.map((d, i) => (
|
||||||
@ -173,7 +173,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span style={{ fontSize: 12 }}>📷</span>
|
<span style={{ fontSize: 12 }}>📷</span>
|
||||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc' }}>
|
||||||
현장사진 — {str(media.photoMeta, 'title', '현장 사진')}
|
현장사진 — {str(media.photoMeta, 'title', '현장 사진')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -184,7 +184,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
{/* Photo content */}
|
{/* Photo content */}
|
||||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 8 }}>
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 8 }}>
|
||||||
<div style={{ fontSize: 48, color: '#30363d' }}>📷</div>
|
<div style={{ fontSize: 48, color: '#30363d' }}>📷</div>
|
||||||
<div style={{ fontSize: 12, color: '#c9d1d9', fontWeight: 600, fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 12, color: '#c9d1d9', fontWeight: 600 }}>
|
||||||
{incident.name.replace('유류유출', '유출 현장').replace('오염', '현장')} 해상 사진
|
{incident.name.replace('유류유출', '유출 현장').replace('오염', '현장')} 해상 사진
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
||||||
@ -205,10 +205,10 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<span style={{ fontSize: 8, color: '#8b949e', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 8, color: '#8b949e' }}>
|
||||||
📷 사진 {num(media.photoMeta, 'thumbCount')}장 · {str(media.photoMeta, 'stage')}
|
📷 사진 {num(media.photoMeta, 'thumbCount')}장 · {str(media.photoMeta, 'stage')}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 8, color: '#a78bfa', cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔗 R&D 연계</span>
|
<span style={{ fontSize: 8, color: '#a78bfa', cursor: 'pointer' }}>🔗 R&D 연계</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -223,7 +223,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span style={{ fontSize: 12 }}>🎬</span>
|
<span style={{ fontSize: 12 }}>🎬</span>
|
||||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc' }}>
|
||||||
드론 영상 — {str(media.droneMeta, 'title', '드론 영상')}
|
드론 영상 — {str(media.droneMeta, 'title', '드론 영상')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -234,7 +234,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 8 }}>
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 8 }}>
|
||||||
<div style={{ fontSize: 48, color: '#30363d' }}>🎬</div>
|
<div style={{ fontSize: 48, color: '#30363d' }}>🎬</div>
|
||||||
<div style={{ fontSize: 12, color: '#c9d1d9', fontWeight: 600, fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 12, color: '#c9d1d9', fontWeight: 600 }}>
|
||||||
드론 항공 촬영 영상
|
드론 항공 촬영 영상
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
||||||
@ -255,12 +255,12 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
<span style={{ fontSize: 10, color: '#8b949e', fontFamily: 'var(--fM)' }}>02:34 / {str(media.droneMeta, 'duration')}</span>
|
<span style={{ fontSize: 10, color: '#8b949e', fontFamily: 'var(--fM)' }}>02:34 / {str(media.droneMeta, 'duration')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<span style={{ fontSize: 8, color: '#8b949e', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 8, color: '#8b949e' }}>
|
||||||
🎬 영상 {num(media.droneMeta, 'videoCount')}건 · {str(media.droneMeta, 'stage')}
|
🎬 영상 {num(media.droneMeta, 'videoCount')}건 · {str(media.droneMeta, 'stage')}
|
||||||
</span>
|
</span>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<span style={{ fontSize: 8, color: '#58a6ff', cursor: 'pointer', fontFamily: 'var(--fK)' }}>📂 전체보기</span>
|
<span style={{ fontSize: 8, color: '#58a6ff', cursor: 'pointer' }}>📂 전체보기</span>
|
||||||
<span style={{ fontSize: 8, color: '#a78bfa', cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔗 R&D 연계</span>
|
<span style={{ fontSize: 8, color: '#a78bfa', cursor: 'pointer' }}>🔗 R&D 연계</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -276,7 +276,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span style={{ fontSize: 12 }}>🛰</span>
|
<span style={{ fontSize: 12 }}>🛰</span>
|
||||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc' }}>
|
||||||
위성영상 — {str(media.satMeta, 'title', '위성영상')}
|
위성영상 — {str(media.satMeta, 'title', '위성영상')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -295,7 +295,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
{str(media.satMeta, 'detection')}
|
{str(media.satMeta, 'detection')}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 40, color: '#30363d' }}>🛰</div>
|
<div style={{ fontSize: 40, color: '#30363d' }}>🛰</div>
|
||||||
<div style={{ fontSize: 11, color: '#c9d1d9', fontWeight: 600, fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 11, color: '#c9d1d9', fontWeight: 600 }}>
|
||||||
{str(media.satMeta, 'title', '위성영상')} 위성영상
|
{str(media.satMeta, 'title', '위성영상')} 위성영상
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 8, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: 8, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
||||||
@ -306,7 +306,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
{str(media.satMeta, 'detection') === '—' && (
|
{str(media.satMeta, 'detection') === '—' && (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: 40, color: '#30363d' }}>🛰</div>
|
<div style={{ fontSize: 40, color: '#30363d' }}>🛰</div>
|
||||||
<div style={{ fontSize: 11, color: '#8b949e', fontFamily: 'var(--fK)', marginTop: 8 }}>위성영상 없음</div>
|
<div style={{ fontSize: 11, color: '#8b949e', marginTop: 8 }}>위성영상 없음</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -325,10 +325,10 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<span style={{ fontSize: 8, color: '#8b949e', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 8, color: '#8b949e' }}>
|
||||||
🛰 위성 {num(media.satMeta, 'thumbCount')}장 · {str(media.satMeta, 'sensor')}
|
🛰 위성 {num(media.satMeta, 'thumbCount')}장 · {str(media.satMeta, 'sensor')}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 8, color: '#58a6ff', cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔍 편집/측 비교</span>
|
<span style={{ fontSize: 8, color: '#58a6ff', cursor: 'pointer' }}>🔍 편집/측 비교</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -343,7 +343,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span style={{ fontSize: 12 }}>📹</span>
|
<span style={{ fontSize: 12 }}>📹</span>
|
||||||
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 12, fontWeight: 700, color: '#f0f6fc' }}>
|
||||||
CCTV — {str(media.cctvMeta, 'title', 'CCTV')}
|
CCTV — {str(media.cctvMeta, 'title', 'CCTV')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -364,7 +364,7 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ fontSize: 48, color: '#30363d' }}>📹</div>
|
<div style={{ fontSize: 48, color: '#30363d' }}>📹</div>
|
||||||
<div style={{ fontSize: 12, color: '#c9d1d9', fontWeight: 600, fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: 12, color: '#c9d1d9', fontWeight: 600 }}>
|
||||||
{str(media.cctvMeta, 'title', 'CCTV').replace('#', 'CCTV #')}
|
{str(media.cctvMeta, 'title', 'CCTV').replace('#', 'CCTV #')}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: 9, color: '#8b949e', fontFamily: 'var(--fM)' }}>
|
||||||
@ -385,12 +385,12 @@ export function MediaModal({ incident, onClose }: { incident: Incident; onClose:
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<span style={{ fontSize: 8, color: '#8b949e', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: 8, color: '#8b949e' }}>
|
||||||
📹 CCTV {num(media.cctvMeta, 'camCount')}채널 · {str(media.cctvMeta, 'location')}
|
📹 CCTV {num(media.cctvMeta, 'camCount')}채널 · {str(media.cctvMeta, 'location')}
|
||||||
</span>
|
</span>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<span style={{ fontSize: 8, color: '#ef4444', cursor: 'pointer', fontFamily: 'var(--fK)' }}>🔴 녹화영상</span>
|
<span style={{ fontSize: 8, color: '#ef4444', cursor: 'pointer' }}>🔴 녹화영상</span>
|
||||||
<span style={{ fontSize: 8, color: '#58a6ff', cursor: 'pointer', fontFamily: 'var(--fK)' }}>🎥 PTZ</span>
|
<span style={{ fontSize: 8, color: '#58a6ff', cursor: 'pointer' }}>🎥 PTZ</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -436,7 +436,7 @@ function BottomBtn({ icon, label, bg, bd, fg }: { icon: string; label: string; b
|
|||||||
return (
|
return (
|
||||||
<button style={{
|
<button style={{
|
||||||
padding: '6px 14px', borderRadius: 6, fontSize: 10, fontWeight: 700,
|
padding: '6px 14px', borderRadius: 6, fontSize: 10, fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: bg, border: `1px solid ${bd}`, color: fg,
|
background: bg, border: `1px solid ${bd}`, color: fg,
|
||||||
display: 'flex', alignItems: 'center', gap: 4,
|
display: 'flex', alignItems: 'center', gap: 4,
|
||||||
}}>{icon} {label}</button>
|
}}>{icon} {label}</button>
|
||||||
|
|||||||
@ -64,13 +64,13 @@ export function BacktrackModal({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h2 style={{
|
<h2 style={{
|
||||||
fontSize: '16px', fontWeight: 700, color: 'var(--t1)',
|
fontSize: '16px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', margin: 0,
|
margin: 0,
|
||||||
}}>
|
}}>
|
||||||
유출유 역추적 분석
|
유출유 역추적 분석
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '11px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px',
|
fontSize: '11px', color: 'var(--t3)', marginTop: '2px',
|
||||||
}}>
|
}}>
|
||||||
AIS 항적 기반 유출 선박 추정
|
AIS 항적 기반 유출 선박 추정
|
||||||
</div>
|
</div>
|
||||||
@ -97,7 +97,7 @@ export function BacktrackModal({
|
|||||||
<div>
|
<div>
|
||||||
<h3 style={{
|
<h3 style={{
|
||||||
fontSize: '12px', fontWeight: 700, color: 'var(--t2)',
|
fontSize: '12px', fontWeight: 700, color: 'var(--t2)',
|
||||||
fontFamily: 'var(--fK)', marginBottom: '10px',
|
marginBottom: '10px',
|
||||||
}}>
|
}}>
|
||||||
분석 조건
|
분석 조건
|
||||||
</h3>
|
</h3>
|
||||||
@ -114,10 +114,10 @@ export function BacktrackModal({
|
|||||||
padding: '10px 12px', background: 'var(--bg3)',
|
padding: '10px 12px', background: 'var(--bg3)',
|
||||||
border: '1px solid var(--bd)', borderRadius: '8px',
|
border: '1px solid var(--bd)', borderRadius: '8px',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '4px' }}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '12px', fontWeight: 600, fontFamily: 'var(--fM)' }}>
|
||||||
{item.value}
|
{item.value}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -127,7 +127,7 @@ export function BacktrackModal({
|
|||||||
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '8px',
|
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '8px',
|
||||||
gridColumn: '1 / -1',
|
gridColumn: '1 / -1',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '4px' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '4px' }}>
|
||||||
분석 대상 선박
|
분석 대상 선박
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '14px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>
|
||||||
@ -146,14 +146,13 @@ export function BacktrackModal({
|
|||||||
}}>
|
}}>
|
||||||
<h3 style={{
|
<h3 style={{
|
||||||
fontSize: '12px', fontWeight: 700, color: 'var(--t2)',
|
fontSize: '12px', fontWeight: 700, color: 'var(--t2)',
|
||||||
fontFamily: 'var(--fK)', margin: 0,
|
margin: 0,
|
||||||
}}>
|
}}>
|
||||||
분석 결과
|
분석 결과
|
||||||
</h3>
|
</h3>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '4px 10px', borderRadius: '12px', fontSize: '10px', fontWeight: 700,
|
padding: '4px 10px', borderRadius: '12px', fontSize: '10px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)',
|
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
|
||||||
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
|
|
||||||
color: 'var(--red)',
|
color: 'var(--red)',
|
||||||
}}>
|
}}>
|
||||||
{conditions.totalVessels}척 중 {vessels.length}척 의심
|
{conditions.totalVessels}척 중 {vessels.length}척 의심
|
||||||
@ -179,7 +178,7 @@ export function BacktrackModal({
|
|||||||
onClick={onRunAnalysis}
|
onClick={onRunAnalysis}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
|
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: 'pointer',
|
borderRadius: '8px', cursor: 'pointer',
|
||||||
background: 'linear-gradient(135deg, var(--purple), var(--cyan))',
|
background: 'linear-gradient(135deg, var(--purple), var(--cyan))',
|
||||||
border: 'none', color: '#fff',
|
border: 'none', color: '#fff',
|
||||||
}}
|
}}
|
||||||
@ -192,7 +191,7 @@ export function BacktrackModal({
|
|||||||
disabled
|
disabled
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
|
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
||||||
color: 'var(--purple)', cursor: 'wait',
|
color: 'var(--purple)', cursor: 'wait',
|
||||||
}}
|
}}
|
||||||
@ -205,7 +204,7 @@ export function BacktrackModal({
|
|||||||
onClick={onStartReplay}
|
onClick={onStartReplay}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
|
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: 'pointer',
|
borderRadius: '8px', cursor: 'pointer',
|
||||||
background: 'linear-gradient(135deg, var(--purple), var(--cyan))',
|
background: 'linear-gradient(135deg, var(--purple), var(--cyan))',
|
||||||
border: 'none', color: '#fff',
|
border: 'none', color: '#fff',
|
||||||
}}
|
}}
|
||||||
@ -240,7 +239,7 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
|
|||||||
{vessel.rank}
|
{vessel.rank}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: '13px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '13px', fontWeight: 700, fontFamily: 'var(--fM)' }}>
|
||||||
{vessel.name}
|
{vessel.name}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fM)', marginTop: '2px' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fM)', marginTop: '2px' }}>
|
||||||
@ -251,7 +250,7 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
|
|||||||
<div style={{ fontSize: '22px', fontWeight: 800, color: probColor, fontFamily: 'var(--fM)', lineHeight: 1 }}>
|
<div style={{ fontSize: '22px', fontWeight: 800, color: probColor, fontFamily: 'var(--fM)', lineHeight: 1 }}>
|
||||||
{vessel.probability}%
|
{vessel.probability}%
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>유출 확률</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>유출 확률</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -267,7 +266,7 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
|
|||||||
padding: '6px', background: 'var(--bg3)', borderRadius: '6px',
|
padding: '6px', background: 'var(--bg3)', borderRadius: '6px',
|
||||||
border: s.highlight ? '1px solid rgba(239,68,68,0.3)' : '1px solid var(--bd)',
|
border: s.highlight ? '1px solid rgba(239,68,68,0.3)' : '1px solid var(--bd)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '2px' }}>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '2px' }}>
|
||||||
{s.label}
|
{s.label}
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
@ -285,7 +284,7 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
padding: '8px 10px', background: 'rgba(239,68,68,0.05)',
|
padding: '8px 10px', background: 'rgba(239,68,68,0.05)',
|
||||||
border: '1px solid rgba(239,68,68,0.15)', borderRadius: '6px',
|
border: '1px solid rgba(239,68,68,0.15)', borderRadius: '6px',
|
||||||
fontSize: '9px', color: 'var(--t2)', fontFamily: 'var(--fK)',
|
fontSize: '9px', color: 'var(--t2)',
|
||||||
lineHeight: '1.5',
|
lineHeight: '1.5',
|
||||||
}}>
|
}}>
|
||||||
{vessel.description}
|
{vessel.description}
|
||||||
|
|||||||
@ -28,13 +28,13 @@ export function BoomDeploymentTheoryView() {
|
|||||||
🛡️
|
🛡️
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[15px] font-bold" style={{ fontFamily: 'var(--fK)', color: 'var(--t1)' }}>오일펜스 배치 최적화 알고리즘 이론</div>
|
<div className="text-[15px] font-bold">오일펜스 배치 최적화 알고리즘 이론</div>
|
||||||
<div className="text-[10px] mt-0.5" style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>Oil Boom Deployment Optimization · 유출유 확산예측 연동 · 방제효율 최대화</div>
|
<div className="text-[10px] mt-0.5" style={{ color: 'var(--t3)' }}>Oil Boom Deployment Optimization · 유출유 확산예측 연동 · 방제효율 최대화</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleExportPDF}
|
<button onClick={handleExportPDF}
|
||||||
className="px-3.5 py-1.5 rounded-md text-[10px] font-semibold cursor-pointer"
|
className="px-3.5 py-1.5 rounded-md text-[10px] font-semibold cursor-pointer"
|
||||||
style={{ border: '1px solid rgba(59,130,246,.3)', background: 'rgba(59,130,246,.08)', color: 'var(--blue)', fontFamily: 'var(--fK)' }}>
|
style={{ border: '1px solid rgba(59,130,246,.3)', background: 'rgba(59,130,246,.08)', color: 'var(--blue)' }}>
|
||||||
📤 PDF 내보내기
|
📤 PDF 내보내기
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -48,8 +48,7 @@ export function BoomDeploymentTheoryView() {
|
|||||||
onClick={() => setActivePanel(tab.id)}
|
onClick={() => setActivePanel(tab.id)}
|
||||||
className="flex-1 py-2 px-2 text-[12px] font-semibold rounded-md transition-all"
|
className="flex-1 py-2 px-2 text-[12px] font-semibold rounded-md transition-all"
|
||||||
style={{
|
style={{
|
||||||
fontFamily: 'var(--fK)',
|
background: activePanel === tab.id ? 'rgba(249,115,22,.15)' : 'var(--bg3)',
|
||||||
background: activePanel === tab.id ? 'rgba(249,115,22,.15)' : 'var(--bg3)',
|
|
||||||
color: activePanel === tab.id ? 'var(--orange)' : 'var(--t3)',
|
color: activePanel === tab.id ? 'var(--orange)' : 'var(--t3)',
|
||||||
border: activePanel === tab.id ? '1px solid rgba(249,115,22,.3)' : '1px solid var(--bd)',
|
border: activePanel === tab.id ? '1px solid rgba(249,115,22,.3)' : '1px solid var(--bd)',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -84,14 +83,14 @@ function OverviewPanel() {
|
|||||||
<div className="absolute top-0 left-0 right-0 h-[3px]" style={{ background: 'linear-gradient(90deg,var(--orange),var(--yellow),var(--cyan))' }} />
|
<div className="absolute top-0 left-0 right-0 h-[3px]" style={{ background: 'linear-gradient(90deg,var(--orange),var(--yellow),var(--cyan))' }} />
|
||||||
<div className="grid grid-cols-2 gap-5">
|
<div className="grid grid-cols-2 gap-5">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[13px] font-bold mb-2" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🛡️ 오일펜스 배치 최적화란?</div>
|
<div className="text-[13px] font-bold mb-2">🛡️ 오일펜스 배치 최적화란?</div>
|
||||||
<div className="text-[11px] leading-[1.8]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[11px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
|
||||||
해양 유류오염 발생 시 <b style={{ color: 'var(--orange)' }}>유출유 확산 예측 결과</b>와 실시간 해양환경(조류·풍향·파고)을 연동하여, 제한된 방제자원(오일펜스 길이·방제정 수)으로 <b style={{ color: 'var(--cyan)' }}>오염 확산 차단 효율을 최대화</b>하는 최적 배치 지점·형태·순서를 자동 산출하는 수치 알고리즘 체계입니다.
|
해양 유류오염 발생 시 <b style={{ color: 'var(--orange)' }}>유출유 확산 예측 결과</b>와 실시간 해양환경(조류·풍향·파고)을 연동하여, 제한된 방제자원(오일펜스 길이·방제정 수)으로 <b style={{ color: 'var(--cyan)' }}>오염 확산 차단 효율을 최대화</b>하는 최적 배치 지점·형태·순서를 자동 산출하는 수치 알고리즘 체계입니다.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[13px] font-bold mb-2" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🎯 WING 최적화 목표</div>
|
<div className="text-[13px] font-bold mb-2">🎯 WING 최적화 목표</div>
|
||||||
<div className="flex flex-col gap-[5px] text-[11px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-[5px] text-[11px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
{ num: '①', color: 'var(--orange)', bg: 'rgba(249,115,22,.05)', bd: 'rgba(249,115,22,.12)', text: '차단 면적 최대화 — 예측 유출유 확산 경계와 오일펜스 교차 면적 극대화' },
|
{ num: '①', color: 'var(--orange)', bg: 'rgba(249,115,22,.05)', bd: 'rgba(249,115,22,.12)', text: '차단 면적 최대화 — 예측 유출유 확산 경계와 오일펜스 교차 면적 극대화' },
|
||||||
{ num: '②', color: 'var(--cyan)', bg: 'rgba(6,182,212,.05)', bd: 'rgba(6,182,212,.12)', text: '도달시간 최소화 — 유출유 해안·ESI 민감구역 도달 전 선제적 차단선 구축' },
|
{ num: '②', color: 'var(--cyan)', bg: 'rgba(6,182,212,.05)', bd: 'rgba(6,182,212,.12)', text: '도달시간 최소화 — 유출유 해안·ESI 민감구역 도달 전 선제적 차단선 구축' },
|
||||||
@ -109,7 +108,7 @@ function OverviewPanel() {
|
|||||||
|
|
||||||
{/* 전체 흐름도 */}
|
{/* 전체 흐름도 */}
|
||||||
<div className="rounded-[10px] p-4 mb-4" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-4" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3.5" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>⚙️ WING 오일펜스 배치 최적화 전체 흐름</div>
|
<div className="text-xs font-bold mb-3.5">⚙️ WING 오일펜스 배치 최적화 전체 흐름</div>
|
||||||
<div className="flex items-center justify-center gap-0 flex-nowrap overflow-x-auto py-2">
|
<div className="flex items-center justify-center gap-0 flex-nowrap overflow-x-auto py-2">
|
||||||
{[
|
{[
|
||||||
{ icon: '🌊', label: '확산예측', sub: 'KOSPS/POSEIDON\nOpenDrift', color: 'var(--orange)', bg: 'rgba(249,115,22,.08)', bd: 'rgba(249,115,22,.2)' },
|
{ icon: '🌊', label: '확산예측', sub: 'KOSPS/POSEIDON\nOpenDrift', color: 'var(--orange)', bg: 'rgba(249,115,22,.08)', bd: 'rgba(249,115,22,.2)' },
|
||||||
@ -123,7 +122,7 @@ function OverviewPanel() {
|
|||||||
<div className="text-center min-w-[80px] rounded-lg px-3 py-2.5" style={{
|
<div className="text-center min-w-[80px] rounded-lg px-3 py-2.5" style={{
|
||||||
background: step.bg,
|
background: step.bg,
|
||||||
border: `${step.bold ? '2px' : '1px'} solid ${step.bd}`,
|
border: `${step.bold ? '2px' : '1px'} solid ${step.bd}`,
|
||||||
fontFamily: 'var(--fK)', fontSize: '9px'
|
fontSize: '9px'
|
||||||
}}>
|
}}>
|
||||||
<div className="text-[15px] mb-1">{step.icon}</div>
|
<div className="text-[15px] mb-1">{step.icon}</div>
|
||||||
<div style={{ fontWeight: 700, color: step.color }}>{step.label}</div>
|
<div style={{ fontWeight: 700, color: step.color }}>{step.label}</div>
|
||||||
@ -143,9 +142,9 @@ function OverviewPanel() {
|
|||||||
{ icon: '🔄', title: '자항식 오일펜스', color: 'var(--green)', desc: '방제정 예인 또는 자체 추진. U형·V형 동적 배치. 강조류 해역 적합.', specs: ['운용수심: 5m 이상', 'U형·V형·J형 동적 형태', '내조류: 조류각도 보정으로 극복'] },
|
{ icon: '🔄', title: '자항식 오일펜스', color: 'var(--green)', desc: '방제정 예인 또는 자체 추진. U형·V형 동적 배치. 강조류 해역 적합.', specs: ['운용수심: 5m 이상', 'U형·V형·J형 동적 형태', '내조류: 조류각도 보정으로 극복'] },
|
||||||
].map((boom, i) => (
|
].map((boom, i) => (
|
||||||
<div key={i} className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderTop: `3px solid ${boom.color}` }}>
|
<div key={i} className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderTop: `3px solid ${boom.color}` }}>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: boom.color, fontFamily: 'var(--fK)' }}>{boom.icon} {boom.title}</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: boom.color }}>{boom.icon} {boom.title}</div>
|
||||||
<div className="text-[10px] mb-2 leading-[1.7]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>{boom.desc}</div>
|
<div className="text-[10px] mb-2 leading-[1.7]" style={{ color: 'var(--t2)' }}>{boom.desc}</div>
|
||||||
<div className="flex flex-col gap-[3px] text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-[3px] text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{boom.specs.map((spec, j) => (
|
{boom.specs.map((spec, j) => (
|
||||||
<div key={j} className="px-[7px] py-[3px] rounded-[3px]" style={{ background: `${boom.color}11`, color: 'var(--t2)' }}>{spec}</div>
|
<div key={j} className="px-[7px] py-[3px] rounded-[3px]" style={{ background: `${boom.color}11`, color: 'var(--t2)' }}>{spec}</div>
|
||||||
))}
|
))}
|
||||||
@ -156,8 +155,8 @@ function OverviewPanel() {
|
|||||||
|
|
||||||
{/* 핵심 제약조건 */}
|
{/* 핵심 제약조건 */}
|
||||||
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--red)', fontFamily: 'var(--fK)' }}>⚠️ 최적화 핵심 제약조건</div>
|
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--red)' }}>⚠️ 최적화 핵심 제약조건</div>
|
||||||
<div className="grid grid-cols-3 gap-2.5 text-[10px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="grid grid-cols-3 gap-2.5 text-[10px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '🌊', title: '조류 제약', color: 'var(--red)', bg: 'rgba(239,68,68,.05)', bd: 'rgba(239,68,68,.15)', lines: ['조류속도 > 임계유속 시', '오일펜스 하단 통과 발생', 'U<0.7 knot 유지 필수', '임계각도 자동 계산 적용'] },
|
{ icon: '🌊', title: '조류 제약', color: 'var(--red)', bg: 'rgba(239,68,68,.05)', bd: 'rgba(239,68,68,.15)', lines: ['조류속도 > 임계유속 시', '오일펜스 하단 통과 발생', 'U<0.7 knot 유지 필수', '임계각도 자동 계산 적용'] },
|
||||||
{ icon: '📏', title: '자원 제약', color: 'var(--yellow)', bg: 'rgba(234,179,8,.05)', bd: 'rgba(234,179,8,.15)', lines: ['가용 오일펜스 총 길이', '방제정 척수·이동시간', '앵커링 가능 수심 조건', '연결부 허용 장력'] },
|
{ icon: '📏', title: '자원 제약', color: 'var(--yellow)', bg: 'rgba(234,179,8,.05)', bd: 'rgba(234,179,8,.15)', lines: ['가용 오일펜스 총 길이', '방제정 척수·이동시간', '앵커링 가능 수심 조건', '연결부 허용 장력'] },
|
||||||
@ -182,14 +181,14 @@ function DeploymentTheoryPanel() {
|
|||||||
<>
|
<>
|
||||||
{/* 차단 효율 이론 */}
|
{/* 차단 효율 이론 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📐 오일펜스 차단 효율 이론 (Boom Containment Efficiency)</div>
|
<div className="text-xs font-bold mb-3">📐 오일펜스 차단 효율 이론 (Boom Containment Efficiency)</div>
|
||||||
<div className="grid grid-cols-2 gap-3.5">
|
<div className="grid grid-cols-2 gap-3.5">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--orange)', fontFamily: 'var(--fK)' }}>① 차단 효율 함수 E(θ, U)</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--orange)' }}>① 차단 효율 함수 E(θ, U)</div>
|
||||||
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>
|
||||||
오일펜스의 차단 효율은 <b style={{ color: 'var(--t1)' }}>조류 유속(U)</b>과 <b style={{ color: 'var(--t1)' }}>오일펜스 방향각(θ)</b>의 함수입니다. 조류가 오일펜스에 수직으로 입사할수록 차단 효율이 낮아지며, 임계유속 초과 시 기름이 오일펜스 하부로 통과합니다.
|
오일펜스의 차단 효율은 <b>조류 유속(U)</b>과 <b>오일펜스 방향각(θ)</b>의 함수입니다. 조류가 오일펜스에 수직으로 입사할수록 차단 효율이 낮아지며, 임계유속 초과 시 기름이 오일펜스 하부로 통과합니다.
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(249,115,22,.2)', fontFamily: 'var(--fM)', color: 'var(--t1)' }}>
|
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(249,115,22,.2)', fontFamily: 'var(--fM)' }}>
|
||||||
E(θ,U) = 1 − <span style={{ color: 'var(--red)' }}>F<sub>loss</sub>(U<sub>n</sub>)</span><br />
|
E(θ,U) = 1 − <span style={{ color: 'var(--red)' }}>F<sub>loss</sub>(U<sub>n</sub>)</span><br />
|
||||||
U<sub>n</sub> = U · sin(θ) <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(법선방향 유속)</span><br />
|
U<sub>n</sub> = U · sin(θ) <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(법선방향 유속)</span><br />
|
||||||
E = 1 (U<sub>n</sub> ≤ U<sub>c</sub>)<br />
|
E = 1 (U<sub>n</sub> ≤ U<sub>c</sub>)<br />
|
||||||
@ -198,11 +197,11 @@ function DeploymentTheoryPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--cyan)', fontFamily: 'var(--fK)' }}>② 최적 방향각 θ* 산정</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--cyan)' }}>② 최적 방향각 θ* 산정</div>
|
||||||
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>
|
||||||
오일펜스 방향각은 조류 방향에 따라 최적화됩니다. 차단 면적과 오일 수집 효율의 <b style={{ color: 'var(--t1)' }}>트레이드오프</b>를 고려하여, 일반적으로 조류에 대해 <b style={{ color: 'var(--cyan)' }}>30°~45° 예각 배치</b>가 최적입니다.
|
오일펜스 방향각은 조류 방향에 따라 최적화됩니다. 차단 면적과 오일 수집 효율의 <b>트레이드오프</b>를 고려하여, 일반적으로 조류에 대해 <b style={{ color: 'var(--cyan)' }}>30°~45° 예각 배치</b>가 최적입니다.
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)', fontFamily: 'var(--fM)', color: 'var(--t1)' }}>
|
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)', fontFamily: 'var(--fM)' }}>
|
||||||
θ* = arcsin(U<sub>c</sub> / U) <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(임계조건)</span><br />
|
θ* = arcsin(U<sub>c</sub> / U) <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(임계조건)</span><br />
|
||||||
θ<sub>opt</sub> = argmax [A<sub>block</sub>(θ) · E(θ,U)]<br />
|
θ<sub>opt</sub> = argmax [A<sub>block</sub>(θ) · E(θ,U)]<br />
|
||||||
실용범위: 15° ≤ θ ≤ 60°<br />
|
실용범위: 15° ≤ θ ≤ 60°<br />
|
||||||
@ -214,11 +213,11 @@ function DeploymentTheoryPanel() {
|
|||||||
|
|
||||||
{/* V형·U형·J형 배치 패턴 */}
|
{/* V형·U형·J형 배치 패턴 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🔷 오일펜스 배치 형태별 이론</div>
|
<div className="text-xs font-bold mb-3">🔷 오일펜스 배치 형태별 이론</div>
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
{/* V형 */}
|
{/* V형 */}
|
||||||
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)', borderTop: '3px solid var(--cyan)' }}>
|
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)', borderTop: '3px solid var(--cyan)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--cyan)', fontFamily: 'var(--fK)' }}>V형 (Chevron)</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--cyan)' }}>V형 (Chevron)</div>
|
||||||
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(6,182,212,.04)' }}>
|
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(6,182,212,.04)' }}>
|
||||||
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
|
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
|
||||||
<defs><marker id="arr1" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
|
<defs><marker id="arr1" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
|
||||||
@ -230,8 +229,8 @@ function DeploymentTheoryPanel() {
|
|||||||
<text x="58" y="64" fill="rgba(6,182,212,.7)" fontSize="6">조류</text>
|
<text x="58" y="64" fill="rgba(6,182,212,.7)" fontSize="6">조류</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>조류 방향 정면에서 양측으로 펼친 V형. 기름을 중앙 집유점으로 유도. 회수선 배치 용이.</div>
|
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)' }}>조류 방향 정면에서 양측으로 펼친 V형. 기름을 중앙 집유점으로 유도. 회수선 배치 용이.</div>
|
||||||
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', color: 'var(--t1)', background: 'rgba(6,182,212,.05)' }}>
|
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', background: 'rgba(6,182,212,.05)' }}>
|
||||||
A<sub>V</sub> = L²·sin(2α)/2<br />
|
A<sub>V</sub> = L²·sin(2α)/2<br />
|
||||||
<span style={{ color: 'var(--t3)' }}>α: 반개각, L: 편측 길이</span><br />
|
<span style={{ color: 'var(--t3)' }}>α: 반개각, L: 편측 길이</span><br />
|
||||||
최적 α = 30°~45°
|
최적 α = 30°~45°
|
||||||
@ -240,7 +239,7 @@ function DeploymentTheoryPanel() {
|
|||||||
|
|
||||||
{/* U형 */}
|
{/* U형 */}
|
||||||
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(34,197,94,.2)', borderTop: '3px solid var(--green)' }}>
|
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(34,197,94,.2)', borderTop: '3px solid var(--green)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--green)', fontFamily: 'var(--fK)' }}>U형 (Horseshoe)</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--green)' }}>U형 (Horseshoe)</div>
|
||||||
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(34,197,94,.04)' }}>
|
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(34,197,94,.04)' }}>
|
||||||
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
|
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
|
||||||
<defs><marker id="arr2" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
|
<defs><marker id="arr2" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
|
||||||
@ -251,8 +250,8 @@ function DeploymentTheoryPanel() {
|
|||||||
<text x="58" y="5" fill="rgba(6,182,212,.7)" fontSize="6">조류</text>
|
<text x="58" y="5" fill="rgba(6,182,212,.7)" fontSize="6">조류</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>말굽형으로 기름을 완전 포위. 폐쇄형 구조로 회수 효율 최고. 저조류 해역 적합.</div>
|
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)' }}>말굽형으로 기름을 완전 포위. 폐쇄형 구조로 회수 효율 최고. 저조류 해역 적합.</div>
|
||||||
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', color: 'var(--t1)', background: 'rgba(34,197,94,.05)' }}>
|
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', background: 'rgba(34,197,94,.05)' }}>
|
||||||
A<sub>U</sub> = π·r²/2 + 2r·h<br />
|
A<sub>U</sub> = π·r²/2 + 2r·h<br />
|
||||||
<span style={{ color: 'var(--t3)' }}>r: 반경, h: 직선부 길이</span><br />
|
<span style={{ color: 'var(--t3)' }}>r: 반경, h: 직선부 길이</span><br />
|
||||||
전제: U < 0.5 knot
|
전제: U < 0.5 knot
|
||||||
@ -261,7 +260,7 @@ function DeploymentTheoryPanel() {
|
|||||||
|
|
||||||
{/* J형 */}
|
{/* J형 */}
|
||||||
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(168,85,247,.2)', borderTop: '3px solid var(--purple)' }}>
|
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(168,85,247,.2)', borderTop: '3px solid var(--purple)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--purple)', fontFamily: 'var(--fK)' }}>J형 (Skimming)</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--purple)' }}>J형 (Skimming)</div>
|
||||||
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(168,85,247,.04)' }}>
|
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(168,85,247,.04)' }}>
|
||||||
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
|
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
|
||||||
<defs><marker id="arr3" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
|
<defs><marker id="arr3" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
|
||||||
@ -273,8 +272,8 @@ function DeploymentTheoryPanel() {
|
|||||||
<text x="58" y="5" fill="rgba(6,182,212,.7)" fontSize="6">조류</text>
|
<text x="58" y="5" fill="rgba(6,182,212,.7)" fontSize="6">조류</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>직선+곡선 조합. 기름을 한쪽으로 편향 유도하여 집유. 강조류·연안 배치에 최적.</div>
|
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)' }}>직선+곡선 조합. 기름을 한쪽으로 편향 유도하여 집유. 강조류·연안 배치에 최적.</div>
|
||||||
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', color: 'var(--t1)', background: 'rgba(168,85,247,.05)' }}>
|
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', background: 'rgba(168,85,247,.05)' }}>
|
||||||
θ<sub>J</sub> = arcsin(U<sub>c</sub>/U) + δ<br />
|
θ<sub>J</sub> = arcsin(U<sub>c</sub>/U) + δ<br />
|
||||||
<span style={{ color: 'var(--t3)' }}>δ: 안전여유각(5°~10°)</span><br />
|
<span style={{ color: 'var(--t3)' }}>δ: 안전여유각(5°~10°)</span><br />
|
||||||
활용: U > 0.7 knot
|
활용: U > 0.7 knot
|
||||||
@ -285,16 +284,16 @@ function DeploymentTheoryPanel() {
|
|||||||
|
|
||||||
{/* 다단 배치 이론 */}
|
{/* 다단 배치 이론 */}
|
||||||
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🔢 다단계 차단선(Multi-Boom) 배치 이론</div>
|
<div className="text-[11px] font-bold mb-2.5">🔢 다단계 차단선(Multi-Boom) 배치 이론</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="text-[10px] leading-[1.8]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[10px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
|
||||||
단일 오일펜스로 차단 불가한 경우 <b style={{ color: 'var(--t1)' }}>직렬 다단 배치</b>로 누적 차단 효율을 향상합니다. n개 직렬 배치 시 누적 차단 효율:
|
단일 오일펜스로 차단 불가한 경우 <b>직렬 다단 배치</b>로 누적 차단 효율을 향상합니다. n개 직렬 배치 시 누적 차단 효율:
|
||||||
<div className="mt-2 rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)', color: 'var(--t1)' }}>
|
<div className="mt-2 rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
|
||||||
E<sub>total</sub> = 1 − ∏(1−E<sub>i</sub>)<br />
|
E<sub>total</sub> = 1 − ∏(1−E<sub>i</sub>)<br />
|
||||||
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>E<sub>i</sub>: i번째 오일펜스 단독 차단효율</span>
|
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>E<sub>i</sub>: i번째 오일펜스 단독 차단효율</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-[5px] text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
{ color: 'var(--green)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)', label: '2단 직렬', text: ': E_total = E₁+E₂−E₁·E₂ (예: 70%+70% → 91%)' },
|
{ color: 'var(--green)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)', label: '2단 직렬', text: ': E_total = E₁+E₂−E₁·E₂ (예: 70%+70% → 91%)' },
|
||||||
{ color: 'var(--cyan)', bg: 'rgba(6,182,212,.05)', bd: 'rgba(6,182,212,.12)', label: '단간 거리', text: ': 부표 집적 방지를 위해 ≥ 200m 이격 권장' },
|
{ color: 'var(--cyan)', bg: 'rgba(6,182,212,.05)', bd: 'rgba(6,182,212,.12)', label: '단간 거리', text: ': 부표 집적 방지를 위해 ≥ 200m 이격 권장' },
|
||||||
@ -320,19 +319,19 @@ function OptimizationPanel() {
|
|||||||
<div className="rounded-xl p-4 mb-3.5 relative overflow-hidden"
|
<div className="rounded-xl p-4 mb-3.5 relative overflow-hidden"
|
||||||
style={{ background: 'linear-gradient(135deg,rgba(168,85,247,.06),rgba(59,130,246,.04))', border: '1px solid rgba(168,85,247,.2)' }}>
|
style={{ background: 'linear-gradient(135deg,rgba(168,85,247,.06),rgba(59,130,246,.04))', border: '1px solid rgba(168,85,247,.2)' }}>
|
||||||
<div className="absolute top-0 left-0 right-0 h-[3px]" style={{ background: 'linear-gradient(90deg,var(--purple),var(--blue))' }} />
|
<div className="absolute top-0 left-0 right-0 h-[3px]" style={{ background: 'linear-gradient(90deg,var(--purple),var(--blue))' }} />
|
||||||
<div className="text-[13px] font-bold mb-2" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>⚙️ 다목적 최적화 문제 (Multi-Objective Optimization)</div>
|
<div className="text-[13px] font-bold mb-2">⚙️ 다목적 최적화 문제 (Multi-Objective Optimization)</div>
|
||||||
<div className="text-[11px] leading-[1.8]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[11px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
|
||||||
오일펜스 배치 최적화는 <b style={{ color: 'var(--purple)' }}>상충하는 복수 목적함수</b>를 동시에 만족해야 하는 전형적인 다목적 최적화 문제입니다. 차단 효율 최대화와 자원 사용 최소화는 서로 트레이드오프 관계를 가지며, <b style={{ color: 'var(--cyan)' }}>파레토 최적(Pareto Optimal) 해집합</b>에서 의사결정자가 선택합니다.
|
오일펜스 배치 최적화는 <b style={{ color: 'var(--purple)' }}>상충하는 복수 목적함수</b>를 동시에 만족해야 하는 전형적인 다목적 최적화 문제입니다. 차단 효율 최대화와 자원 사용 최소화는 서로 트레이드오프 관계를 가지며, <b style={{ color: 'var(--cyan)' }}>파레토 최적(Pareto Optimal) 해집합</b>에서 의사결정자가 선택합니다.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 목적함수 정의 */}
|
{/* 목적함수 정의 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📊 목적함수 및 제약조건 정의</div>
|
<div className="text-xs font-bold mb-3">📊 목적함수 및 제약조건 정의</div>
|
||||||
<div className="grid grid-cols-2 gap-3 mb-3">
|
<div className="grid grid-cols-2 gap-3 mb-3">
|
||||||
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(34,197,94,.2)' }}>
|
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(34,197,94,.2)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--green)', fontFamily: 'var(--fK)' }}>🎯 목적함수 F(x)</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--green)' }}>🎯 목적함수 F(x)</div>
|
||||||
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2]" style={{ fontFamily: 'var(--fM)', color: 'var(--t1)', background: 'rgba(34,197,94,.04)' }}>
|
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2]" style={{ fontFamily: 'var(--fM)', background: 'rgba(34,197,94,.04)' }}>
|
||||||
<b style={{ color: 'var(--green)' }}>최대화:</b><br />
|
<b style={{ color: 'var(--green)' }}>최대화:</b><br />
|
||||||
f₁(x) = Σ A<sub>blocked,i</sub> · w<sub>ESI,i</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(가중 차단면적)</span><br />
|
f₁(x) = Σ A<sub>blocked,i</sub> · w<sub>ESI,i</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(가중 차단면적)</span><br />
|
||||||
f₂(x) = T<sub>deadline</sub> − T<sub>deploy</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(여유시간)</span><br />
|
f₂(x) = T<sub>deadline</sub> − T<sub>deploy</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(여유시간)</span><br />
|
||||||
@ -342,8 +341,8 @@ function OptimizationPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(239,68,68,.2)' }}>
|
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(239,68,68,.2)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--red)', fontFamily: 'var(--fK)' }}>🚫 제약조건 G(x)</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--red)' }}>🚫 제약조건 G(x)</div>
|
||||||
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2]" style={{ fontFamily: 'var(--fM)', color: 'var(--t1)', background: 'rgba(239,68,68,.04)' }}>
|
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2]" style={{ fontFamily: 'var(--fM)', background: 'rgba(239,68,68,.04)' }}>
|
||||||
g₁: U·sin(θ<sub>i</sub>) ≤ U<sub>c</sub> ∀i <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(임계유속)</span><br />
|
g₁: U·sin(θ<sub>i</sub>) ≤ U<sub>c</sub> ∀i <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(임계유속)</span><br />
|
||||||
g₂: Σ L<sub>j</sub> ≤ L<sub>max</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(자원 한계)</span><br />
|
g₂: Σ L<sub>j</sub> ≤ L<sub>max</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(자원 한계)</span><br />
|
||||||
g₃: T<sub>deploy,i</sub> ≤ T<sub>arrive,i</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(시간 제약)</span><br />
|
g₃: T<sub>deploy,i</sub> ≤ T<sub>arrive,i</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>(시간 제약)</span><br />
|
||||||
@ -355,8 +354,8 @@ function OptimizationPanel() {
|
|||||||
|
|
||||||
{/* ESI 가중치 */}
|
{/* ESI 가중치 */}
|
||||||
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(234,179,8,.2)' }}>
|
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(234,179,8,.2)' }}>
|
||||||
<div className="text-[10px] font-bold mb-2" style={{ color: 'var(--yellow)', fontFamily: 'var(--fK)' }}>🏖️ ESI 가중치 w<sub>ESI</sub> 설계</div>
|
<div className="text-[10px] font-bold mb-2" style={{ color: 'var(--yellow)' }}>🏖️ ESI 가중치 w<sub>ESI</sub> 설계</div>
|
||||||
<div className="grid grid-cols-5 gap-[5px] text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="grid grid-cols-5 gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
{ grade: 'ESI 1~2', desc: '노출암반', w: 'w = 0.2', color: 'var(--green)', bg: 'rgba(34,197,94,.06)' },
|
{ grade: 'ESI 1~2', desc: '노출암반', w: 'w = 0.2', color: 'var(--green)', bg: 'rgba(34,197,94,.06)' },
|
||||||
{ grade: 'ESI 3~4', desc: '모래해변', w: 'w = 0.4', color: 'var(--cyan)', bg: 'rgba(6,182,212,.06)' },
|
{ grade: 'ESI 3~4', desc: '모래해변', w: 'w = 0.4', color: 'var(--cyan)', bg: 'rgba(6,182,212,.06)' },
|
||||||
@ -367,7 +366,7 @@ function OptimizationPanel() {
|
|||||||
<div key={i} className="p-1.5 rounded text-center" style={{ background: esi.bg, border: esi.bd ? `1px solid ${esi.bd}` : undefined }}>
|
<div key={i} className="p-1.5 rounded text-center" style={{ background: esi.bg, border: esi.bd ? `1px solid ${esi.bd}` : undefined }}>
|
||||||
<div style={{ fontWeight: 700, color: esi.color }}>{esi.grade}</div>
|
<div style={{ fontWeight: 700, color: esi.color }}>{esi.grade}</div>
|
||||||
<div style={{ color: 'var(--t3)' }}>{esi.desc}</div>
|
<div style={{ color: 'var(--t3)' }}>{esi.desc}</div>
|
||||||
<div style={{ color: 'var(--t1)', fontWeight: 700 }}>{esi.w}</div>
|
<div style={{ fontWeight: 700 }}>{esi.w}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -376,13 +375,13 @@ function OptimizationPanel() {
|
|||||||
|
|
||||||
{/* NSGA-II 알고리즘 */}
|
{/* NSGA-II 알고리즘 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--purple)', fontFamily: 'var(--fK)' }}>🧬 NSGA-II (Non-dominated Sorting Genetic Algorithm II)</div>
|
<div className="text-xs font-bold mb-3" style={{ color: 'var(--purple)' }}>🧬 NSGA-II (Non-dominated Sorting Genetic Algorithm II)</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>
|
||||||
WING의 오일펜스 배치 최적화는 다목적 유전알고리즘 <b style={{ color: 'var(--purple)' }}>NSGA-II</b>(Deb et al., 2002)를 핵심 엔진으로 사용합니다. 파레토 전면(Pareto Front)을 탐색하여 차단 효율과 자원 효율의 최적 해집합을 제공합니다.
|
WING의 오일펜스 배치 최적화는 다목적 유전알고리즘 <b style={{ color: 'var(--purple)' }}>NSGA-II</b>(Deb et al., 2002)를 핵심 엔진으로 사용합니다. 파레토 전면(Pareto Front)을 탐색하여 차단 효율과 자원 효율의 최적 해집합을 제공합니다.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-1 text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
'염색체 구조 : [배치지점 좌표, 방향각θ, 길이L, 형태, 배치순서]',
|
'염색체 구조 : [배치지점 좌표, 방향각θ, 길이L, 형태, 배치순서]',
|
||||||
'집단 크기 : 100~200개체 · 세대수 50~200',
|
'집단 크기 : 100~200개체 · 세대수 50~200',
|
||||||
@ -397,8 +396,8 @@ function OptimizationPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] font-bold mb-[7px]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>NSGA-II 5단계 진화 루프</div>
|
<div className="text-[10px] font-bold mb-[7px]" style={{ color: 'var(--t2)' }}>NSGA-II 5단계 진화 루프</div>
|
||||||
<div className="flex flex-col gap-[5px] text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
{ step: '①', title: '초기 집단 생성', desc: '확산예측 결과 기반 랜덤 + 휴리스틱 배치안 혼합 초기화' },
|
{ step: '①', title: '초기 집단 생성', desc: '확산예측 결과 기반 랜덤 + 휴리스틱 배치안 혼합 초기화' },
|
||||||
{ step: '②', title: '적합도 평가', desc: '유출유 확산 시뮬레이터로 각 배치안의 차단면적·도달시간 계산' },
|
{ step: '②', title: '적합도 평가', desc: '유출유 확산 시뮬레이터로 각 배치안의 차단면적·도달시간 계산' },
|
||||||
@ -418,9 +417,9 @@ function OptimizationPanel() {
|
|||||||
|
|
||||||
{/* 보조 알고리즘 비교 */}
|
{/* 보조 알고리즘 비교 */}
|
||||||
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🔬 보조 최적화 알고리즘 비교 적용</div>
|
<div className="text-[11px] font-bold mb-2.5">🔬 보조 최적화 알고리즘 비교 적용</div>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full" style={{ borderCollapse: 'collapse', fontFamily: 'var(--fK)', fontSize: '10px' }}>
|
<table className="w-full" style={{ borderCollapse: 'collapse', fontSize: '10px' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'rgba(255,255,255,.03)', borderBottom: '1px solid var(--bdL)' }}>
|
<tr style={{ background: 'rgba(255,255,255,.03)', borderBottom: '1px solid var(--bdL)' }}>
|
||||||
{['알고리즘', '유형', '장점', '단점', 'WING 활용'].map(h => (
|
{['알고리즘', '유형', '장점', '단점', 'WING 활용'].map(h => (
|
||||||
@ -457,12 +456,12 @@ function FluidDynamicsPanel() {
|
|||||||
<>
|
<>
|
||||||
{/* 유동 수치 모델 */}
|
{/* 유동 수치 모델 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🌊 오일펜스 주변 유동 수치 모델</div>
|
<div className="text-xs font-bold mb-3">🌊 오일펜스 주변 유동 수치 모델</div>
|
||||||
<div className="grid grid-cols-2 gap-3.5">
|
<div className="grid grid-cols-2 gap-3.5">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--blue)', fontFamily: 'var(--fK)' }}>① 오일펜스 항력 모델</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--blue)' }}>① 오일펜스 항력 모델</div>
|
||||||
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>오일펜스에 작용하는 항력은 조류속도의 제곱에 비례합니다. 오일펜스 구조 변형(catenary형태)을 고려한 동적 항력 계산.</div>
|
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>오일펜스에 작용하는 항력은 조류속도의 제곱에 비례합니다. 오일펜스 구조 변형(catenary형태)을 고려한 동적 항력 계산.</div>
|
||||||
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(59,130,246,.2)', fontFamily: 'var(--fM)', color: 'var(--t1)' }}>
|
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(59,130,246,.2)', fontFamily: 'var(--fM)' }}>
|
||||||
F<sub>D</sub> = ½ · ρ · C<sub>D</sub> · A · U<sub>n</sub>²<br />
|
F<sub>D</sub> = ½ · ρ · C<sub>D</sub> · A · U<sub>n</sub>²<br />
|
||||||
T = F<sub>D</sub> · L / (2·sin(α))<br />
|
T = F<sub>D</sub> · L / (2·sin(α))<br />
|
||||||
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>C<sub>D</sub>: 항력계수(≈1.2), A: 수중 투영면적</span><br />
|
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>C<sub>D</sub>: 항력계수(≈1.2), A: 수중 투영면적</span><br />
|
||||||
@ -470,9 +469,9 @@ function FluidDynamicsPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--orange)', fontFamily: 'var(--fK)' }}>② 기름 통과(Splash-over) 조건</div>
|
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--orange)' }}>② 기름 통과(Splash-over) 조건</div>
|
||||||
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>조류 유속이 임계값을 초과하면 기름이 파도를 타고 오일펜스를 넘어가는 Splash-over가 발생합니다.</div>
|
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>조류 유속이 임계값을 초과하면 기름이 파도를 타고 오일펜스를 넘어가는 Splash-over가 발생합니다.</div>
|
||||||
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(249,115,22,.2)', fontFamily: 'var(--fM)', color: 'var(--t1)' }}>
|
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(249,115,22,.2)', fontFamily: 'var(--fM)' }}>
|
||||||
Fr = U<sub>n</sub> / √(g·Δρ/ρ·h)<br />
|
Fr = U<sub>n</sub> / √(g·Δρ/ρ·h)<br />
|
||||||
Splash-over: Fr > 0.5~0.6<br />
|
Splash-over: Fr > 0.5~0.6<br />
|
||||||
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>Fr: 수정 Froude수, h: 오일펜스 수중깊이</span><br />
|
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>Fr: 수정 Froude수, h: 오일펜스 수중깊이</span><br />
|
||||||
@ -484,10 +483,10 @@ function FluidDynamicsPanel() {
|
|||||||
|
|
||||||
{/* Catenary 변형 모델 */}
|
{/* Catenary 변형 모델 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🔗 오일펜스 현수선(Catenary) 변형 모델</div>
|
<div className="text-xs font-bold mb-3">🔗 오일펜스 현수선(Catenary) 변형 모델</div>
|
||||||
<div className="grid grid-cols-2 gap-3.5">
|
<div className="grid grid-cols-2 gap-3.5">
|
||||||
<div className="text-[10px] leading-[1.8]" style={{ color: 'var(--t2)', fontFamily: 'var(--fK)' }}>
|
<div className="text-[10px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
|
||||||
조류와 바람에 의해 오일펜스는 현수선(Catenary) 형태로 변형됩니다. 실제 차단 길이가 설계 길이보다 짧아지며, 최적화 알고리즘에서 <b style={{ color: 'var(--t1)' }}>변형 후 유효 차단 길이</b> L<sub>eff</sub>를 계산합니다.
|
조류와 바람에 의해 오일펜스는 현수선(Catenary) 형태로 변형됩니다. 실제 차단 길이가 설계 길이보다 짧아지며, 최적화 알고리즘에서 <b>변형 후 유효 차단 길이</b> L<sub>eff</sub>를 계산합니다.
|
||||||
<div className="mt-2 rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
|
<div className="mt-2 rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
|
||||||
y(x) = a·cosh(x/a) − a<br />
|
y(x) = a·cosh(x/a) − a<br />
|
||||||
L<sub>arc</sub> = 2a·sinh(L<sub>span</sub>/(2a))<br />
|
L<sub>arc</sub> = 2a·sinh(L<sub>span</sub>/(2a))<br />
|
||||||
@ -495,7 +494,7 @@ function FluidDynamicsPanel() {
|
|||||||
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>a: catenary 파라미터, φ: 최대 편향각</span>
|
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>a: catenary 파라미터, φ: 최대 편향각</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5 text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-1.5 text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
<div className="text-[10px] font-bold mb-1" style={{ color: 'var(--t2)' }}>변형 단계별 유효 차단 길이 보정</div>
|
<div className="text-[10px] font-bold mb-1" style={{ color: 'var(--t2)' }}>변형 단계별 유효 차단 길이 보정</div>
|
||||||
{[
|
{[
|
||||||
{ cond: 'U < 0.3 knot', result: 'L_eff ≈ L (직선 유지)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)' },
|
{ cond: 'U < 0.3 knot', result: 'L_eff ≈ L (직선 유지)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)' },
|
||||||
@ -513,8 +512,8 @@ function FluidDynamicsPanel() {
|
|||||||
|
|
||||||
{/* 유막 포집 모델 */}
|
{/* 유막 포집 모델 */}
|
||||||
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🛢️ 오일펜스 내 유막 포집 동역학</div>
|
<div className="text-[11px] font-bold mb-2.5">🛢️ 오일펜스 내 유막 포집 동역학</div>
|
||||||
<div className="grid grid-cols-2 gap-3 text-[10px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="grid grid-cols-2 gap-3 text-[10px]" style={{ color: 'var(--t2)' }}>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-bold mb-1.5" style={{ color: 'var(--cyan)' }}>포집 기름 체적 변화율</div>
|
<div className="font-bold mb-1.5" style={{ color: 'var(--cyan)' }}>포집 기름 체적 변화율</div>
|
||||||
<div className="rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
|
<div className="rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
|
||||||
@ -548,13 +547,13 @@ function FieldApplicationPanel() {
|
|||||||
<>
|
<>
|
||||||
{/* 배치 5단계 절차 */}
|
{/* 배치 5단계 절차 */}
|
||||||
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-xs font-bold mb-3" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>🗺️ WING 오일펜스 배치 의사결정 5단계</div>
|
<div className="text-xs font-bold mb-3">🗺️ WING 오일펜스 배치 의사결정 5단계</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{steps.map(step => (
|
{steps.map(step => (
|
||||||
<div key={step.num} className="flex gap-3 items-start p-3 rounded-lg" style={{ background: step.bg, border: `1px solid ${step.bd}` }}>
|
<div key={step.num} className="flex gap-3 items-start p-3 rounded-lg" style={{ background: step.bg, border: `1px solid ${step.bd}` }}>
|
||||||
<div className="min-w-[36px] h-[36px] rounded-[9px] flex items-center justify-center font-extrabold text-sm flex-shrink-0" style={{ background: step.numBg, border: `1px solid ${step.numBd}`, color: step.color }}>{step.num}</div>
|
<div className="min-w-[36px] h-[36px] rounded-[9px] flex items-center justify-center font-extrabold text-sm flex-shrink-0" style={{ background: step.numBg, border: `1px solid ${step.numBd}`, color: step.color }}>{step.num}</div>
|
||||||
<div className="text-[10px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="text-[10px]" style={{ color: 'var(--t2)' }}>
|
||||||
<div className="font-bold mb-1" style={{ color: 'var(--t1)' }}>{step.title}</div>
|
<div className="font-bold mb-1">{step.title}</div>
|
||||||
<div className="leading-[1.7]" style={{ color: 'var(--t2)' }}>{step.desc}</div>
|
<div className="leading-[1.7]" style={{ color: 'var(--t2)' }}>{step.desc}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -564,8 +563,8 @@ function FieldApplicationPanel() {
|
|||||||
|
|
||||||
{/* 해역별 적용 특성 */}
|
{/* 해역별 적용 특성 */}
|
||||||
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
|
||||||
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📍 해역별 적용 특성 및 전략</div>
|
<div className="text-[11px] font-bold mb-2.5">📍 해역별 적용 특성 및 전략</div>
|
||||||
<div className="grid grid-cols-3 gap-2.5 text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="grid grid-cols-3 gap-2.5 text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{[
|
{[
|
||||||
{ icon: '🌊', title: '서해 (조차 대형)', color: 'var(--blue)', bg: 'rgba(59,130,246,.05)', bd: 'rgba(59,130,246,.12)', desc: '최대 조차 9m (인천), 조류 최대 3~5 knot. J형 배치 주력. 조석 전환 재배치 필수. 앵커링 수심 급변화 주의.' },
|
{ icon: '🌊', title: '서해 (조차 대형)', color: 'var(--blue)', bg: 'rgba(59,130,246,.05)', bd: 'rgba(59,130,246,.12)', desc: '최대 조차 9m (인천), 조류 최대 3~5 knot. J형 배치 주력. 조석 전환 재배치 필수. 앵커링 수심 급변화 주의.' },
|
||||||
{ icon: '🌿', title: '남해 (다도해)', color: 'var(--green)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)', desc: '복잡한 해안선·섬. 조류 1~2 knot. V형·U형 복합 배치. 좁은 수로 통제 우선. ESI 고등급 갯벌 보호.' },
|
{ icon: '🌿', title: '남해 (다도해)', color: 'var(--green)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)', desc: '복잡한 해안선·섬. 조류 1~2 knot. V형·U형 복합 배치. 좁은 수로 통제 우선. ESI 고등급 갯벌 보호.' },
|
||||||
@ -632,15 +631,15 @@ function ReferencesPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="text-xs font-bold mb-1" style={{ color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📚 오일펜스 배치 최적화 이론 근거 문헌</div>
|
<div className="text-xs font-bold mb-1">📚 오일펜스 배치 최적화 이론 근거 문헌</div>
|
||||||
<div className="text-[10px] mb-3.5" style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>총 12편 · 4개 카테고리</div>
|
<div className="text-[10px] mb-3.5" style={{ color: 'var(--t3)' }}>총 12편 · 4개 카테고리</div>
|
||||||
|
|
||||||
{categories.map((cat, ci) => (
|
{categories.map((cat, ci) => (
|
||||||
<div key={ci} className="mb-4">
|
<div key={ci} className="mb-4">
|
||||||
<div className="text-[10px] font-bold mb-[7px] flex items-center gap-1.5" style={{ color: cat.color, fontFamily: 'var(--fK)' }}>
|
<div className="text-[10px] font-bold mb-[7px] flex items-center gap-1.5" style={{ color: cat.color }}>
|
||||||
<span className="px-[7px] py-0.5 rounded" style={{ background: cat.bg, border: `1px solid ${cat.bd}` }}>{cat.title}</span>
|
<span className="px-[7px] py-0.5 rounded" style={{ background: cat.bg, border: `1px solid ${cat.bd}` }}>{cat.title}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-[5px] text-[9px]" style={{ fontFamily: 'var(--fK)', color: 'var(--t2)' }}>
|
<div className="flex flex-col gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
|
||||||
{cat.refs.map((ref, ri) => (
|
{cat.refs.map((ref, ri) => (
|
||||||
<div key={ri} className="p-[9px] px-3 rounded-[7px] grid gap-2" style={{
|
<div key={ri} className="p-[9px] px-3 rounded-[7px] grid gap-2" style={{
|
||||||
gridTemplateColumns: '24px 1fr',
|
gridTemplateColumns: '24px 1fr',
|
||||||
@ -656,7 +655,7 @@ function ReferencesPanel() {
|
|||||||
{ri + 1 === 1 ? '①' : ri + 1 === 2 ? '②' : ri + 1 === 3 ? '③' : '④'}
|
{ri + 1 === 1 ? '①' : ri + 1 === 2 ? '②' : ri + 1 === 3 ? '③' : '④'}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-bold mb-0.5" style={{ color: 'var(--t1)' }}>{ref.title}</div>
|
<div className="font-bold mb-0.5">{ref.title}</div>
|
||||||
<div className="leading-[1.6]" style={{ color: 'var(--t3)' }}>{ref.author}</div>
|
<div className="leading-[1.6]" style={{ color: 'var(--t3)' }}>{ref.author}</div>
|
||||||
<div className="mt-0.5" style={{ color: 'var(--t2)' }}>{ref.desc}</div>
|
<div className="mt-0.5" style={{ color: 'var(--t2)' }}>{ref.desc}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -83,8 +83,7 @@ const InfoLayerSection = ({
|
|||||||
padding: '4px 8px',
|
padding: '4px 8px',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
border: '1px solid var(--cyan)',
|
||||||
border: '1px solid var(--cyan)',
|
|
||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
color: 'var(--cyan)',
|
color: 'var(--cyan)',
|
||||||
@ -121,8 +120,7 @@ const InfoLayerSection = ({
|
|||||||
padding: '4px 8px',
|
padding: '4px 8px',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
border: '1px solid var(--red)',
|
||||||
border: '1px solid var(--red)',
|
|
||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
color: 'var(--red)',
|
color: 'var(--red)',
|
||||||
|
|||||||
@ -69,8 +69,7 @@ const OilBoomSection = ({
|
|||||||
padding: '6px 8px',
|
padding: '6px 8px',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
borderRadius: 'var(--rS)',
|
||||||
borderRadius: 'var(--rS)',
|
|
||||||
border: boomPlacementTab === tab.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
|
border: boomPlacementTab === tab.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
|
||||||
background: boomPlacementTab === tab.id ? 'rgba(245,158,11,0.1)' : 'var(--bg0)',
|
background: boomPlacementTab === tab.id ? 'rgba(245,158,11,0.1)' : 'var(--bg0)',
|
||||||
color: boomPlacementTab === tab.id ? 'var(--orange)' : 'var(--t3)',
|
color: boomPlacementTab === tab.id ? 'var(--orange)' : 'var(--t3)',
|
||||||
@ -99,8 +98,7 @@ const OilBoomSection = ({
|
|||||||
padding: '6px 10px',
|
padding: '6px 10px',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)',
|
borderRadius: 'var(--rS)',
|
||||||
borderRadius: 'var(--rS)',
|
|
||||||
border: '1px solid var(--bd)',
|
border: '1px solid var(--bd)',
|
||||||
background: 'var(--bg0)',
|
background: 'var(--bg0)',
|
||||||
color: (boomLines.length === 0 && !isDrawingBoom && !containmentResult) ? 'var(--t3)' : 'var(--red)',
|
color: (boomLines.length === 0 && !isDrawingBoom && !containmentResult) ? 'var(--t3)' : 'var(--red)',
|
||||||
@ -130,7 +128,7 @@ const OilBoomSection = ({
|
|||||||
<div style={{ fontSize: '18px', fontWeight: 700, color: metric.color, fontFamily: 'var(--fM)', marginBottom: '2px' }}>
|
<div style={{ fontSize: '18px', fontWeight: 700, color: metric.color, fontFamily: 'var(--fM)', marginBottom: '2px' }}>
|
||||||
{metric.value}
|
{metric.value}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>
|
||||||
{metric.label}
|
{metric.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -148,16 +146,16 @@ const OilBoomSection = ({
|
|||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '8px' }}>
|
||||||
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }} />
|
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }} />
|
||||||
<span style={{ fontSize: '10px', fontWeight: 700, color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '10px', fontWeight: 700, color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
|
||||||
{oilTrajectory.length > 0 ? '확산 데이터 준비 완료' : '확산 예측을 먼저 실행하세요'}
|
{oilTrajectory.length > 0 ? '확산 데이터 준비 완료' : '확산 예측을 먼저 실행하세요'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 style={{ fontSize: '13px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: '8px' }}>
|
<h4 style={{ fontSize: '13px', fontWeight: 700, marginBottom: '8px' }}>
|
||||||
확산 예측 기반 최적 배치안
|
확산 예측 기반 최적 배치안
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: '1.5', marginBottom: '10px' }}>
|
<p style={{ fontSize: '9px', color: 'var(--t3)', lineHeight: '1.5', marginBottom: '10px' }}>
|
||||||
{oilTrajectory.length > 0
|
{oilTrajectory.length > 0
|
||||||
? '확산 궤적을 분석하여 해류 직교 방향 1차 방어선, U형 포위 2차 방어선, 연안 보호 3차 방어선을 자동 생성합니다.'
|
? '확산 궤적을 분석하여 해류 직교 방향 1차 방어선, U형 포위 2차 방어선, 연안 보호 3차 방어선을 자동 생성합니다.'
|
||||||
: '상단에서 확산 예측을 실행한 뒤 AI 배치를 적용할 수 있습니다.'
|
: '상단에서 확산 예측을 실행한 뒤 AI 배치를 적용할 수 있습니다.'
|
||||||
@ -179,8 +177,7 @@ const OilBoomSection = ({
|
|||||||
padding: '10px',
|
padding: '10px',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)',
|
background: oilTrajectory.length > 0 ? 'rgba(245,158,11,0.15)' : 'var(--bg0)',
|
||||||
background: oilTrajectory.length > 0 ? 'rgba(245,158,11,0.15)' : 'var(--bg0)',
|
|
||||||
border: oilTrajectory.length > 0 ? '2px solid var(--orange)' : '1px solid var(--bd)',
|
border: oilTrajectory.length > 0 ? '2px solid var(--orange)' : '1px solid var(--bd)',
|
||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
color: oilTrajectory.length > 0 ? 'var(--orange)' : 'var(--t3)',
|
color: oilTrajectory.length > 0 ? 'var(--orange)' : 'var(--t3)',
|
||||||
@ -194,7 +191,7 @@ const OilBoomSection = ({
|
|||||||
|
|
||||||
{/* 알고리즘 설정 */}
|
{/* 알고리즘 설정 */}
|
||||||
<div>
|
<div>
|
||||||
<h4 style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: '8px', letterSpacing: '0.5px' }}>
|
<h4 style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '8px', letterSpacing: '0.5px' }}>
|
||||||
📊 배치 알고리즘 설정
|
📊 배치 알고리즘 설정
|
||||||
</h4>
|
</h4>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
@ -208,7 +205,7 @@ const OilBoomSection = ({
|
|||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
padding: '6px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)'
|
padding: '6px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)'
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>● {setting.label}</span>
|
<span style={{ fontSize: '9px', color: 'var(--t3)' }}>● {setting.label}</span>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@ -220,7 +217,7 @@ const OilBoomSection = ({
|
|||||||
className="boom-setting-input"
|
className="boom-setting-input"
|
||||||
step={setting.key === 'waveHeightCorrectionFactor' ? 0.1 : 1}
|
step={setting.key === 'waveHeightCorrectionFactor' ? 0.1 : 1}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: '9px', color: 'var(--orange)', fontFamily: 'var(--fK)' }}>{setting.unit}</span>
|
<span style={{ fontSize: '9px', color: 'var(--orange)' }}>{setting.unit}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -238,7 +235,7 @@ const OilBoomSection = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => { onDrawingBoomChange(true); onDrawingPointsChange([]) }}
|
onClick={() => { onDrawingBoomChange(true); onDrawingPointsChange([]) }}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '10px', fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
flex: 1, padding: '10px', fontSize: '11px', fontWeight: 700,
|
||||||
background: 'rgba(245,158,11,0.15)', border: '2px solid var(--orange)',
|
background: 'rgba(245,158,11,0.15)', border: '2px solid var(--orange)',
|
||||||
borderRadius: 'var(--rS)', color: 'var(--orange)', cursor: 'pointer', transition: '0.15s'
|
borderRadius: 'var(--rS)', color: 'var(--orange)', cursor: 'pointer', transition: '0.15s'
|
||||||
}}
|
}}
|
||||||
@ -268,7 +265,7 @@ const OilBoomSection = ({
|
|||||||
}}
|
}}
|
||||||
disabled={drawingPoints.length < 2}
|
disabled={drawingPoints.length < 2}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '10px', fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
flex: 1, padding: '10px', fontSize: '11px', fontWeight: 700,
|
||||||
background: drawingPoints.length >= 2 ? 'rgba(34,197,94,0.15)' : 'var(--bg0)',
|
background: drawingPoints.length >= 2 ? 'rgba(34,197,94,0.15)' : 'var(--bg0)',
|
||||||
border: drawingPoints.length >= 2 ? '2px solid var(--green)' : '1px solid var(--bd)',
|
border: drawingPoints.length >= 2 ? '2px solid var(--green)' : '1px solid var(--bd)',
|
||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
@ -281,7 +278,7 @@ const OilBoomSection = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => { onDrawingBoomChange(false); onDrawingPointsChange([]) }}
|
onClick={() => { onDrawingBoomChange(false); onDrawingPointsChange([]) }}
|
||||||
style={{
|
style={{
|
||||||
padding: '10px 14px', fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
padding: '10px 14px', fontSize: '11px', fontWeight: 700,
|
||||||
background: 'rgba(239,68,68,0.1)', border: '1px solid var(--red)',
|
background: 'rgba(239,68,68,0.1)', border: '1px solid var(--red)',
|
||||||
borderRadius: 'var(--rS)', color: 'var(--red)', cursor: 'pointer', transition: '0.15s'
|
borderRadius: 'var(--rS)', color: 'var(--red)', cursor: 'pointer', transition: '0.15s'
|
||||||
}}
|
}}
|
||||||
@ -297,19 +294,19 @@ const OilBoomSection = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
padding: '8px 10px', background: 'rgba(245,158,11,0.05)',
|
padding: '8px 10px', background: 'rgba(245,158,11,0.05)',
|
||||||
border: '1px solid rgba(245,158,11,0.3)', borderRadius: 'var(--rS)',
|
border: '1px solid rgba(245,158,11,0.3)', borderRadius: 'var(--rS)',
|
||||||
display: 'flex', gap: '12px', fontSize: '10px', fontFamily: 'var(--fK)', color: 'var(--t2)'
|
display: 'flex', gap: '12px', fontSize: '10px', color: 'var(--t2)'
|
||||||
}}>
|
}}>
|
||||||
<span>포인트: <strong style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{drawingPoints.length}</strong></span>
|
<span>포인트: <strong style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{drawingPoints.length}</strong></span>
|
||||||
<span>길이: <strong style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>{computePolylineLength(drawingPoints).toFixed(0)}m</strong></span>
|
<span>길이: <strong style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>{computePolylineLength(drawingPoints).toFixed(0)}m</strong></span>
|
||||||
{drawingPoints.length >= 2 && (
|
{drawingPoints.length >= 2 && (
|
||||||
<span>방위각: <strong style={{ color: 'var(--t1)', fontFamily: 'var(--fM)' }}>{computeBearing(drawingPoints[0], drawingPoints[drawingPoints.length - 1]).toFixed(0)}°</strong></span>
|
<span>방위각: <strong style={{ fontFamily: 'var(--fM)' }}>{computeBearing(drawingPoints[0], drawingPoints[drawingPoints.length - 1]).toFixed(0)}°</strong></span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 배치된 라인 목록 */}
|
{/* 배치된 라인 목록 */}
|
||||||
{boomLines.length === 0 ? (
|
{boomLines.length === 0 ? (
|
||||||
<p style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', textAlign: 'center', padding: '16px 0' }}>
|
<p style={{ fontSize: '10px', color: 'var(--t3)', textAlign: 'center', padding: '16px 0' }}>
|
||||||
배치된 오일펜스 라인이 없습니다.
|
배치된 오일펜스 라인이 없습니다.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@ -329,8 +326,8 @@ const OilBoomSection = ({
|
|||||||
onBoomLinesChange(updated)
|
onBoomLinesChange(updated)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
flex: 1, fontSize: '11px', fontWeight: 700,
|
||||||
background: 'transparent', border: 'none', color: 'var(--t1)', outline: 'none'
|
background: 'transparent', border: 'none', outline: 'none'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@ -343,14 +340,14 @@ const OilBoomSection = ({
|
|||||||
삭제
|
삭제
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '6px', fontSize: '9px', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '6px', fontSize: '9px' }}>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--t3)' }}>길이</span>
|
<span style={{ color: 'var(--t3)' }}>길이</span>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>{line.length.toFixed(0)}m</div>
|
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)' }}>{line.length.toFixed(0)}m</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--t3)' }}>각도</span>
|
<span style={{ color: 'var(--t3)' }}>각도</span>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>{line.angle.toFixed(0)}°</div>
|
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)' }}>{line.angle.toFixed(0)}°</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--t3)' }}>우선순위</span>
|
<span style={{ color: 'var(--t3)' }}>우선순위</span>
|
||||||
@ -362,9 +359,9 @@ const OilBoomSection = ({
|
|||||||
onBoomLinesChange(updated)
|
onBoomLinesChange(updated)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: '100%', fontSize: '10px', fontWeight: 600, fontFamily: 'var(--fK)',
|
width: '100%', fontSize: '10px', fontWeight: 600,
|
||||||
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: '3px',
|
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: '3px',
|
||||||
color: 'var(--t1)', padding: '2px', outline: 'none'
|
padding: '2px', outline: 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="CRITICAL">긴급</option>
|
<option value="CRITICAL">긴급</option>
|
||||||
@ -387,7 +384,7 @@ const OilBoomSection = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 10px',
|
display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 10px',
|
||||||
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
||||||
fontSize: '10px', fontFamily: 'var(--fK)'
|
fontSize: '10px'
|
||||||
}}>
|
}}>
|
||||||
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--red)' }} />
|
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--red)' }} />
|
||||||
<span style={{ color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
|
<span style={{ color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
|
||||||
@ -397,7 +394,7 @@ const OilBoomSection = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 10px',
|
display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 10px',
|
||||||
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
||||||
fontSize: '10px', fontFamily: 'var(--fK)'
|
fontSize: '10px'
|
||||||
}}>
|
}}>
|
||||||
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: boomLines.length > 0 ? 'var(--green)' : 'var(--red)' }} />
|
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: boomLines.length > 0 ? 'var(--green)' : 'var(--red)' }} />
|
||||||
<span style={{ color: boomLines.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
|
<span style={{ color: boomLines.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
|
||||||
@ -414,7 +411,7 @@ const OilBoomSection = ({
|
|||||||
}}
|
}}
|
||||||
disabled={oilTrajectory.length === 0 || boomLines.length === 0}
|
disabled={oilTrajectory.length === 0 || boomLines.length === 0}
|
||||||
style={{
|
style={{
|
||||||
width: '100%', padding: '10px', fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
width: '100%', padding: '10px', fontSize: '11px', fontWeight: 700,
|
||||||
background: (oilTrajectory.length > 0 && boomLines.length > 0) ? 'rgba(6,182,212,0.15)' : 'var(--bg0)',
|
background: (oilTrajectory.length > 0 && boomLines.length > 0) ? 'rgba(6,182,212,0.15)' : 'var(--bg0)',
|
||||||
border: (oilTrajectory.length > 0 && boomLines.length > 0) ? '2px solid var(--cyan)' : '1px solid var(--bd)',
|
border: (oilTrajectory.length > 0 && boomLines.length > 0) ? '2px solid var(--cyan)' : '1px solid var(--bd)',
|
||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
@ -437,7 +434,7 @@ const OilBoomSection = ({
|
|||||||
<div style={{ fontSize: '28px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '28px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>
|
||||||
{containmentResult.overallEfficiency}%
|
{containmentResult.overallEfficiency}%
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>
|
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>
|
||||||
전체 차단 효율
|
전체 차단 효율
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -448,13 +445,13 @@ const OilBoomSection = ({
|
|||||||
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--green)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--green)', fontFamily: 'var(--fM)' }}>
|
||||||
{containmentResult.blockedParticles}
|
{containmentResult.blockedParticles}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>차단 입자</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>차단 입자</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', textAlign: 'center' }}>
|
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>
|
||||||
{containmentResult.passedParticles}
|
{containmentResult.passedParticles}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>통과 입자</div>
|
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>통과 입자</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -468,7 +465,7 @@ const OilBoomSection = ({
|
|||||||
|
|
||||||
{/* 라인별 분석 */}
|
{/* 라인별 분석 */}
|
||||||
<div>
|
<div>
|
||||||
<h4 style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '6px' }}>
|
<h4 style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t3)', marginBottom: '6px' }}>
|
||||||
라인별 차단 분석
|
라인별 차단 분석
|
||||||
</h4>
|
</h4>
|
||||||
{containmentResult.perLineResults.map((r) => (
|
{containmentResult.perLineResults.map((r) => (
|
||||||
@ -476,7 +473,7 @@ const OilBoomSection = ({
|
|||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
padding: '6px 8px', marginBottom: '4px',
|
padding: '6px 8px', marginBottom: '4px',
|
||||||
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
|
||||||
fontSize: '9px', fontFamily: 'var(--fK)'
|
fontSize: '9px'
|
||||||
}}>
|
}}>
|
||||||
<span style={{ color: 'var(--t2)', flex: 1 }}>{r.boomLineName}</span>
|
<span style={{ color: 'var(--t2)', flex: 1 }}>{r.boomLineName}</span>
|
||||||
<span style={{ fontWeight: 700, color: r.efficiency >= 50 ? 'var(--green)' : 'var(--orange)', fontFamily: 'var(--fM)', marginLeft: '8px' }}>
|
<span style={{ fontWeight: 700, color: r.efficiency >= 50 ? 'var(--green)' : 'var(--orange)', fontFamily: 'var(--fM)', marginLeft: '8px' }}>
|
||||||
@ -502,11 +499,11 @@ const OilBoomSection = ({
|
|||||||
borderLeft: `3px solid ${priorityColor}`, borderRadius: 'var(--rS)'
|
borderLeft: `3px solid ${priorityColor}`, borderRadius: 'var(--rS)'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||||
<span style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '11px', fontWeight: 700 }}>
|
||||||
🛡 {idx + 1}차 방어선 ({line.type})
|
🛡 {idx + 1}차 방어선 ({line.type})
|
||||||
</span>
|
</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
padding: '2px 6px', fontSize: '8px', fontWeight: 700, fontFamily: 'var(--fK)',
|
padding: '2px 6px', fontSize: '8px', fontWeight: 700,
|
||||||
background: `${priorityColor}20`, border: `1px solid ${priorityColor}`,
|
background: `${priorityColor}20`, border: `1px solid ${priorityColor}`,
|
||||||
borderRadius: '3px', color: priorityColor
|
borderRadius: '3px', color: priorityColor
|
||||||
}}>
|
}}>
|
||||||
@ -515,21 +512,21 @@ const OilBoomSection = ({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>길이</span>
|
<span style={{ fontSize: '8px', color: 'var(--t3)' }}>길이</span>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fM)' }}>
|
||||||
{line.length.toFixed(0)}m
|
{line.length.toFixed(0)}m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)' }}>각도</span>
|
<span style={{ fontSize: '8px', color: 'var(--t3)' }}>각도</span>
|
||||||
<div style={{ fontSize: '14px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fM)' }}>
|
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fM)' }}>
|
||||||
{line.angle.toFixed(0)}°
|
{line.angle.toFixed(0)}°
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)' }} />
|
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)' }} />
|
||||||
<span style={{ fontSize: '9px', fontWeight: 600, color: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)', fontFamily: 'var(--fK)' }}>
|
<span style={{ fontSize: '9px', fontWeight: 600, color: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)' }}>
|
||||||
차단 효율 {line.efficiency}%
|
차단 효율 {line.efficiency}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
@ -603,12 +603,12 @@ export function OilSpillView() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px' }}>
|
||||||
{[
|
{[
|
||||||
{ label: '풍화율', value: `${Math.min(99, Math.round(timelinePosition * 0.4))}%`, color: 'var(--t1)' },
|
{ label: '풍화율', value: `${Math.min(99, Math.round(timelinePosition * 0.4))}%` },
|
||||||
{ label: '면적', value: `${(timelinePosition * 0.08).toFixed(1)} km²`, color: 'var(--t1)' },
|
{ label: '면적', value: `${(timelinePosition * 0.08).toFixed(1)} km²` },
|
||||||
{ label: '차단율', value: boomLines.length > 0 ? `${Math.min(95, 70 + Math.round(timelinePosition * 0.2))}%` : '—', color: 'var(--boom)' },
|
{ label: '차단율', value: boomLines.length > 0 ? `${Math.min(95, 70 + Math.round(timelinePosition * 0.2))}%` : '—', color: 'var(--boom)' },
|
||||||
].map((s, i) => (
|
].map((s, i) => (
|
||||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '5px', fontSize: '11px' }}>
|
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '5px', fontSize: '11px' }}>
|
||||||
<span style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{s.label}</span>
|
<span style={{ color: 'var(--t3)' }}>{s.label}</span>
|
||||||
<span style={{ color: s.color, fontWeight: 600, fontFamily: 'var(--fM)' }}>{s.value}</span>
|
<span style={{ color: s.color, fontWeight: 600, fontFamily: 'var(--fM)' }}>{s.value}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ const PredictionInputSection = ({
|
|||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="px-4 pb-4" style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
<div className="px-4 pb-4" style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||||
{/* Input Mode Selection */}
|
{/* Input Mode Selection */}
|
||||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center', fontSize: '11px', color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<div style={{ display: 'flex', gap: '10px', alignItems: 'center', fontSize: '11px' }}>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '3px', cursor: 'pointer' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: '3px', cursor: 'pointer' }}>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
@ -139,8 +139,7 @@ const PredictionInputSection = ({
|
|||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
color: '#22c55e',
|
color: '#22c55e',
|
||||||
fontFamily: 'var(--fK)',
|
fontWeight: 600
|
||||||
fontWeight: 600
|
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '12px' }}>✓</span>
|
<span style={{ fontSize: '12px' }}>✓</span>
|
||||||
내 이미지가 업로드됨
|
내 이미지가 업로드됨
|
||||||
@ -161,8 +160,7 @@ const PredictionInputSection = ({
|
|||||||
transition: '0.15s',
|
transition: '0.15s',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
color: 'var(--t3)',
|
color: 'var(--t3)',
|
||||||
fontFamily: 'var(--fK)'
|
}}
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.borderColor = 'var(--cyan)'
|
e.currentTarget.style.borderColor = 'var(--cyan)'
|
||||||
e.currentTarget.style.background = 'rgba(6,182,212,0.05)'
|
e.currentTarget.style.background = 'rgba(6,182,212,0.05)'
|
||||||
@ -357,8 +355,7 @@ const PredictionInputSection = ({
|
|||||||
borderRadius: 'var(--rS)',
|
borderRadius: 'var(--rS)',
|
||||||
fontSize: '9px',
|
fontSize: '9px',
|
||||||
color: 'var(--t3)',
|
color: 'var(--t3)',
|
||||||
fontFamily: 'var(--fK)',
|
lineHeight: '1.4'
|
||||||
lineHeight: '1.4'
|
|
||||||
}}>
|
}}>
|
||||||
📊 이미지 내 확산경로를 분석하였습니다. 각 방제요소 가이드 참고하세요.
|
📊 이미지 내 확산경로를 분석하였습니다. 각 방제요소 가이드 참고하세요.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -134,13 +134,13 @@ export function RecalcModal({
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h2 style={{
|
<h2 style={{
|
||||||
fontSize: '15px', fontWeight: 700, color: 'var(--t1)',
|
fontSize: '15px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', margin: 0,
|
margin: 0,
|
||||||
}}>
|
}}>
|
||||||
확산예측 재계산
|
확산예측 재계산
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px',
|
fontSize: '10px', color: 'var(--t3)', marginTop: '2px',
|
||||||
}}>
|
}}>
|
||||||
유출유·유출량 등 파라미터를 수정하여 재실행
|
유출유·유출량 등 파라미터를 수정하여 재실행
|
||||||
</div>
|
</div>
|
||||||
@ -168,7 +168,7 @@ export function RecalcModal({
|
|||||||
padding: '10px 12px', background: 'rgba(6,182,212,0.04)',
|
padding: '10px 12px', background: 'rgba(6,182,212,0.04)',
|
||||||
border: '1px solid rgba(6,182,212,0.15)', borderRadius: '8px',
|
border: '1px solid rgba(6,182,212,0.15)', borderRadius: '8px',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontSize: '9px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: '6px' }}>
|
<div style={{ fontSize: '9px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '6px' }}>
|
||||||
현재 분석 정보
|
현재 분석 정보
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px', fontSize: '9px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px', fontSize: '9px' }}>
|
||||||
@ -245,7 +245,7 @@ export function RecalcModal({
|
|||||||
<FieldGroup label="유출 위치 (좌표)">
|
<FieldGroup label="유출 위치 (좌표)">
|
||||||
<div style={{ display: 'flex', gap: '6px' }}>
|
<div style={{ display: 'flex', gap: '6px' }}>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '3px' }}>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}>
|
||||||
위도 (N)
|
위도 (N)
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@ -258,7 +258,7 @@ export function RecalcModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginBottom: '3px' }}>
|
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}>
|
||||||
경도 (E)
|
경도 (E)
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@ -315,7 +315,7 @@ export function RecalcModal({
|
|||||||
disabled={phase !== 'editing'}
|
disabled={phase !== 'editing'}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
|
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: 'pointer',
|
borderRadius: '8px', cursor: 'pointer',
|
||||||
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
background: 'var(--bg3)', border: '1px solid var(--bd)',
|
||||||
color: 'var(--t2)',
|
color: 'var(--t2)',
|
||||||
opacity: phase !== 'editing' ? 0.5 : 1,
|
opacity: phase !== 'editing' ? 0.5 : 1,
|
||||||
@ -328,7 +328,7 @@ export function RecalcModal({
|
|||||||
disabled={phase !== 'editing' || models.size === 0}
|
disabled={phase !== 'editing' || models.size === 0}
|
||||||
style={{
|
style={{
|
||||||
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
|
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
|
||||||
fontFamily: 'var(--fK)', borderRadius: '8px', cursor: phase === 'editing' ? 'pointer' : 'wait',
|
borderRadius: '8px', cursor: phase === 'editing' ? 'pointer' : 'wait',
|
||||||
background: phase === 'done'
|
background: phase === 'done'
|
||||||
? 'rgba(34,197,94,0.15)'
|
? 'rgba(34,197,94,0.15)'
|
||||||
: phase === 'running'
|
: phase === 'running'
|
||||||
@ -360,7 +360,7 @@ function FieldGroup({ label, children }: { label: string; children: React.ReactN
|
|||||||
<div>
|
<div>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '10px', fontWeight: 700, color: 'var(--t2)',
|
fontSize: '10px', fontWeight: 700, color: 'var(--t2)',
|
||||||
fontFamily: 'var(--fK)', marginBottom: '6px',
|
marginBottom: '6px',
|
||||||
}}>
|
}}>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
@ -372,8 +372,8 @@ function FieldGroup({ label, children }: { label: string; children: React.ReactN
|
|||||||
function InfoItem({ label, value }: { label: string; value: string }) {
|
function InfoItem({ label, value }: { label: string; value: string }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
|
||||||
<span style={{ color: 'var(--t3)', fontFamily: 'var(--fK)' }}>{label}</span>
|
<span style={{ color: 'var(--t3)' }}>{label}</span>
|
||||||
<span style={{ color: 'var(--t1)', fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</span>
|
<span style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,7 +146,7 @@ export function createSampleReport(): OilSpillReportData {
|
|||||||
|
|
||||||
// ─── Styles ─────────────────────────────────────────────────
|
// ─── Styles ─────────────────────────────────────────────────
|
||||||
const S = {
|
const S = {
|
||||||
page: { background: 'var(--bg1)', color: 'var(--t1)', padding: '32px 40px', marginBottom: '24px', borderRadius: '6px', border: '1px solid var(--bd)', fontFamily: "'Pretendard', 'Noto Sans KR', sans-serif", fontSize: '12px', lineHeight: '1.6', position: 'relative' as const, width: '100%', boxSizing: 'border-box' as const },
|
page: { background: 'var(--bg1)', padding: '32px 40px', marginBottom: '24px', borderRadius: '6px', border: '1px solid var(--bd)', fontFamily: "'Pretendard', 'Noto Sans KR', sans-serif", fontSize: '12px', lineHeight: '1.6', position: 'relative' as const, width: '100%', boxSizing: 'border-box' as const },
|
||||||
sectionTitle: { background: 'rgba(6,182,212,0.12)', color: 'var(--cyan)', padding: '8px 16px', fontSize: '13px', fontWeight: 700, marginBottom: '12px', borderRadius: '4px', border: '1px solid rgba(6,182,212,0.2)' },
|
sectionTitle: { background: 'rgba(6,182,212,0.12)', color: 'var(--cyan)', padding: '8px 16px', fontSize: '13px', fontWeight: 700, marginBottom: '12px', borderRadius: '4px', border: '1px solid rgba(6,182,212,0.2)' },
|
||||||
subHeader: { fontSize: '14px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '12px', borderBottom: '2px solid var(--bd)', paddingBottom: '6px' },
|
subHeader: { fontSize: '14px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '12px', borderBottom: '2px solid var(--bd)', paddingBottom: '6px' },
|
||||||
table: { width: '100%', tableLayout: 'fixed' as const, borderCollapse: 'collapse' as const, fontSize: '11px', marginBottom: '16px' },
|
table: { width: '100%', tableLayout: 'fixed' as const, borderCollapse: 'collapse' as const, fontSize: '11px', marginBottom: '16px' },
|
||||||
@ -160,7 +160,7 @@ const S = {
|
|||||||
// ─── Editable cell ──────────────────────────────────────────
|
// ─── Editable cell ──────────────────────────────────────────
|
||||||
const inputStyle: React.CSSProperties = {
|
const inputStyle: React.CSSProperties = {
|
||||||
width: '100%', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '3px',
|
width: '100%', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '3px',
|
||||||
padding: '4px 8px', fontSize: '11px', color: 'var(--t1)', outline: 'none', textAlign: 'center',
|
padding: '4px 8px', fontSize: '11px', outline: 'none', textAlign: 'center',
|
||||||
}
|
}
|
||||||
|
|
||||||
function ECell({ value, editing, onChange, align, placeholder }: {
|
function ECell({ value, editing, onChange, align, placeholder }: {
|
||||||
@ -302,7 +302,7 @@ function Page3({ data, editing, onChange }: { data: OilSpillReportData; editing:
|
|||||||
value={data.analysis}
|
value={data.analysis}
|
||||||
onChange={e => onChange({ ...data, analysis: e.target.value })}
|
onChange={e => onChange({ ...data, analysis: e.target.value })}
|
||||||
placeholder="분석 내용을 작성하세요..."
|
placeholder="분석 내용을 작성하세요..."
|
||||||
style={{ width: '100%', minHeight: '300px', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '16px', fontSize: '13px', color: 'var(--t1)', outline: 'none', resize: 'vertical', lineHeight: '1.8' }}
|
style={{ width: '100%', minHeight: '300px', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '16px', fontSize: '13px', outline: 'none', resize: 'vertical', lineHeight: '1.8' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ minHeight: '300px', border: data.analysis ? '1px solid var(--bd)' : '2px dashed var(--bd)', borderRadius: '4px', padding: '16px', color: data.analysis ? 'var(--t1)' : 'var(--t3)', fontStyle: data.analysis ? 'normal' : 'italic', fontSize: '13px', whiteSpace: 'pre-wrap', lineHeight: '1.8' }}>
|
<div style={{ minHeight: '300px', border: data.analysis ? '1px solid var(--bd)' : '2px dashed var(--bd)', borderRadius: '4px', padding: '16px', color: data.analysis ? 'var(--t1)' : 'var(--t3)', fontStyle: data.analysis ? 'normal' : 'italic', fontSize: '13px', whiteSpace: 'pre-wrap', lineHeight: '1.8' }}>
|
||||||
@ -459,7 +459,7 @@ function Page6({ data, editing, onChange }: { data: OilSpillReportData; editing:
|
|||||||
{editing && <AddRowBtn onClick={() => onChange({ ...data, vessels: [...data.vessels, { name: '', org: '', dist: '', speed: '', ton: '', collectorType: '', collectorCap: '', boomType: '', boomLength: '' }] })} />}
|
{editing && <AddRowBtn onClick={() => onChange({ ...data, vessels: [...data.vessels, { name: '', org: '', dist: '', speed: '', ton: '', collectorType: '', collectorCap: '', boomType: '', boomLength: '' }] })} />}
|
||||||
<div style={S.subHeader}>기타 장비</div>
|
<div style={S.subHeader}>기타 장비</div>
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<textarea value={data.etcEquipment} onChange={e => onChange({ ...data, etcEquipment: e.target.value })} placeholder="기타 장비 입력" style={{ width: '100%', minHeight: '60px', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '12px', fontSize: '12px', color: 'var(--t1)', outline: 'none', resize: 'vertical' }} />
|
<textarea value={data.etcEquipment} onChange={e => onChange({ ...data, etcEquipment: e.target.value })} placeholder="기타 장비 입력" style={{ width: '100%', minHeight: '60px', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '12px', fontSize: '12px', outline: 'none', resize: 'vertical' }} />
|
||||||
) : (
|
) : (
|
||||||
<div style={{ minHeight: '60px', border: data.etcEquipment ? '1px solid var(--bd)' : '2px dashed var(--bd)', borderRadius: '4px', padding: '12px', color: data.etcEquipment ? 'var(--t1)' : 'var(--t3)', fontStyle: data.etcEquipment ? 'normal' : 'italic', fontSize: '12px' }}>{data.etcEquipment || '-'}</div>
|
<div style={{ minHeight: '60px', border: data.etcEquipment ? '1px solid var(--bd)' : '2px dashed var(--bd)', borderRadius: '4px', padding: '12px', color: data.etcEquipment ? 'var(--t1)' : 'var(--t3)', fontStyle: data.etcEquipment ? 'normal' : 'italic', fontSize: '12px' }}>{data.etcEquipment || '-'}</div>
|
||||||
)}
|
)}
|
||||||
@ -551,13 +551,13 @@ export function OilSpillReportTemplate({ mode, initialData, onSave, onBack }: Pr
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px', flexWrap: 'wrap', gap: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px', flexWrap: 'wrap', gap: '12px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
{onBack && <button onClick={onBack} style={{ padding: '6px 12px', fontSize: '12px', fontWeight: 600, color: 'var(--t2)', background: 'none', border: 'none', cursor: 'pointer' }}>← 돌아가기</button>}
|
{onBack && <button onClick={onBack} style={{ padding: '6px 12px', fontSize: '12px', fontWeight: 600, color: 'var(--t2)', background: 'none', border: 'none', cursor: 'pointer' }}>← 돌아가기</button>}
|
||||||
<h2 style={{ fontSize: '18px', fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>
|
<h2 style={{ fontSize: '18px', fontWeight: 700 }}>
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<input
|
<input
|
||||||
value={data.title}
|
value={data.title}
|
||||||
onChange={e => setData({ ...data, title: e.target.value })}
|
onChange={e => setData({ ...data, title: e.target.value })}
|
||||||
placeholder="보고서 제목 입력"
|
placeholder="보고서 제목 입력"
|
||||||
style={{ fontSize: '18px', fontWeight: 700, color: 'var(--t1)', background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '4px 10px', outline: 'none', width: '100%', maxWidth: '600px' }}
|
style={{ fontSize: '18px', fontWeight: 700, background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '4px 10px', outline: 'none', width: '100%', maxWidth: '600px' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
data.title || '유류오염사고 대응지원 상황도'
|
data.title || '유류오염사고 대응지원 상황도'
|
||||||
@ -568,12 +568,12 @@ export function OilSpillReportTemplate({ mode, initialData, onSave, onBack }: Pr
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
<button onClick={() => setViewMode('all')} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, fontFamily: 'var(--fK)', borderRadius: '4px', border: viewMode === 'all' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'all' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'all' ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>전체 보기</button>
|
<button onClick={() => setViewMode('all')} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: viewMode === 'all' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'all' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'all' ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>전체 보기</button>
|
||||||
<button onClick={() => setViewMode('page')} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, fontFamily: 'var(--fK)', borderRadius: '4px', border: viewMode === 'page' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'page' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'page' ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>페이지별</button>
|
<button onClick={() => setViewMode('page')} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: viewMode === 'page' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'page' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'page' ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>페이지별</button>
|
||||||
{editing && (
|
{editing && (
|
||||||
<button onClick={handleSave} style={{ padding: '6px 16px', fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)', borderRadius: '4px', border: '1px solid #22c55e', background: 'rgba(34,197,94,0.15)', color: '#22c55e', cursor: 'pointer' }}>저장</button>
|
<button onClick={handleSave} style={{ padding: '6px 16px', fontSize: '11px', fontWeight: 700, borderRadius: '4px', border: '1px solid #22c55e', background: 'rgba(34,197,94,0.15)', color: '#22c55e', cursor: 'pointer' }}>저장</button>
|
||||||
)}
|
)}
|
||||||
<button onClick={() => window.print()} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, fontFamily: 'var(--fK)', borderRadius: '4px', border: '1px solid var(--red)', background: 'rgba(239,68,68,0.1)', color: '#ef4444', cursor: 'pointer' }}>인쇄 / PDF</button>
|
<button onClick={() => window.print()} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: '1px solid var(--red)', background: 'rgba(239,68,68,0.1)', color: '#ef4444', cursor: 'pointer' }}>인쇄 / PDF</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -581,7 +581,7 @@ export function OilSpillReportTemplate({ mode, initialData, onSave, onBack }: Pr
|
|||||||
{viewMode === 'page' && (
|
{viewMode === 'page' && (
|
||||||
<div style={{ display: 'flex', gap: '4px', marginBottom: '16px', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: '4px', marginBottom: '16px', flexWrap: 'wrap' }}>
|
||||||
{pages.map((p, i) => (
|
{pages.map((p, i) => (
|
||||||
<button key={i} onClick={() => setCurrentPage(i)} style={{ padding: '6px 12px', fontSize: '11px', fontWeight: 600, fontFamily: 'var(--fK)', borderRadius: '4px', border: currentPage === i ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: currentPage === i ? 'rgba(6,182,212,0.15)' : 'transparent', color: currentPage === i ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>{p.label}</button>
|
<button key={i} onClick={() => setCurrentPage(i)} style={{ padding: '6px 12px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: currentPage === i ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: currentPage === i ? 'rgba(6,182,212,0.15)' : 'transparent', color: currentPage === i ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>{p.label}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -103,12 +103,12 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
|
|||||||
>
|
>
|
||||||
<div style={{ fontSize: '22px', marginBottom: '4px' }}>{c.icon}</div>
|
<div style={{ fontSize: '22px', marginBottom: '4px' }}>{c.icon}</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '12px', fontWeight: 700, fontFamily: 'var(--fK)',
|
fontSize: '12px', fontWeight: 700,
|
||||||
color: isActive ? c.color : 'var(--t3)',
|
color: isActive ? c.color : 'var(--t3)',
|
||||||
}}>
|
}}>
|
||||||
{c.label}
|
{c.label}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>
|
<div style={{ fontSize: '9px', color: 'var(--t3)', marginTop: '2px' }}>
|
||||||
{c.desc}
|
{c.desc}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@ -137,7 +137,7 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
|
|||||||
>
|
>
|
||||||
<span style={{ fontSize: '14px' }}>{tmpl.icon}</span>
|
<span style={{ fontSize: '14px' }}>{tmpl.icon}</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '11px', fontWeight: 600, fontFamily: 'var(--fK)',
|
fontSize: '11px', fontWeight: 600,
|
||||||
color: selectedTemplate === i ? cat.color : 'var(--t2)',
|
color: selectedTemplate === i ? cat.color : 'var(--t2)',
|
||||||
}}>
|
}}>
|
||||||
{tmpl.label}
|
{tmpl.label}
|
||||||
@ -175,13 +175,13 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
<span style={{ fontSize: '12px' }}>{sec.icon}</span>
|
<span style={{ fontSize: '12px' }}>{sec.icon}</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '11px', fontWeight: 700, fontFamily: 'var(--fK)',
|
fontSize: '11px', fontWeight: 700,
|
||||||
color: sec.checked ? 'var(--t1)' : 'var(--t3)',
|
color: sec.checked ? 'var(--t1)' : 'var(--t3)',
|
||||||
}}>
|
}}>
|
||||||
{sec.title}
|
{sec.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: '2px' }}>
|
<p style={{ fontSize: '9px', color: 'var(--t3)', marginTop: '2px' }}>
|
||||||
{sec.desc}
|
{sec.desc}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -198,7 +198,7 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
|
|||||||
📄 보고서 미리보기
|
📄 보고서 미리보기
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: '10px', fontWeight: 600, padding: '2px 8px', borderRadius: '4px',
|
fontSize: '10px', fontWeight: 600, padding: '2px 8px', borderRadius: '4px',
|
||||||
background: cat.bgActive, color: cat.color, fontFamily: 'var(--fK)',
|
background: cat.bgActive, color: cat.color,
|
||||||
}}>
|
}}>
|
||||||
{cat.templates[selectedTemplate].label}
|
{cat.templates[selectedTemplate].label}
|
||||||
</span>
|
</span>
|
||||||
@ -445,7 +445,7 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
|
|||||||
{sec.id === 'rescue-casualty' && (
|
{sec.id === 'rescue-casualty' && (
|
||||||
<div className="grid grid-cols-4 gap-3">
|
<div className="grid grid-cols-4 gap-3">
|
||||||
{[
|
{[
|
||||||
{ label: '총원', value: sampleRescueData.casualty.total, color: 'var(--t1)' },
|
{ label: '총원', value: sampleRescueData.casualty.total },
|
||||||
{ label: '구조완료', value: sampleRescueData.casualty.rescued, color: '#22c55e' },
|
{ label: '구조완료', value: sampleRescueData.casualty.rescued, color: '#22c55e' },
|
||||||
{ label: '실종', value: sampleRescueData.casualty.missing, color: '#ef4444' },
|
{ label: '실종', value: sampleRescueData.casualty.missing, color: '#ef4444' },
|
||||||
{ label: '부상', value: sampleRescueData.casualty.injured, color: '#f97316' },
|
{ label: '부상', value: sampleRescueData.casualty.injured, color: '#f97316' },
|
||||||
|
|||||||
@ -225,7 +225,7 @@ export function ReportsView() {
|
|||||||
<div className="text-center mb-2.5" style={{ fontSize: '28px' }}>
|
<div className="text-center mb-2.5" style={{ fontSize: '28px' }}>
|
||||||
{({ '초기보고서': '📋', '지휘부 보고': '📊', '예측보고서': '🔬', '종합보고서': '📑', '유출유 보고': '🛢️' } as Record<string, string>)[previewReport.reportType] || '📄'}
|
{({ '초기보고서': '📋', '지휘부 보고': '📊', '예측보고서': '🔬', '종합보고서': '📑', '유출유 보고': '🛢️' } as Record<string, string>)[previewReport.reportType] || '📄'}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center font-korean" style={{ fontSize: '13px', fontWeight: 700, color: 'var(--t1)', lineHeight: 1.4, wordBreak: 'keep-all' }}>
|
<div className="text-center font-korean" style={{ fontSize: '13px', fontWeight: 700, lineHeight: 1.4, wordBreak: 'keep-all' }}>
|
||||||
{previewReport.title || '제목 없음'}
|
{previewReport.title || '제목 없음'}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center mt-2">
|
<div className="text-center mt-2">
|
||||||
@ -239,15 +239,15 @@ export function ReportsView() {
|
|||||||
<div className="flex flex-col gap-2.5 font-korean" style={{ padding: '14px 18px', fontSize: '11px', borderBottom: '1px solid var(--bd)' }}>
|
<div className="flex flex-col gap-2.5 font-korean" style={{ padding: '14px 18px', fontSize: '11px', borderBottom: '1px solid var(--bd)' }}>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>작성자</span>
|
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>작성자</span>
|
||||||
<span style={{ color: 'var(--t1)', fontWeight: 600 }}>{previewReport.author || '—'}</span>
|
<span style={{ fontWeight: 600 }}>{previewReport.author || '—'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>관할</span>
|
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>관할</span>
|
||||||
<span style={{ color: 'var(--t1)', fontWeight: 600 }}>{previewReport.jurisdiction}</span>
|
<span style={{ fontWeight: 600 }}>{previewReport.jurisdiction}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>생성일시</span>
|
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>생성일시</span>
|
||||||
<span className="font-mono" style={{ color: 'var(--t1)', fontWeight: 600 }}>{formatDate(previewReport.createdAt)}</span>
|
<span className="font-mono" style={{ fontWeight: 600 }}>{formatDate(previewReport.createdAt)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>상태</span>
|
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>상태</span>
|
||||||
@ -346,7 +346,7 @@ export function ReportsView() {
|
|||||||
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
||||||
1. 사고개요
|
1. 사고개요
|
||||||
</div>
|
</div>
|
||||||
<div className="font-korean" style={{ fontSize: '12px', color: 'var(--t1)', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
||||||
{[
|
{[
|
||||||
previewReport.incident.name && `사고명: ${previewReport.incident.name}`,
|
previewReport.incident.name && `사고명: ${previewReport.incident.name}`,
|
||||||
previewReport.incident.occurTime && `발생일시: ${previewReport.incident.occurTime}`,
|
previewReport.incident.occurTime && `발생일시: ${previewReport.incident.occurTime}`,
|
||||||
@ -362,7 +362,7 @@ export function ReportsView() {
|
|||||||
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
||||||
2. 유출현황
|
2. 유출현황
|
||||||
</div>
|
</div>
|
||||||
<div className="font-korean" style={{ fontSize: '12px', color: 'var(--t1)', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
||||||
{[
|
{[
|
||||||
previewReport.incident.pollutant && `유출유종: ${previewReport.incident.pollutant}`,
|
previewReport.incident.pollutant && `유출유종: ${previewReport.incident.pollutant}`,
|
||||||
previewReport.incident.spillAmount && `유출량: ${previewReport.incident.spillAmount}`,
|
previewReport.incident.spillAmount && `유출량: ${previewReport.incident.spillAmount}`,
|
||||||
@ -376,7 +376,7 @@ export function ReportsView() {
|
|||||||
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
||||||
3. 초동조치 / 대응현황
|
3. 초동조치 / 대응현황
|
||||||
</div>
|
</div>
|
||||||
<div className="font-korean" style={{ fontSize: '12px', color: 'var(--t1)', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
||||||
{previewReport.analysis || '—'}
|
{previewReport.analysis || '—'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -386,7 +386,7 @@ export function ReportsView() {
|
|||||||
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
|
||||||
4. 향후 계획
|
4. 향후 계획
|
||||||
</div>
|
</div>
|
||||||
<div className="font-korean" style={{ fontSize: '12px', color: 'var(--t1)', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
|
||||||
{previewReport.etcEquipment || '—'}
|
{previewReport.etcEquipment || '—'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -175,15 +175,15 @@ export function RescueScenarioView() {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.2),rgba(59,130,246,.15))', border: '1px solid rgba(6,182,212,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>📊</div>
|
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.2),rgba(59,130,246,.15))', border: '1px solid rgba(6,182,212,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>📊</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 15, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>긴급구난 시나리오 관리</div>
|
<div style={{ fontSize: 15, fontWeight: 700 }}>긴급구난 시나리오 관리</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 2 }}>시간 단계별 시나리오 비교·검토 및 구난 의사결정 지원 (SFR-009)</div>
|
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}>시간 단계별 시나리오 비교·검토 및 구난 의사결정 지원 (SFR-009)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||||
<select value={selectedIncident} onChange={e => setSelectedIncident(Number(e.target.value))} style={{ padding: '6px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', outline: 'none' }}>
|
<select value={selectedIncident} onChange={e => setSelectedIncident(Number(e.target.value))} style={{ padding: '6px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', fontSize: 10, outline: 'none' }}>
|
||||||
{ops.map((op, i) => <option key={op.rescueOpsSn} value={i}>{op.opsCd} · {op.vesselNm}</option>)}
|
{ops.map((op, i) => <option key={op.rescueOpsSn} value={i}>{op.opsCd} · {op.vesselNm}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<button onClick={() => setNewScnModalOpen(true)} style={{ padding: '6px 14px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--cyan),#3b82f6)', color: '#fff', fontSize: 10, fontWeight: 700, cursor: 'pointer', fontFamily: 'var(--fK)' }}>+ 신규 시나리오</button>
|
<button onClick={() => setNewScnModalOpen(true)} style={{ padding: '6px 14px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--cyan),#3b82f6)', color: '#fff', fontSize: 10, fontWeight: 700, cursor: 'pointer' }}>+ 신규 시나리오</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -194,10 +194,10 @@ export function RescueScenarioView() {
|
|||||||
<div style={{ width: 360, minWidth: 360, background: 'var(--bg1)', borderRight: '1px solid var(--bd)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
<div style={{ width: 360, minWidth: 360, background: 'var(--bg1)', borderRight: '1px solid var(--bd)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||||
{/* Sort bar */}
|
{/* Sort bar */}
|
||||||
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>📋 시나리오 목록 <span style={{ fontWeight: 400, color: 'var(--t3)', fontSize: 9 }}>({scenarios.length}개)</span></div>
|
<div style={{ fontSize: 11, fontWeight: 700 }}>📋 시나리오 목록 <span style={{ fontWeight: 400, color: 'var(--t3)', fontSize: 9 }}>({scenarios.length}개)</span></div>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
{(['time', 'risk'] as const).map(s => (
|
{(['time', 'risk'] as const).map(s => (
|
||||||
<button key={s} onClick={() => setSortBy(s)} style={{ padding: '3px 8px', borderRadius: 4, border: `1px solid ${sortBy === s ? 'rgba(6,182,212,.4)' : 'var(--bd)'}`, background: sortBy === s ? 'rgba(6,182,212,.08)' : 'var(--bg3)', color: sortBy === s ? 'var(--cyan)' : 'var(--t3)', fontSize: 9, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>{s === 'time' ? '시간순' : '위험도순'}</button>
|
<button key={s} onClick={() => setSortBy(s)} style={{ padding: '3px 8px', borderRadius: 4, border: `1px solid ${sortBy === s ? 'rgba(6,182,212,.4)' : 'var(--bd)'}`, background: sortBy === s ? 'rgba(6,182,212,.08)' : 'var(--bg3)', color: sortBy === s ? 'var(--cyan)' : 'var(--t3)', fontSize: 9, fontWeight: 600, cursor: 'pointer' }}>{s === 'time' ? '시간순' : '위험도순'}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -205,7 +205,7 @@ export function RescueScenarioView() {
|
|||||||
{/* Card list */}
|
{/* Card list */}
|
||||||
<div style={{ flex: 1, overflowY: 'auto', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
<div style={{ flex: 1, overflowY: 'auto', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
|
||||||
{loading && scenarios.length === 0 && (
|
{loading && scenarios.length === 0 && (
|
||||||
<div style={{ textAlign: 'center', padding: '40px 0', fontSize: 11, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>시나리오 로딩 중...</div>
|
<div style={{ textAlign: 'center', padding: '40px 0', fontSize: 11, color: 'var(--t3)' }}>시나리오 로딩 중...</div>
|
||||||
)}
|
)}
|
||||||
{sorted.map(sc => {
|
{sorted.map(sc => {
|
||||||
const isSel = selectedId === sc.id
|
const isSel = selectedId === sc.id
|
||||||
@ -221,10 +221,10 @@ export function RescueScenarioView() {
|
|||||||
<span style={{ marginLeft: 'auto', fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{sc.timeStep}</span>
|
<span style={{ marginLeft: 'auto', fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{sc.timeStep}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Name + time */}
|
{/* Name + time */}
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: 4 }}>{sc.name}</div>
|
<div style={{ fontSize: 11, fontWeight: 700, marginBottom: 4 }}>{sc.name}</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)', marginBottom: 8 }}>{sc.datetime}</div>
|
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)', marginBottom: 8 }}>{sc.datetime}</div>
|
||||||
{/* KPI grid */}
|
{/* KPI grid */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 4, marginBottom: 8, fontSize: 8, fontFamily: 'var(--fK)', textAlign: 'center' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 4, marginBottom: 8, fontSize: 8, textAlign: 'center' }}>
|
||||||
<div style={{ padding: '4px 2px', background: 'var(--bg0)', borderRadius: 4 }}>
|
<div style={{ padding: '4px 2px', background: 'var(--bg0)', borderRadius: 4 }}>
|
||||||
<div style={{ color: 'var(--t3)' }}>GM</div>
|
<div style={{ color: 'var(--t3)' }}>GM</div>
|
||||||
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: gmColor(parseFloat(sc.gm)) }}>{sc.gm}m</div>
|
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: gmColor(parseFloat(sc.gm)) }}>{sc.gm}m</div>
|
||||||
@ -243,7 +243,7 @@ export function RescueScenarioView() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>{sc.description}</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}>{sc.description}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -251,8 +251,8 @@ export function RescueScenarioView() {
|
|||||||
|
|
||||||
{/* Bottom actions */}
|
{/* Bottom actions */}
|
||||||
<div style={{ padding: '10px 14px', borderTop: '1px solid var(--bd)', display: 'flex', gap: 6 }}>
|
<div style={{ padding: '10px 14px', borderTop: '1px solid var(--bd)', display: 'flex', gap: 6 }}>
|
||||||
<button onClick={() => setDetailView(1)} style={{ flex: 1, padding: '8px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--cyan),#3b82f6)', color: '#fff', fontSize: 10, fontWeight: 700, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📊 선택 시나리오 비교</button>
|
<button onClick={() => setDetailView(1)} style={{ flex: 1, padding: '8px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--cyan),#3b82f6)', color: '#fff', fontSize: 10, fontWeight: 700, cursor: 'pointer' }}>📊 선택 시나리오 비교</button>
|
||||||
<button style={{ padding: '8px 14px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)', fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>📄 보고서</button>
|
<button style={{ padding: '8px 14px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}>📄 보고서</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -261,7 +261,7 @@ export function RescueScenarioView() {
|
|||||||
{/* Detail tabs */}
|
{/* Detail tabs */}
|
||||||
<div style={{ display: 'flex', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
|
<div style={{ display: 'flex', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
|
||||||
{(['📋 시나리오 상세', '📊 비교 차트', '🗺 지도 오버레이'] as const).map((label, i) => (
|
{(['📋 시나리오 상세', '📊 비교 차트', '🗺 지도 오버레이'] as const).map((label, i) => (
|
||||||
<button key={i} onClick={() => setDetailView(i as DetailView)} className="rsc-atab" style={{ flex: 1, padding: '9px 4px', border: 'none', background: detailView === i ? 'rgba(6,182,212,.04)' : 'transparent', color: detailView === i ? 'var(--cyan)' : 'var(--t3)', fontFamily: 'var(--fK)', fontSize: 10, fontWeight: detailView === i ? 700 : 600, cursor: 'pointer', borderBottom: `2px solid ${detailView === i ? 'var(--cyan)' : 'transparent'}`, transition: '.2s' }}>{label}</button>
|
<button key={i} onClick={() => setDetailView(i as DetailView)} className="rsc-atab" style={{ flex: 1, padding: '9px 4px', border: 'none', background: detailView === i ? 'rgba(6,182,212,.04)' : 'transparent', color: detailView === i ? 'var(--cyan)' : 'var(--t3)', fontSize: 10, fontWeight: detailView === i ? 700 : 600, cursor: 'pointer', borderBottom: `2px solid ${detailView === i ? 'var(--cyan)' : 'transparent'}`, transition: '.2s' }}>{label}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -275,12 +275,12 @@ export function RescueScenarioView() {
|
|||||||
<div style={{ padding: '16px 20px', borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.06),rgba(239,68,68,.04))', border: '1px solid rgba(6,182,212,.2)', marginBottom: 16 }}>
|
<div style={{ padding: '16px 20px', borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.06),rgba(239,68,68,.04))', border: '1px solid rgba(6,182,212,.2)', marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
|
||||||
<span style={{ fontSize: 16, fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>{selected.id}</span>
|
<span style={{ fontSize: 16, fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>{selected.id}</span>
|
||||||
<span style={{ fontSize: 14, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>{selected.name}</span>
|
<span style={{ fontSize: 14, fontWeight: 700 }}>{selected.name}</span>
|
||||||
<span style={{ padding: '2px 8px', borderRadius: 4, background: SEV_STYLE[selected.severity].bg, color: SEV_STYLE[selected.severity].color, fontSize: 9, fontWeight: 700, fontFamily: 'var(--fM)' }}>{selected.severity}</span>
|
<span style={{ padding: '2px 8px', borderRadius: 4, background: SEV_STYLE[selected.severity].bg, color: SEV_STYLE[selected.severity].color, fontSize: 9, fontWeight: 700, fontFamily: 'var(--fM)' }}>{selected.severity}</span>
|
||||||
<span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{selected.datetime}</span>
|
<span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{selected.datetime}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* 6 KPI cards */}
|
{/* 6 KPI cards */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6,1fr)', gap: 8, fontSize: 8, fontFamily: 'var(--fK)', textAlign: 'center' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6,1fr)', gap: 8, fontSize: 8, textAlign: 'center' }}>
|
||||||
{[
|
{[
|
||||||
{ label: 'GM (복원심)', value: `${selected.gm}m`, color: gmColor(parseFloat(selected.gm)) },
|
{ label: 'GM (복원심)', value: `${selected.gm}m`, color: gmColor(parseFloat(selected.gm)) },
|
||||||
{ label: '횡경사 (List)', value: `${selected.list}°`, color: listColor(parseFloat(selected.list)) },
|
{ label: '횡경사 (List)', value: `${selected.list}°`, color: listColor(parseFloat(selected.list)) },
|
||||||
@ -301,11 +301,11 @@ export function RescueScenarioView() {
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
|
||||||
{/* 침수 구획 */}
|
{/* 침수 구획 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: 10 }}>🚢 침수 구획 상태</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--cyan)', marginBottom: 10 }}>🚢 침수 구획 상태</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
{selected.compartments.map((c, i) => (
|
{selected.compartments.map((c, i) => (
|
||||||
<div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 10px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${c.color}` }}>
|
<div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 10px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${c.color}` }}>
|
||||||
<span style={{ fontSize: 9, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>{c.name}</span>
|
<span style={{ fontSize: 9 }}>{c.name}</span>
|
||||||
<span style={{ fontSize: 9, fontFamily: 'var(--fM)', color: c.color, fontWeight: 600 }}>{c.status}</span>
|
<span style={{ fontSize: 9, fontFamily: 'var(--fM)', color: c.color, fontWeight: 600 }}>{c.status}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -313,12 +313,12 @@ export function RescueScenarioView() {
|
|||||||
</div>
|
</div>
|
||||||
{/* 구난 판단 */}
|
{/* 구난 판단 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)', marginBottom: 10 }}>⚠️ 구난 판단 요약</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', marginBottom: 10 }}>⚠️ 구난 판단 요약</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
{selected.assessment.map((a, i) => (
|
{selected.assessment.map((a, i) => (
|
||||||
<div key={i} style={{ padding: '8px 10px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${a.color}` }}>
|
<div key={i} style={{ padding: '8px 10px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${a.color}` }}>
|
||||||
<div style={{ fontSize: 9, fontWeight: 700, color: a.color, fontFamily: 'var(--fK)' }}>{a.label}</div>
|
<div style={{ fontSize: 9, fontWeight: 700, color: a.color }}>{a.label}</div>
|
||||||
<div style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', marginTop: 2 }}>{a.value}</div>
|
<div style={{ fontSize: 9, color: 'var(--t2)', marginTop: 2 }}>{a.value}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -327,13 +327,13 @@ export function RescueScenarioView() {
|
|||||||
|
|
||||||
{/* 대응 조치 이력 */}
|
{/* 대응 조치 이력 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: 10 }}>📋 대응 조치 이력</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', marginBottom: 10 }}>📋 대응 조치 이력</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
{selected.actions.map((a, i) => (
|
{selected.actions.map((a, i) => (
|
||||||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px', background: 'var(--bg0)', borderRadius: 4 }}>
|
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px', background: 'var(--bg0)', borderRadius: 4 }}>
|
||||||
<span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--fM)', color: a.color, minWidth: 40 }}>{a.time}</span>
|
<span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--fM)', color: a.color, minWidth: 40 }}>{a.time}</span>
|
||||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: a.color, flexShrink: 0 }} />
|
<div style={{ width: 6, height: 6, borderRadius: '50%', background: a.color, flexShrink: 0 }} />
|
||||||
<span style={{ fontSize: 9, fontFamily: 'var(--fK)', color: 'var(--t1)' }}>{a.text}</span>
|
<span style={{ fontSize: 9 }}>{a.text}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -349,18 +349,18 @@ export function RescueScenarioView() {
|
|||||||
<div style={{ padding: 20 }}>
|
<div style={{ padding: 20 }}>
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 20, textAlign: 'center' }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 20, textAlign: 'center' }}>
|
||||||
<div style={{ fontSize: 32, opacity: 0.3, marginBottom: 10 }}>🗺</div>
|
<div style={{ fontSize: 32, opacity: 0.3, marginBottom: 10 }}>🗺</div>
|
||||||
<div style={{ fontSize: 13, fontWeight: 700, fontFamily: 'var(--fK)', color: 'var(--t1)', marginBottom: 6 }}>GIS 기반 시나리오 비교</div>
|
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 6 }}>GIS 기반 시나리오 비교</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.6, marginBottom: 16 }}>선택된 시나리오의 침수 구역을 지도 위에 오버레이하여 비교합니다.</div>
|
<div style={{ fontSize: 10, color: 'var(--t3)', lineHeight: 1.6, marginBottom: 16 }}>선택된 시나리오의 침수 구역을 지도 위에 오버레이하여 비교합니다.</div>
|
||||||
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
|
||||||
{scenarios.map(sc => (
|
{scenarios.map(sc => (
|
||||||
<div key={sc.id} style={{ padding: '6px 12px', borderRadius: 6, border: `1px solid ${SEV_STYLE[sc.severity].color}40`, background: SEV_STYLE[sc.severity].bg, fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<div key={sc.id} style={{ padding: '6px 12px', borderRadius: 6, border: `1px solid ${SEV_STYLE[sc.severity].color}40`, background: SEV_STYLE[sc.severity].bg, fontSize: 9 }}>
|
||||||
<span style={{ fontWeight: 700, color: SEV_STYLE[sc.severity].color }}>{sc.id}</span>
|
<span style={{ fontWeight: 700, color: SEV_STYLE[sc.severity].color }}>{sc.id}</span>
|
||||||
<span style={{ color: 'var(--t2)', marginLeft: 6 }}>{sc.name}</span>
|
<span style={{ color: 'var(--t2)', marginLeft: 6 }}>{sc.name}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 16, padding: 30, background: 'var(--bg0)', borderRadius: 8, border: '1px dashed var(--bd)' }}>
|
<div style={{ marginTop: 16, padding: 30, background: 'var(--bg0)', borderRadius: 8, border: '1px dashed var(--bd)' }}>
|
||||||
<div style={{ fontSize: 11, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>지도 뷰 영역 — 구난 분석 지도와 연동하여 침수 구역 오버레이 표시</div>
|
<div style={{ fontSize: 11, color: 'var(--t3)' }}>지도 뷰 영역 — 구난 분석 지도와 연동하여 침수 구역 오버레이 표시</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -387,14 +387,14 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── shared styles ── */
|
/* ── shared styles ── */
|
||||||
const labelSt: React.CSSProperties = { fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: 4 }
|
const labelSt: React.CSSProperties = { fontSize: 9, color: 'var(--t3)', display: 'block', marginBottom: 4 }
|
||||||
const inputSt: React.CSSProperties = { width: '100%', padding: '8px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 11, fontFamily: 'var(--fK)', outline: 'none', boxSizing: 'border-box' as const }
|
const inputSt: React.CSSProperties = { width: '100%', padding: '8px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 11, outline: 'none', boxSizing: 'border-box' as const }
|
||||||
const numSt: React.CSSProperties = { ...inputSt, fontFamily: 'var(--fM)' }
|
const numSt: React.CSSProperties = { ...inputSt, fontFamily: 'var(--fM)' }
|
||||||
const selSt = inputSt
|
const selSt = inputSt
|
||||||
const sectionIcon = (n: number) => (
|
const sectionIcon = (n: number) => (
|
||||||
<div style={{ width: 18, height: 18, borderRadius: 5, background: 'rgba(6,182,212,.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 9, fontWeight: 700, color: 'var(--cyan)' }}>{n}</div>
|
<div style={{ width: 18, height: 18, borderRadius: 5, background: 'rgba(6,182,212,.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 9, fontWeight: 700, color: 'var(--cyan)' }}>{n}</div>
|
||||||
)
|
)
|
||||||
const sectionTitle: React.CSSProperties = { fontSize: 11, fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }
|
const sectionTitle: React.CSSProperties = { fontSize: 11, fontWeight: 700, color: 'var(--cyan)', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={overlayRef} onClick={e => { if (e.target === overlayRef.current) onClose() }}
|
<div ref={overlayRef} onClick={e => { if (e.target === overlayRef.current) onClose() }}
|
||||||
@ -408,8 +408,8 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
<div style={{ width: 36, height: 36, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(59,130,246,.08))', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>🚨</div>
|
<div style={{ width: 36, height: 36, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(59,130,246,.08))', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>🚨</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)' }}>신규 긴급구난 시나리오 생성</div>
|
<div style={{ fontSize: 15, fontWeight: 700 }}>신규 긴급구난 시나리오 생성</div>
|
||||||
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fK)', marginTop: 2 }}>선박 사고 조건 및 구난 분석 파라미터를 설정합니다 (SFR-009)</div>
|
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}>선박 사고 조건 및 구난 분석 파라미터를 설정합니다 (SFR-009)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span onClick={onClose} style={{ fontSize: 18, cursor: 'pointer', color: 'var(--t3)', padding: 4 }}>✕</span>
|
<span onClick={onClose} style={{ fontSize: 18, cursor: 'pointer', color: 'var(--t3)', padding: 4 }}>✕</span>
|
||||||
@ -531,25 +531,25 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
</div>
|
</div>
|
||||||
{/* 침수 상태 */}
|
{/* 침수 상태 */}
|
||||||
<div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 8 }}>
|
<div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 8 }}>
|
||||||
<div style={{ fontSize: 9, fontWeight: 700, color: '#f87171', fontFamily: 'var(--fK)', marginBottom: 8 }}>💧 침수 상태</div>
|
<div style={{ fontSize: 9, fontWeight: 700, color: '#f87171', marginBottom: 8 }}>💧 침수 상태</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 8 }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: 3 }}>침수 구역 수</label>
|
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}>침수 구역 수</label>
|
||||||
<select defaultValue="2개" style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', outline: 'none', boxSizing: 'border-box' as const }}>
|
<select defaultValue="2개" style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none', boxSizing: 'border-box' as const }}>
|
||||||
{['1개', '2개', '3개', '4개 이상'].map(v => <option key={v}>{v}</option>)}
|
{['1개', '2개', '3개', '4개 이상'].map(v => <option key={v}>{v}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: 3 }}>침수량 (톤)</label>
|
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}>침수량 (톤)</label>
|
||||||
<input type="number" defaultValue={850} step={50} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
|
<input type="number" defaultValue={850} step={50} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: 3 }}>침수 진행률 (t/h)</label>
|
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}>침수 진행률 (t/h)</label>
|
||||||
<input type="number" defaultValue={120} step={10} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
|
<input type="number" defaultValue={120} step={10} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', display: 'block', marginBottom: 3 }}>배수 능력 (t/h)</label>
|
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}>배수 능력 (t/h)</label>
|
||||||
<input type="number" defaultValue={80} step={10} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
|
<input type="number" defaultValue={80} step={10} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -567,16 +567,16 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
<label style={labelSt}>경도 (Lon)</label>
|
<label style={labelSt}>경도 (Lon)</label>
|
||||||
<input type="text" defaultValue="128.4217" style={{ ...inputSt, fontFamily: 'var(--fM)' }} />
|
<input type="text" defaultValue="128.4217" style={{ ...inputSt, fontFamily: 'var(--fM)' }} />
|
||||||
</div>
|
</div>
|
||||||
<button style={{ padding: '8px 14px', borderRadius: 6, border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)', whiteSpace: 'nowrap' }}>📍 지도에서 선택</button>
|
<button style={{ padding: '8px 14px', borderRadius: 6, border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: 10, fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap' }}>📍 지도에서 선택</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
|
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={labelSt}>풍향 / 풍속 <span style={{ color: '#f87171' }}>*</span></label>
|
<label style={labelSt}>풍향 / 풍속 <span style={{ color: '#f87171' }}>*</span></label>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<select defaultValue="SW" style={{ flex: 1, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', outline: 'none' }}>
|
<select defaultValue="SW" style={{ flex: 1, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none' }}>
|
||||||
{['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].map(d => <option key={d}>{d}</option>)}
|
{['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].map(d => <option key={d}>{d}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<input type="number" defaultValue={12.5} step={0.5} min={0} style={{ width: 60, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', textAlign: 'center' }} />
|
<input type="number" defaultValue={12.5} step={0.5} min={0} style={{ width: 60, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', textAlign: 'center' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -586,10 +586,10 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
<div>
|
<div>
|
||||||
<label style={labelSt}>조류 방향 / 유속</label>
|
<label style={labelSt}>조류 방향 / 유속</label>
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<select defaultValue="NE" style={{ flex: 1, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', outline: 'none' }}>
|
<select defaultValue="NE" style={{ flex: 1, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none' }}>
|
||||||
{['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].map(d => <option key={d}>{d}</option>)}
|
{['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].map(d => <option key={d}>{d}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<input type="number" defaultValue={1.2} step={0.1} min={0} style={{ width: 60, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', textAlign: 'center' }} />
|
<input type="number" defaultValue={1.2} step={0.1} min={0} style={{ width: 60, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', textAlign: 'center' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -635,7 +635,7 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
<label style={labelSt}>분석 항목</label>
|
<label style={labelSt}>분석 항목</label>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 2 }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 2 }}>
|
||||||
{['복원성', '예인력', '인양력', '유출 위험'].map(item => (
|
{['복원성', '예인력', '인양력', '유출 위험'].map(item => (
|
||||||
<label key={item} style={{ display: 'flex', alignItems: 'center', gap: 3, fontSize: 8, color: 'var(--t2)', fontFamily: 'var(--fK)', cursor: 'pointer' }}>
|
<label key={item} style={{ display: 'flex', alignItems: 'center', gap: 3, fontSize: 8, color: 'var(--t2)', cursor: 'pointer' }}>
|
||||||
<input type="checkbox" defaultChecked style={{ accentColor: 'var(--cyan)', transform: 'scale(.85)' }} />{item}
|
<input type="checkbox" defaultChecked style={{ accentColor: 'var(--cyan)', transform: 'scale(.85)' }} />{item}
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
@ -644,36 +644,36 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
|
|||||||
</div>
|
</div>
|
||||||
{/* R&D 연계 분석 */}
|
{/* R&D 연계 분석 */}
|
||||||
<div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)' }}>🔗 R&D 연계 분석</div>
|
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--orange)' }}>🔗 R&D 연계 분석</div>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<div style={{ display: 'flex', gap: 12 }}>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', cursor: 'pointer' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: 'var(--t2)', cursor: 'pointer' }}>
|
||||||
<input type="checkbox" style={{ accentColor: 'var(--orange)' }} /> 유출유 확산예측 동시 실행
|
<input type="checkbox" style={{ accentColor: 'var(--orange)' }} /> 유출유 확산예측 동시 실행
|
||||||
</label>
|
</label>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fK)', cursor: 'pointer' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: 'var(--t2)', cursor: 'pointer' }}>
|
||||||
<input type="checkbox" style={{ accentColor: 'var(--orange)' }} /> HNS 대기확산 연계 분석
|
<input type="checkbox" style={{ accentColor: 'var(--orange)' }} /> HNS 대기확산 연계 분석
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>화물 유출 가능성이 있는 경우, 긴급구난 분석 결과와 확산예측을 동시에 수행하여 종합 대응 판단을 지원합니다</div>
|
<div style={{ fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>화물 유출 가능성이 있는 경우, 긴급구난 분석 결과와 확산예측을 동시에 수행하여 종합 대응 판단을 지원합니다</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ⑥ 비고 */}
|
{/* ⑥ 비고 */}
|
||||||
<div>
|
<div>
|
||||||
<div style={sectionTitle}>{sectionIcon(6)} 비고</div>
|
<div style={sectionTitle}>{sectionIcon(6)} 비고</div>
|
||||||
<textarea placeholder="시나리오 설명, 가정 조건, 현장 상황 특이사항 등을 기록합니다..." style={{ width: '100%', height: 60, padding: '10px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t1)', fontSize: 10, fontFamily: 'var(--fK)', outline: 'none', resize: 'vertical', lineHeight: 1.6, boxSizing: 'border-box' as const, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }} />
|
<textarea placeholder="시나리오 설명, 가정 조건, 현장 상황 특이사항 등을 기록합니다..." style={{ width: '100%', height: 60, padding: '10px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none', resize: 'vertical', lineHeight: 1.6, boxSizing: 'border-box' as const, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── 하단 버튼 ── */}
|
{/* ── 하단 버튼 ── */}
|
||||||
<div style={{ padding: '16px 24px', borderTop: '1px solid var(--bd)', flexShrink: 0, display: 'flex', gap: 8, alignItems: 'center' }}>
|
<div style={{ padding: '16px 24px', borderTop: '1px solid var(--bd)', flexShrink: 0, display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||||
<div style={{ flex: 1, fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fK)', lineHeight: 1.5 }}>
|
<div style={{ flex: 1, fontSize: 9, color: 'var(--t3)', lineHeight: 1.5 }}>
|
||||||
<span style={{ color: '#f87171' }}>*</span> 필수 입력 항목 · 해상 조건은 기상청/조사원 API 연계 시 자동 갱신
|
<span style={{ color: '#f87171' }}>*</span> 필수 입력 항목 · 해상 조건은 기상청/조사원 API 연계 시 자동 갱신
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onClose} style={{ padding: '10px 20px', borderRadius: 8, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--fK)' }}>취소</button>
|
<button onClick={onClose} style={{ padding: '10px 20px', borderRadius: 8, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>취소</button>
|
||||||
{done ? (
|
{done ? (
|
||||||
<button onClick={onClose} style={{ padding: '10px 28px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg,#22c55e,#10b981)', color: '#fff', fontSize: 12, fontWeight: 700, cursor: 'pointer', fontFamily: 'var(--fK)', boxShadow: '0 4px 16px rgba(34,197,94,.3)' }}>✅ 생성 완료 — 닫기</button>
|
<button onClick={onClose} style={{ padding: '10px 28px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg,#22c55e,#10b981)', color: '#fff', fontSize: 12, fontWeight: 700, cursor: 'pointer', boxShadow: '0 4px 16px rgba(34,197,94,.3)' }}>✅ 생성 완료 — 닫기</button>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={handleSubmit} disabled={submitting} style={{ padding: '10px 28px', borderRadius: 8, border: 'none', background: submitting ? 'var(--bg3)' : 'linear-gradient(135deg,#06b6d4,#3b82f6)', color: submitting ? 'var(--t3)' : '#fff', fontSize: 12, fontWeight: 700, cursor: submitting ? 'wait' : 'pointer', fontFamily: 'var(--fK)', boxShadow: submitting ? 'none' : '0 4px 16px rgba(6,182,212,.3)' }}>
|
<button onClick={handleSubmit} disabled={submitting} style={{ padding: '10px 28px', borderRadius: 8, border: 'none', background: submitting ? 'var(--bg3)' : 'linear-gradient(135deg,#06b6d4,#3b82f6)', color: submitting ? 'var(--t3)' : '#fff', fontSize: 12, fontWeight: 700, cursor: submitting ? 'wait' : 'pointer', boxShadow: submitting ? 'none' : '0 4px 16px rgba(6,182,212,.3)' }}>
|
||||||
{submitting ? '⏳ 분석 중...' : '🚨 시나리오 생성 · 분석 실행'}
|
{submitting ? '⏳ 분석 중...' : '🚨 시나리오 생성 · 분석 실행'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -691,7 +691,7 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
|
|||||||
|
|
||||||
if (chartData.length === 0) {
|
if (chartData.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 40, textAlign: 'center', fontSize: 11, color: 'var(--t3)', fontFamily: 'var(--fK)' }}>
|
<div style={{ padding: 40, textAlign: 'center', fontSize: 11, color: 'var(--t3)' }}>
|
||||||
비교할 시나리오 데이터가 없습니다.
|
비교할 시나리오 데이터가 없습니다.
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -701,7 +701,7 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
|
|||||||
<div style={{ padding: 20 }}>
|
<div style={{ padding: 20 }}>
|
||||||
{/* Chart 1: GM 추이 */}
|
{/* Chart 1: GM 추이 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fK)', marginBottom: 10 }}>📈 GM (복원심) 변화 추이 (m)</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--cyan)', marginBottom: 10 }}>📈 GM (복원심) 변화 추이 (m)</div>
|
||||||
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 180 }}>
|
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 180 }}>
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
{[0, 0.5, 1.0, 1.5, 2.0].map(v => {
|
{[0, 0.5, 1.0, 1.5, 2.0].map(v => {
|
||||||
@ -729,7 +729,7 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
|
||||||
{/* Chart 2: 횡경사 변화 */}
|
{/* Chart 2: 횡경사 변화 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fK)', marginBottom: 10 }}>📉 횡경사 (List) 변화 (°)</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', marginBottom: 10 }}>📉 횡경사 (List) 변화 (°)</div>
|
||||||
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 160 }}>
|
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 160 }}>
|
||||||
{[0, 5, 10, 15, 20, 25].map(v => {
|
{[0, 5, 10, 15, 20, 25].map(v => {
|
||||||
const y = PY + ph - (v / 25) * ph
|
const y = PY + ph - (v / 25) * ph
|
||||||
@ -748,7 +748,7 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
|
|||||||
|
|
||||||
{/* Chart 3: 유출률 변화 (bar) */}
|
{/* Chart 3: 유출률 변화 (bar) */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fK)', marginBottom: 10 }}>📊 유출률 변화 (L/min)</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', marginBottom: 10 }}>📊 유출률 변화 (L/min)</div>
|
||||||
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 160 }}>
|
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 160 }}>
|
||||||
{[0, 50, 100, 150, 200].map(v => {
|
{[0, 50, 100, 150, 200].map(v => {
|
||||||
const y = PY + ph - (v / 200) * ph
|
const y = PY + ph - (v / 200) * ph
|
||||||
@ -771,8 +771,8 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
|
|||||||
|
|
||||||
{/* Chart 4: 비교 테이블 */}
|
{/* Chart 4: 비교 테이블 */}
|
||||||
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--t1)', fontFamily: 'var(--fK)', marginBottom: 10 }}>📋 시나리오 종합 비교표</div>
|
<div style={{ fontSize: 11, fontWeight: 700, marginBottom: 10 }}>📋 시나리오 종합 비교표</div>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9, fontFamily: 'var(--fK)' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'rgba(6,182,212,.06)' }}>
|
<tr style={{ background: 'rgba(6,182,212,.06)' }}>
|
||||||
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--cyan)' }}>지표</th>
|
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--cyan)' }}>지표</th>
|
||||||
|
|||||||
@ -502,7 +502,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
|
|||||||
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5">해상 e-Call (GMDSS / VHF-DSC)</div>
|
<div className="text-[9px] font-bold text-text-1 font-korean mb-1.5">해상 e-Call (GMDSS / VHF-DSC)</div>
|
||||||
<div className="flex flex-col gap-0.5 text-[7px] font-mono text-text-3">
|
<div className="flex flex-col gap-0.5 text-[7px] font-mono text-text-3">
|
||||||
{[
|
{[
|
||||||
{ label: 'MMSI', value: '440123456', color: 'var(--t1)' },
|
{ label: 'MMSI', value: '440123456' },
|
||||||
{ label: 'Nature of Distress', value: 'COLLISION', color: 'var(--red)' },
|
{ label: 'Nature of Distress', value: 'COLLISION', color: 'var(--red)' },
|
||||||
{ label: 'DSC Alert', value: 'SENT ✓', color: 'var(--green)' },
|
{ label: 'DSC Alert', value: 'SENT ✓', color: 'var(--green)' },
|
||||||
{ label: 'EPIRB', value: 'ACTIVATED ✓', color: 'var(--green)' },
|
{ label: 'EPIRB', value: 'ACTIVATED ✓', color: 'var(--green)' },
|
||||||
@ -593,7 +593,7 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
|
|||||||
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono">
|
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono">
|
||||||
{[
|
{[
|
||||||
{ label: '지반반력', value: '1,240 kN', color: 'var(--yellow)' },
|
{ label: '지반반력', value: '1,240 kN', color: 'var(--yellow)' },
|
||||||
{ label: '접촉 면적', value: '12.5 m²', color: 'var(--t1)' },
|
{ label: '접촉 면적', value: '12.5 m²' },
|
||||||
{ label: '제거력(Removal)', value: '1,850 kN', color: 'var(--red)' },
|
{ label: '제거력(Removal)', value: '1,850 kN', color: 'var(--red)' },
|
||||||
{ label: '좌초 GM', value: '0.65 m', color: 'var(--yellow)' },
|
{ label: '좌초 GM', value: '0.65 m', color: 'var(--yellow)' },
|
||||||
].map((r, i) => (
|
].map((r, i) => (
|
||||||
@ -750,12 +750,12 @@ function BottomBar() {
|
|||||||
<div className="flex-1 overflow-y-auto px-3 py-1 font-mono text-[9px] leading-[1.7] scrollbar-thin">
|
<div className="flex-1 overflow-y-auto px-3 py-1 font-mono text-[9px] leading-[1.7] scrollbar-thin">
|
||||||
{[
|
{[
|
||||||
{ time: '10:35', msg: 'SOS FROM M/V SEA GUARDIAN', color: 'var(--red)', bold: true },
|
{ time: '10:35', msg: 'SOS FROM M/V SEA GUARDIAN', color: 'var(--red)', bold: true },
|
||||||
{ time: '10:35', msg: 'OIL LEAK DETECTED SENSOR #3', color: 'var(--t1)', bold: false },
|
{ time: '10:35', msg: 'OIL LEAK DETECTED SENSOR #3', bold: false },
|
||||||
{ time: '10:40', msg: 'CG HELO DISPATCHED', color: 'var(--red)', bold: true },
|
{ time: '10:40', msg: 'CG HELO DISPATCHED', color: 'var(--red)', bold: true },
|
||||||
{ time: '10:41', msg: 'GM CRITICAL ALERT — DAMAGE STABILITY FAIL', color: 'var(--red)', bold: true },
|
{ time: '10:41', msg: 'GM CRITICAL ALERT — DAMAGE STABILITY FAIL', color: 'var(--red)', bold: true },
|
||||||
{ time: '10:42', msg: 'Coast Guard 123 en route — ETA 15 min', color: 'var(--blue)', bold: false },
|
{ time: '10:42', msg: 'Coast Guard 123 en route — ETA 15 min', color: 'var(--blue)', bold: false },
|
||||||
{ time: '10:43', msg: 'LONGITUDINAL STRENGTH WARNING — BM 92% of LIMIT', color: 'var(--yellow)', bold: false },
|
{ time: '10:43', msg: 'LONGITUDINAL STRENGTH WARNING — BM 92% of LIMIT', color: 'var(--yellow)', bold: false },
|
||||||
{ time: '10:45', msg: 'BALLAST TRANSFER INITIATED — PORT #2 → STBD #3', color: 'var(--t1)', bold: false },
|
{ time: '10:45', msg: 'BALLAST TRANSFER INITIATED — PORT #2 → STBD #3', bold: false },
|
||||||
{ time: '10:48', msg: 'LIST INCREASING — 12° → 15°', color: 'var(--yellow)', bold: false },
|
{ time: '10:48', msg: 'LIST INCREASING — 12° → 15°', color: 'var(--yellow)', bold: false },
|
||||||
{ time: '10:50', msg: 'RESCUE HELO ON SCENE — HOISTING OPS', color: 'var(--blue)', bold: false },
|
{ time: '10:50', msg: 'RESCUE HELO ON SCENE — HOISTING OPS', color: 'var(--blue)', bold: false },
|
||||||
{ time: '10:55', msg: '5 SURVIVORS RECOVERED BY HELO', color: 'var(--green)', bold: false },
|
{ time: '10:55', msg: '5 SURVIVORS RECOVERED BY HELO', color: 'var(--green)', bold: false },
|
||||||
|
|||||||
@ -19,9 +19,9 @@ export default {
|
|||||||
light: '#2a3a5c',
|
light: '#2a3a5c',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
1: '#e4e8f1',
|
1: '#edf0f7',
|
||||||
2: '#8892a8',
|
2: '#b0b8cc',
|
||||||
3: '#5a6478',
|
3: '#8690a6',
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
blue: '#3b82f6',
|
blue: '#3b82f6',
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user