refactor: 인라인 CSS 정리 #69
@ -1317,6 +1317,48 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ═══ 범용 팝업 스타일 ═══ */
|
||||
.popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
margin: -10px -10px 8px -10px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.popup-body-sm {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.popup-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 3px 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.popup-label {
|
||||
color: var(--kcg-muted, #888);
|
||||
}
|
||||
|
||||
.popup-desc {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--kcg-text-secondary, #ccc);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ship-popup-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@ -2234,3 +2276,147 @@
|
||||
color: var(--kcg-border-light);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* ===== CollectorMonitor ===== */
|
||||
.collector-modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10000;
|
||||
background: var(--kcg-panel-bg, rgba(15, 23, 42, 0.95));
|
||||
border: 1px solid var(--kcg-border, rgba(100, 116, 139, 0.3));
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-width: 600px;
|
||||
max-width: 800px;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
color: var(--kcg-text, #e2e8f0);
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.collector-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.collector-title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.collector-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.collector-server-time {
|
||||
font-size: 11px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.collector-refresh-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--kcg-border, rgba(100, 116, 139, 0.3));
|
||||
color: var(--kcg-text, #e2e8f0);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.collector-close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--kcg-text, #e2e8f0);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.collector-error {
|
||||
padding: 8px 12px;
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.collector-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.collector-table thead tr {
|
||||
border-bottom: 1px solid var(--kcg-border, rgba(100, 116, 139, 0.3));
|
||||
}
|
||||
|
||||
.collector-th {
|
||||
text-align: left;
|
||||
padding: 6px 8px;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.collector-th-right {
|
||||
text-align: right;
|
||||
padding: 6px 8px;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.collector-table tbody tr {
|
||||
border-bottom: 1px solid var(--kcg-border, rgba(100, 116, 139, 0.15));
|
||||
}
|
||||
|
||||
.collector-td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.collector-td-right {
|
||||
padding: 8px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.collector-td-name {
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.collector-status-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.collector-success-count {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.collector-td-last-success {
|
||||
padding: 8px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.collector-td-error {
|
||||
padding: 8px;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.collector-td-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@ -91,28 +91,18 @@ const LoginPage = ({ onGoogleLogin, onDevLogin }: LoginPageProps) => {
|
||||
const googleBtnRef = useGoogleIdentity(handleGoogleCredential);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-screen items-center justify-center"
|
||||
style={{ backgroundColor: 'var(--kcg-bg)' }}
|
||||
>
|
||||
<div
|
||||
className="flex w-full max-w-sm flex-col items-center gap-6 rounded-xl border p-8"
|
||||
style={{
|
||||
backgroundColor: 'var(--kcg-card)',
|
||||
borderColor: 'var(--kcg-border)',
|
||||
}}
|
||||
>
|
||||
<div className="flex min-h-screen items-center justify-center bg-kcg-bg">
|
||||
<div className="flex w-full max-w-sm flex-col items-center gap-6 rounded-xl border border-kcg-border bg-kcg-card p-8">
|
||||
{/* Title */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="relative inline-block">
|
||||
<img src="/kcg.svg" alt="KCG" style={{ width: 120, height: 120 }} />
|
||||
<span
|
||||
className="absolute font-black tracking-widest"
|
||||
className="absolute font-black tracking-widest text-kcg-danger"
|
||||
style={{
|
||||
bottom: 2,
|
||||
right: -8,
|
||||
fontSize: 14,
|
||||
color: 'var(--kcg-danger)',
|
||||
opacity: 0.85,
|
||||
textShadow: '0 0 4px rgba(0,0,0,0.6)',
|
||||
}}
|
||||
@ -120,29 +110,17 @@ const LoginPage = ({ onGoogleLogin, onDevLogin }: LoginPageProps) => {
|
||||
DEMO
|
||||
</span>
|
||||
</div>
|
||||
<h1
|
||||
className="text-xl font-bold"
|
||||
style={{ color: 'var(--kcg-text)' }}
|
||||
>
|
||||
<h1 className="text-xl font-bold text-kcg-text">
|
||||
{t('auth.title')}
|
||||
</h1>
|
||||
<p
|
||||
className="text-sm"
|
||||
style={{ color: 'var(--kcg-muted)' }}
|
||||
>
|
||||
<p className="text-sm text-kcg-muted">
|
||||
{t('auth.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div
|
||||
className="w-full rounded-lg px-4 py-2 text-center text-sm"
|
||||
style={{
|
||||
backgroundColor: 'var(--kcg-danger-bg)',
|
||||
color: 'var(--kcg-danger)',
|
||||
}}
|
||||
>
|
||||
<div className="w-full rounded-lg bg-kcg-danger-bg px-4 py-2 text-center text-sm text-kcg-danger">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@ -151,10 +129,7 @@ const LoginPage = ({ onGoogleLogin, onDevLogin }: LoginPageProps) => {
|
||||
{GOOGLE_CLIENT_ID && (
|
||||
<>
|
||||
<div ref={googleBtnRef} />
|
||||
<p
|
||||
className="text-xs"
|
||||
style={{ color: 'var(--kcg-dim)' }}
|
||||
>
|
||||
<p className="text-xs text-kcg-dim">
|
||||
{t('auth.domainNotice')}
|
||||
</p>
|
||||
</>
|
||||
@ -163,32 +138,15 @@ const LoginPage = ({ onGoogleLogin, onDevLogin }: LoginPageProps) => {
|
||||
{/* Dev Login */}
|
||||
{IS_DEV && (
|
||||
<>
|
||||
<div
|
||||
className="w-full border-t pt-4 text-center"
|
||||
style={{ borderColor: 'var(--kcg-border)' }}
|
||||
>
|
||||
<span
|
||||
className="text-xs font-mono tracking-wider"
|
||||
style={{ color: 'var(--kcg-dim)' }}
|
||||
>
|
||||
<div className="w-full border-t border-kcg-border pt-4 text-center">
|
||||
<span className="text-xs font-mono tracking-wider text-kcg-dim">
|
||||
{t('auth.devNotice')}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDevLogin}
|
||||
className="w-full cursor-pointer rounded-lg border-2 px-4 py-3 text-sm font-bold transition-colors"
|
||||
style={{
|
||||
borderColor: 'var(--kcg-danger)',
|
||||
color: 'var(--kcg-danger)',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'var(--kcg-danger-bg)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}}
|
||||
className="w-full cursor-pointer rounded-lg border-2 border-kcg-danger bg-transparent px-4 py-3 text-sm font-bold text-kcg-danger transition-colors hover:bg-kcg-danger-bg"
|
||||
>
|
||||
{t('auth.devLogin')}
|
||||
</button>
|
||||
|
||||
@ -50,61 +50,27 @@ const CollectorMonitor = ({ onClose }: CollectorMonitorProps) => {
|
||||
}, [refresh]);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: 10000,
|
||||
background: 'var(--kcg-panel-bg, rgba(15, 23, 42, 0.95))',
|
||||
border: '1px solid var(--kcg-border, rgba(100, 116, 139, 0.3))',
|
||||
borderRadius: '12px',
|
||||
padding: '20px',
|
||||
minWidth: '600px',
|
||||
maxWidth: '800px',
|
||||
maxHeight: '80vh',
|
||||
overflow: 'auto',
|
||||
color: 'var(--kcg-text, #e2e8f0)',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '13px',
|
||||
boxShadow: '0 25px 50px rgba(0, 0, 0, 0.5)',
|
||||
}}>
|
||||
<div className='collector-modal'>
|
||||
{/* Header */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '15px', fontWeight: 600 }}>
|
||||
<div className='collector-header'>
|
||||
<h3 className='collector-title'>
|
||||
수집기 모니터링
|
||||
</h3>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
<div className='collector-header-actions'>
|
||||
{serverTime && (
|
||||
<span style={{ fontSize: '11px', opacity: 0.6 }}>
|
||||
<span className='collector-server-time'>
|
||||
서버: {new Date(serverTime).toLocaleTimeString('ko-KR')}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={refresh}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: '1px solid var(--kcg-border, rgba(100, 116, 139, 0.3))',
|
||||
color: 'var(--kcg-text, #e2e8f0)',
|
||||
borderRadius: '4px',
|
||||
padding: '2px 8px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
className='collector-refresh-btn'
|
||||
>
|
||||
새로고침
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'var(--kcg-text, #e2e8f0)',
|
||||
cursor: 'pointer',
|
||||
fontSize: '18px',
|
||||
lineHeight: 1,
|
||||
padding: '0 4px',
|
||||
}}
|
||||
className='collector-close-btn'
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@ -112,62 +78,57 @@ const CollectorMonitor = ({ onClose }: CollectorMonitorProps) => {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div style={{ padding: '8px 12px', background: 'rgba(239, 68, 68, 0.2)', borderRadius: '6px', marginBottom: '12px' }}>
|
||||
<div className='collector-error'>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<table className='collector-table'>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '1px solid var(--kcg-border, rgba(100, 116, 139, 0.3))' }}>
|
||||
<th style={{ textAlign: 'left', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>상태</th>
|
||||
<th style={{ textAlign: 'left', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>수집기</th>
|
||||
<th style={{ textAlign: 'right', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>최근 건수</th>
|
||||
<th style={{ textAlign: 'right', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>성공/실패</th>
|
||||
<th style={{ textAlign: 'right', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>총 수집</th>
|
||||
<th style={{ textAlign: 'left', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>마지막 성공</th>
|
||||
<th style={{ textAlign: 'left', padding: '6px 8px', fontSize: '11px', opacity: 0.7 }}>에러</th>
|
||||
<tr>
|
||||
<th className='collector-th'>상태</th>
|
||||
<th className='collector-th'>수집기</th>
|
||||
<th className='collector-th-right'>최근 건수</th>
|
||||
<th className='collector-th-right'>성공/실패</th>
|
||||
<th className='collector-th-right'>총 수집</th>
|
||||
<th className='collector-th'>마지막 성공</th>
|
||||
<th className='collector-th'>에러</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{collectors.map((c) => (
|
||||
<tr key={c.name} style={{ borderBottom: '1px solid var(--kcg-border, rgba(100, 116, 139, 0.15))' }}>
|
||||
<td style={{ padding: '8px' }}>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: getStatusColor(c),
|
||||
}} />
|
||||
<tr key={c.name}>
|
||||
<td className='collector-td'>
|
||||
<span
|
||||
className='collector-status-dot'
|
||||
style={{ backgroundColor: getStatusColor(c) }}
|
||||
/>
|
||||
</td>
|
||||
<td style={{ padding: '8px', fontWeight: 500 }}>{c.name}</td>
|
||||
<td style={{ padding: '8px', textAlign: 'right' }}>{c.lastCount}</td>
|
||||
<td style={{ padding: '8px', textAlign: 'right' }}>
|
||||
<span style={{ color: '#22c55e' }}>{c.totalSuccess}</span>
|
||||
<td className='collector-td-name'>{c.name}</td>
|
||||
<td className='collector-td-right'>{c.lastCount}</td>
|
||||
<td className='collector-td-right'>
|
||||
<span className='collector-success-count'>{c.totalSuccess}</span>
|
||||
{' / '}
|
||||
<span style={{ color: c.totalFailure > 0 ? '#ef4444' : 'inherit' }}>{c.totalFailure}</span>
|
||||
</td>
|
||||
<td style={{ padding: '8px', textAlign: 'right' }}>{c.totalItems.toLocaleString()}</td>
|
||||
<td style={{ padding: '8px', opacity: 0.8 }}>{formatRelativeTime(c.lastSuccess)}</td>
|
||||
<td style={{
|
||||
padding: '8px',
|
||||
maxWidth: '200px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
color: c.lastError ? '#ef4444' : 'inherit',
|
||||
opacity: c.lastError ? 1 : 0.4,
|
||||
fontSize: '11px',
|
||||
}} title={c.lastError || ''}>
|
||||
<td className='collector-td-right'>{c.totalItems.toLocaleString()}</td>
|
||||
<td className='collector-td-last-success'>{formatRelativeTime(c.lastSuccess)}</td>
|
||||
<td
|
||||
className='collector-td-error'
|
||||
style={{
|
||||
color: c.lastError ? '#ef4444' : 'inherit',
|
||||
opacity: c.lastError ? 1 : 0.4,
|
||||
}}
|
||||
title={c.lastError || ''}
|
||||
>
|
||||
{c.lastError || '-'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{collectors.length === 0 && !error && (
|
||||
<tr>
|
||||
<td colSpan={7} style={{ padding: '20px', textAlign: 'center', opacity: 0.5 }}>
|
||||
<td colSpan={7} className='collector-td-empty'>
|
||||
수집기 데이터 로딩 중...
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -91,16 +91,11 @@ function AirportMarker({ airport }: { airport: Airport }) {
|
||||
<Popup longitude={airport.lng} latitude={airport.lat}
|
||||
onClose={() => setShowPopup(false)} closeOnClick={false}
|
||||
anchor="bottom" offset={[0, -size / 2]} maxWidth="280px" className="gl-popup">
|
||||
<div style={{ minWidth: 220, fontFamily: 'monospace', fontSize: 12 }}>
|
||||
<div style={{
|
||||
background: isUS ? '#1e3a5f' : isMil ? '#991b1b' : '#92400e',
|
||||
color: '#fff', padding: '6px 10px', borderRadius: '4px 4px 0 0',
|
||||
margin: '-10px -10px 8px -10px',
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
}}>
|
||||
<div className="popup-body" style={{ minWidth: 220 }}>
|
||||
<div className="popup-header" style={{ background: isUS ? '#1e3a5f' : isMil ? '#991b1b' : '#92400e' }}>
|
||||
{isUS ? <span style={{ fontSize: 16 }}>{'\u{1F1FA}\u{1F1F8}'}</span>
|
||||
: flag ? <span style={{ fontSize: 16 }}>{flag}</span> : null}
|
||||
<strong style={{ fontSize: 13, flex: 1 }}>{airport.name}</strong>
|
||||
<strong style={{ flex: 1 }}>{airport.name}</strong>
|
||||
</div>
|
||||
{airport.nameKo && (
|
||||
<div style={{ fontSize: 12, color: '#ccc', marginBottom: 6 }}>{airport.nameKo}</div>
|
||||
@ -113,11 +108,11 @@ function AirportMarker({ airport }: { airport: Airport }) {
|
||||
{isUS ? 'US Military Base' : TYPE_LABELS[airport.type]}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 11, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2px 12px' }}>
|
||||
{airport.iata && <div><span style={{ color: '#888' }}>IATA : </span><strong>{airport.iata}</strong></div>}
|
||||
<div><span style={{ color: '#888' }}>ICAO : </span><strong>{airport.icao}</strong></div>
|
||||
{airport.city && <div><span style={{ color: '#888' }}>City : </span>{airport.city}</div>}
|
||||
<div><span style={{ color: '#888' }}>Country : </span>{airport.country}</div>
|
||||
<div className="popup-grid" style={{ gap: '2px 12px' }}>
|
||||
{airport.iata && <div><span className="popup-label">IATA : </span><strong>{airport.iata}</strong></div>}
|
||||
<div><span className="popup-label">ICAO : </span><strong>{airport.icao}</strong></div>
|
||||
{airport.city && <div><span className="popup-label">City : </span>{airport.city}</div>}
|
||||
<div><span className="popup-label">Country : </span>{airport.country}</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 6, fontSize: 10, color: '#999' }}>
|
||||
{airport.lat.toFixed(4)}°{airport.lat >= 0 ? 'N' : 'S'}, {airport.lng.toFixed(4)}°{airport.lng >= 0 ? 'E' : 'W'}
|
||||
|
||||
@ -121,14 +121,8 @@ export function InfraLayer({ facilities }: Props) {
|
||||
<Popup longitude={selected.lng} latitude={selected.lat}
|
||||
onClose={() => setSelectedId(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="280px" className="gl-popup">
|
||||
<div style={{ fontFamily: 'monospace', fontSize: 12, minWidth: 200 }}>
|
||||
<div style={{
|
||||
background: getStyle(selected).color, color: '#000',
|
||||
padding: '4px 8px', borderRadius: '4px 4px 0 0',
|
||||
margin: '-10px -10px 8px -10px',
|
||||
fontWeight: 700, fontSize: 13,
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
}}>
|
||||
<div className="popup-body-sm" style={{ minWidth: 200 }}>
|
||||
<div className="popup-header" style={{ background: getStyle(selected).color, color: '#000', gap: 6, padding: '4px 8px' }}>
|
||||
<span>{getStyle(selected).icon}</span>
|
||||
{selected.name}
|
||||
</div>
|
||||
@ -146,18 +140,18 @@ export function InfraLayer({ facilities }: Props) {
|
||||
{selected.type === 'plant' ? '발전소' : '변전소'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 11, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{selected.output && (
|
||||
<div><span style={{ color: '#888' }}>출력: </span><strong>{selected.output}</strong></div>
|
||||
<div><span className="popup-label">출력: </span><strong>{selected.output}</strong></div>
|
||||
)}
|
||||
{selected.voltage && (
|
||||
<div><span style={{ color: '#888' }}>전압: </span><strong>{formatVoltage(selected.voltage)}</strong></div>
|
||||
<div><span className="popup-label">전압: </span><strong>{formatVoltage(selected.voltage)}</strong></div>
|
||||
)}
|
||||
{selected.operator && (
|
||||
<div><span style={{ color: '#888' }}>운영: </span>{selected.operator}</div>
|
||||
<div><span className="popup-label">운영: </span>{selected.operator}</div>
|
||||
)}
|
||||
{selected.source && (
|
||||
<div><span style={{ color: '#888' }}>연료: </span>{selected.source}</div>
|
||||
<div><span className="popup-label">연료: </span>{selected.source}</div>
|
||||
)}
|
||||
<div style={{ fontSize: 9, color: '#666', marginTop: 2 }}>
|
||||
{selected.lat.toFixed(4)}°N, {selected.lng.toFixed(4)}°E
|
||||
|
||||
@ -86,14 +86,8 @@ export function SubmarineCableLayer() {
|
||||
<Popup longitude={selectedPoint.lng} latitude={selectedPoint.lat}
|
||||
onClose={() => setSelectedPoint(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="260px" className="gl-popup">
|
||||
<div style={{ fontFamily: 'monospace', fontSize: 12, minWidth: 180 }}>
|
||||
<div style={{
|
||||
background: '#00e5ff', color: '#000',
|
||||
padding: '4px 8px', borderRadius: '4px 4px 0 0',
|
||||
margin: '-10px -10px 8px -10px',
|
||||
fontWeight: 700, fontSize: 13,
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
}}>
|
||||
<div className="popup-body-sm" style={{ minWidth: 180 }}>
|
||||
<div className="popup-header" style={{ background: '#00e5ff', color: '#000', gap: 6, padding: '4px 8px' }}>
|
||||
<span>📡</span> {selectedPoint.name} 해저케이블 기지
|
||||
</div>
|
||||
<div style={{ fontSize: 10, color: '#aaa', marginBottom: 4 }}>
|
||||
@ -122,28 +116,23 @@ export function SubmarineCableLayer() {
|
||||
latitude={selectedCable.route[0][1]}
|
||||
onClose={() => setSelectedCable(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="280px" className="gl-popup">
|
||||
<div style={{ fontFamily: 'monospace', fontSize: 12, minWidth: 200 }}>
|
||||
<div style={{
|
||||
background: selectedCable.color, color: '#000',
|
||||
padding: '4px 8px', borderRadius: '4px 4px 0 0',
|
||||
margin: '-10px -10px 8px -10px',
|
||||
fontWeight: 700, fontSize: 13,
|
||||
}}>
|
||||
<div className="popup-body-sm" style={{ minWidth: 200 }}>
|
||||
<div className="popup-header" style={{ background: selectedCable.color, color: '#000', padding: '4px 8px' }}>
|
||||
🔌 {selectedCable.name}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<div>
|
||||
<span style={{ color: '#888' }}>경유지: </span>
|
||||
<span className="popup-label">경유지: </span>
|
||||
<span style={{ color: '#ddd' }}>{selectedCable.landingPoints.join(' → ')}</span>
|
||||
</div>
|
||||
{selectedCable.rfsYear && (
|
||||
<div><span style={{ color: '#888' }}>개통: </span>{selectedCable.rfsYear}년</div>
|
||||
<div><span className="popup-label">개통: </span>{selectedCable.rfsYear}년</div>
|
||||
)}
|
||||
{selectedCable.length && (
|
||||
<div><span style={{ color: '#888' }}>총 길이: </span>{selectedCable.length}</div>
|
||||
<div><span className="popup-label">총 길이: </span>{selectedCable.length}</div>
|
||||
)}
|
||||
{selectedCable.owners && (
|
||||
<div><span style={{ color: '#888' }}>운영: </span>{selectedCable.owners}</div>
|
||||
<div><span className="popup-label">운영: </span>{selectedCable.owners}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -120,13 +120,8 @@ export function DamagedShipLayer({ currentTime }: Props) {
|
||||
<Popup longitude={selected.lng} latitude={selected.lat}
|
||||
onClose={() => setSelectedId(null)} closeOnClick={false}
|
||||
anchor="bottom" maxWidth="320px" className="gl-popup">
|
||||
<div style={{ minWidth: 260, fontFamily: 'monospace', fontSize: 12 }}>
|
||||
<div style={{
|
||||
background: DAMAGE_COLORS[selected.damage], color: '#fff',
|
||||
padding: '6px 10px', borderRadius: '4px 4px 0 0',
|
||||
margin: '-10px -10px 8px -10px',
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
}}>
|
||||
<div className="popup-body" style={{ minWidth: 260 }}>
|
||||
<div className="popup-header" style={{ background: DAMAGE_COLORS[selected.damage] }}>
|
||||
{FLAG_EMOJI[selected.flag] && <span style={{ fontSize: 16 }}>{FLAG_EMOJI[selected.flag]}</span>}
|
||||
<strong style={{ flex: 1 }}>{selected.name}</strong>
|
||||
<span style={{
|
||||
@ -134,13 +129,13 @@ export function DamagedShipLayer({ currentTime }: Props) {
|
||||
borderRadius: 3, fontSize: 10,
|
||||
}}>{DAMAGE_LABELS[selected.damage]}</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px 12px', fontSize: 11 }}>
|
||||
<div><span style={{ color: '#888' }}>선종 : </span>{selected.type}</div>
|
||||
<div><span style={{ color: '#888' }}>국적 : </span>{selected.flag}</div>
|
||||
<div><span style={{ color: '#888' }}>원인 : </span>{selected.cause}</div>
|
||||
<div><span style={{ color: '#888' }}>피격 : </span>{formatKST(selected.damagedAt)}</div>
|
||||
<div className="popup-grid">
|
||||
<div><span className="popup-label">선종 : </span>{selected.type}</div>
|
||||
<div><span className="popup-label">국적 : </span>{selected.flag}</div>
|
||||
<div><span className="popup-label">원인 : </span>{selected.cause}</div>
|
||||
<div><span className="popup-label">피격 : </span>{formatKST(selected.damagedAt)}</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 6, fontSize: 11, color: '#ccc', lineHeight: 1.4 }}>
|
||||
<div className="popup-desc">
|
||||
{selected.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user