- STS(Ship-to-Ship) 접촉 분석 기능 전체 구현 - API 연동 (vessel-contacts), 스토어, 레이어 훅, 레이어 레지스트리 - 접촉 쌍 그룹핑, 그룹 카드 목록, 상세 모달 (그리드 레이아웃) - ScatterplotLayer 접촉 포인트 + 위험도 색상 - 항적분석 탭 UI 분리 (구역분석 / STS분석) - AreaSearchPage → AreaSearchTab, StsAnalysisTab 추출 - 탭 전환 시 결과 초기화 확인, 구역 클리어 - 지도 호버 하이라이트 구현 (구역분석 + STS) - MapContainer pointermove에 STS 레이어 ID 핸들러 추가 - STS 쌍 항적 동시 하이라이트 (vesselId → groupIndex 매핑) - 목록↔지도 호버 연동 자동 스크롤 - pickingRadius 12→20 확대 - 재생 컨트롤러(AreaSearchTimeline) STS 지원 - 항적/궤적 토글 activeTab 기반 스토어 분기 - 닫기 시 양쪽 스토어 + 레이어 정리 - 패널 닫기 초기화 수정 (isOpen 감지, clearResults로 탭 보존) - 조회 중 로딩 오버레이 (LoadingOverlay 공통 컴포넌트) - 항적분석 다중 방문 대응, 선박 상세 모달, 구역 편집 기능 - trackLayer updateTriggers Set 직렬화, highlightedVesselIds 지원 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
320 lines
5.3 KiB
SCSS
320 lines
5.3 KiB
SCSS
.sts-detail-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 300;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
.sts-detail-modal {
|
|
position: fixed;
|
|
z-index: 301;
|
|
width: 680px;
|
|
max-height: 90vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: rgba(20, 24, 32, 0.98);
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
|
|
color: #fff;
|
|
overflow: hidden;
|
|
|
|
&__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
flex-shrink: 0;
|
|
cursor: move;
|
|
user-select: none;
|
|
}
|
|
|
|
&__title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
&__arrow {
|
|
color: #4a9eff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__vessel-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
min-width: 0;
|
|
}
|
|
|
|
&__kind {
|
|
padding: 2px 6px;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-radius: 3px;
|
|
font-size: 10px;
|
|
color: #adb5bd;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__flag {
|
|
width: 18px;
|
|
height: 13px;
|
|
object-fit: contain;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__name {
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
&__close {
|
|
width: 28px;
|
|
height: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: none;
|
|
border: none;
|
|
color: #868e96;
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
flex-shrink: 0;
|
|
|
|
&:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
&__content {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
&__map {
|
|
width: 100%;
|
|
height: 480px;
|
|
flex-shrink: 0;
|
|
background: #0d1117;
|
|
|
|
.ol-scale-line {
|
|
bottom: 8px;
|
|
left: 8px;
|
|
}
|
|
}
|
|
|
|
&__risk-bar {
|
|
height: 3px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__section {
|
|
padding: 10px 16px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
|
|
&__section-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
color: #ced4da;
|
|
margin: 0 0 8px 0;
|
|
}
|
|
|
|
&__track-dot {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
color: #ced4da;
|
|
padding: 2px 0;
|
|
}
|
|
|
|
&__label {
|
|
font-weight: 600;
|
|
font-size: 11px;
|
|
color: #868e96;
|
|
min-width: 60px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__pos {
|
|
color: #74b9ff;
|
|
font-size: 11px;
|
|
}
|
|
|
|
// ========== 그리드 레이아웃 ==========
|
|
|
|
&__summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 6px;
|
|
}
|
|
|
|
&__stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 6px;
|
|
}
|
|
|
|
&__stat-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
padding: 6px 8px;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border-radius: 4px;
|
|
|
|
.stat-label {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #868e96;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 12px;
|
|
color: #ced4da;
|
|
}
|
|
}
|
|
|
|
&__vessel-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 6px;
|
|
}
|
|
|
|
&__vessel-grid-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
padding: 6px 8px;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border-radius: 4px;
|
|
|
|
.vessel-item-label {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #868e96;
|
|
}
|
|
|
|
.vessel-item-value {
|
|
font-size: 12px;
|
|
color: #ced4da;
|
|
}
|
|
}
|
|
|
|
// ========== 접촉 이력 리스트 ==========
|
|
|
|
&__contact-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
&__contact-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 8px;
|
|
font-size: 11px;
|
|
color: #ced4da;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
&__contact-num {
|
|
font-weight: 700;
|
|
color: #4a9eff;
|
|
min-width: 20px;
|
|
}
|
|
|
|
&__contact-sep {
|
|
color: #495057;
|
|
font-size: 10px;
|
|
}
|
|
|
|
&__indicators {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
&__badge {
|
|
display: inline-block;
|
|
padding: 3px 8px;
|
|
border-radius: 3px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
|
|
&--lowSpeedContact {
|
|
background: rgba(46, 204, 113, 0.15);
|
|
color: #2ecc71;
|
|
}
|
|
|
|
&--differentVesselTypes {
|
|
background: rgba(243, 156, 18, 0.15);
|
|
color: #f39c12;
|
|
}
|
|
|
|
&--differentNationalities {
|
|
background: rgba(52, 152, 219, 0.15);
|
|
color: #3498db;
|
|
}
|
|
|
|
&--nightTimeContact {
|
|
background: rgba(155, 89, 182, 0.15);
|
|
color: #9b59b6;
|
|
}
|
|
}
|
|
|
|
&__footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding: 10px 16px;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__save-btn {
|
|
padding: 6px 16px;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
color: #ced4da;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
|
|
&:hover {
|
|
border-color: #4a9eff;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|