From b20510599c874df80975047e86d0dea5f9135d2f Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 20 Feb 2026 18:44:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20UI=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=88=98=EC=A0=95=20+=20=EA=B5=AC=EC=97=AD?= =?UTF-8?q?=EB=B6=84=EC=84=9D/STS=20=EB=B3=B4=EA=B3=A0=EC=84=9C=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20+=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 날짜 입력 세로 배치 (좁은 사이드바 대응) - 결과 리스트 높이 max-h-[40vh] 반응형 개선 - TimelineControl 반응형 폭 + z-index 수정 - VesselDetailModal: 임베디드 지도 + 방문이력 + 이미지 저장 - StsContactDetailModal: 2항적 지도 + 접촉통계 + 이미지 저장 - html2canvas 의존성 추가 Co-Authored-By: Claude Opus 4.6 --- frontend/package-lock.json | 50 +++ frontend/package.json | 1 + .../components/AreaSearchPanel.tsx | 33 +- .../area-search/components/AreaSearchTab.tsx | 24 +- .../components/StsContactDetailModal.tsx | 350 ++++++++++++++++++ .../area-search/components/StsContactList.tsx | 31 +- .../components/TimelineControl.tsx | 2 +- .../components/VesselDetailModal.tsx | 310 ++++++++++++++++ frontend/src/features/area-search/index.ts | 6 +- .../features/area-search/types/sts.types.ts | 32 ++ .../area-search/utils/captureUtils.ts | 58 +++ .../features/area-search/utils/formatUtils.ts | 33 ++ 12 files changed, 901 insertions(+), 29 deletions(-) create mode 100644 frontend/src/features/area-search/components/StsContactDetailModal.tsx create mode 100644 frontend/src/features/area-search/components/VesselDetailModal.tsx create mode 100644 frontend/src/features/area-search/utils/captureUtils.ts create mode 100644 frontend/src/features/area-search/utils/formatUtils.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3b8cdb2..397ff13 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@deck.gl/layers": "^9.2.8", "@deck.gl/mapbox": "^9.2.8", "@stomp/stompjs": "^7.3.0", + "html2canvas": "^1.4.1", "maplibre-gl": "^5.18.0", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -3061,6 +3062,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3312,6 +3322,15 @@ "node": "*" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -4027,6 +4046,19 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5385,6 +5417,15 @@ "terra-draw": "^1.0.0" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/texture-compressor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/texture-compressor/-/texture-compressor-1.0.2.tgz", @@ -5553,6 +5594,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index a59cd2d..2b56c23 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@deck.gl/layers": "^9.2.8", "@deck.gl/mapbox": "^9.2.8", "@stomp/stompjs": "^7.3.0", + "html2canvas": "^1.4.1", "maplibre-gl": "^5.18.0", "react": "^19.2.0", "react-dom": "^19.2.0", diff --git a/frontend/src/features/area-search/components/AreaSearchPanel.tsx b/frontend/src/features/area-search/components/AreaSearchPanel.tsx index 1b06c45..f431e5f 100644 --- a/frontend/src/features/area-search/components/AreaSearchPanel.tsx +++ b/frontend/src/features/area-search/components/AreaSearchPanel.tsx @@ -99,20 +99,25 @@ export default function AreaSearchPanel({ map, mode }: AreaSearchPanelProps) { {/* 시간 범위 */}
-
- setStartTime(e.target.value)} - className="w-full rounded-md border border-border bg-surface px-2 py-1.5 text-xs text-foreground" - /> - ~ - setEndTime(e.target.value)} - className="w-full rounded-md border border-border bg-surface px-2 py-1.5 text-xs text-foreground" - /> +
+
+ 시작 + setStartTime(e.target.value)} + className="w-full rounded-md border border-border bg-surface px-2 py-1.5 text-xs text-foreground" + /> +
+
+ 종료 + setEndTime(e.target.value)} + className="w-full rounded-md border border-border bg-surface px-2 py-1.5 text-xs text-foreground" + /> +
diff --git a/frontend/src/features/area-search/components/AreaSearchTab.tsx b/frontend/src/features/area-search/components/AreaSearchTab.tsx index c839410..57dd23f 100644 --- a/frontend/src/features/area-search/components/AreaSearchTab.tsx +++ b/frontend/src/features/area-search/components/AreaSearchTab.tsx @@ -1,9 +1,10 @@ -import { useCallback } from 'react' +import { useCallback, useState } from 'react' import { useAreaSearchStore } from '../stores/areaSearchStore' import { SEARCH_MODE_OPTIONS } from '../types/areaSearch.types' import { getShipKindLabel } from '../../vessel-map' import { exportAreaSearchCSV } from '../utils/csvExport' import type { SearchMode } from '../types/areaSearch.types' +import VesselDetailModal from './VesselDetailModal' export default function AreaSearchTab() { const searchMode = useAreaSearchStore((s) => s.searchMode) @@ -17,6 +18,8 @@ export default function AreaSearchTab() { const toggleVesselEnabled = useAreaSearchStore((s) => s.toggleVesselEnabled) const setHighlightedVesselId = useAreaSearchStore((s) => s.setHighlightedVesselId) + const [detailVesselId, setDetailVesselId] = useState(null) + const handleModeChange = useCallback( (mode: SearchMode) => setSearchMode(mode), [setSearchMode], @@ -79,7 +82,7 @@ export default function AreaSearchTab() { CSV 내보내기
-