Merge pull request 'feat(hns): HNS �м� �� UI ���� ? ���� ��Ʈ�� ��ġ ���� �� �м� ���� �������� ���Ǻ� ǥ��' (#195) from feature/hns-improvements into develop

This commit is contained in:
jhkang 2026-04-20 16:45:28 +09:00
커밋 1d5ec35c78
4개의 변경된 파일22개의 추가작업 그리고 18개의 파일을 삭제

파일 보기

@ -9,10 +9,12 @@
- 선박: 선박 검색 범위를 전체 캐시 대상으로 확대 - 선박: 선박 검색 범위를 전체 캐시 대상으로 확대
- HNS: 정보 레이어 패널 통합 (레이어 표시/불투명도/밝기/색상 제어) - HNS: 정보 레이어 패널 통합 (레이어 표시/불투명도/밝기/색상 제어)
- HNS: 분석 생성 시 유출량·단위·예측 시간·알고리즘·기준 모델 파라미터 전달 - HNS: 분석 생성 시 유출량·단위·예측 시간·알고리즘·기준 모델 파라미터 전달
- HNS/사건사고: 분석 전용 뷰 모드에서 지도 오버레이 UI 요소 조건부 숨김 처리
### 변경 ### 변경
- InfoLayerSection을 공통 컴포넌트로 이동 (prediction → common/layer) - InfoLayerSection을 공통 컴포넌트로 이동 (prediction → common/layer)
- 기상 탭: 지도 오버레이 컨트롤 위치 우측 상단으로 조정 - 기상 탭: 지도 오버레이 컨트롤 위치 우측 상단으로 조정
- 지도 공통: 컨트롤 버튼 패널(줌 등) 위치 우측 → 좌측으로 변경
### 수정 ### 수정
- 선박: 라우터 전체에 requireAuth 미들웨어 추가 - 선박: 라우터 전체에 requireAuth 미들웨어 추가

파일 보기

@ -139,8 +139,8 @@ function MapOverlayControls({
return ( return (
<> <>
{/* 측 컨트롤 컬럼 */} {/* 측 컨트롤 컬럼 */}
<div className="absolute top-[10px] right-[10px] z-10 flex flex-col gap-1"> <div className="absolute top-[10px] left-[10px] z-10 flex flex-col gap-1">
{/* 줌 */} {/* 줌 */}
<button title="줌 인" onClick={() => map?.zoomIn()} className={btn}> <button title="줌 인" onClick={() => map?.zoomIn()} className={btn}>
+ +

파일 보기

@ -183,6 +183,7 @@ export function IncidentsView() {
// Analysis view mode // Analysis view mode
const [viewMode, setViewMode] = useState<ViewMode>('overlay'); const [viewMode, setViewMode] = useState<ViewMode>('overlay');
const [analysisActive, setAnalysisActive] = useState(true); const [analysisActive, setAnalysisActive] = useState(true);
const isOverlayMode = !analysisActive || viewMode === 'overlay';
// 분할 뷰에서 사용할 체크된 원본 아이템들 (우측 패널에서 주입) // 분할 뷰에서 사용할 체크된 원본 아이템들 (우측 패널에서 주입)
const [checkedPredItems, setCheckedPredItems] = useState<PredictionAnalysis[]>([]); const [checkedPredItems, setCheckedPredItems] = useState<PredictionAnalysis[]>([]);
@ -797,6 +798,7 @@ export function IncidentsView() {
<BaseMap <BaseMap
center={[35.0, 127.8]} center={[35.0, 127.8]}
zoom={7} zoom={7}
showOverlays={isOverlayMode}
cursor={measureMode !== null || dischargeMode ? 'crosshair' : undefined} cursor={measureMode !== null || dischargeMode ? 'crosshair' : undefined}
onMapClick={(lon, lat) => { onMapClick={(lon, lat) => {
if (dischargeMode) { if (dischargeMode) {
@ -845,7 +847,7 @@ export function IncidentsView() {
</BaseMap> </BaseMap>
{/* 선박 검색 */} {/* 선박 검색 */}
{(allRealVessels.length > 0 || realVessels.length > 0) && !dischargeMode && measureMode === null && ( {isOverlayMode && (allRealVessels.length > 0 || realVessels.length > 0) && !dischargeMode && measureMode === null && (
<VesselSearchBar <VesselSearchBar
vessels={allRealVessels.length > 0 ? allRealVessels : realVessels} vessels={allRealVessels.length > 0 ? allRealVessels : realVessels}
onFlyTo={(v) => { onFlyTo={(v) => {
@ -856,7 +858,7 @@ export function IncidentsView() {
)} )}
{/* 호버 툴팁 */} {/* 호버 툴팁 */}
{hoverInfo && ( {isOverlayMode && hoverInfo && (
<div <div
className="absolute z-[1000] pointer-events-none rounded-md" className="absolute z-[1000] pointer-events-none rounded-md"
style={{ style={{
@ -878,7 +880,7 @@ export function IncidentsView() {
)} )}
{/* 오염물 배출 규정 토글 */} {/* 오염물 배출 규정 토글 */}
<button {isOverlayMode && <button
onClick={() => { onClick={() => {
setDischargeMode(!dischargeMode); setDischargeMode(!dischargeMode);
if (dischargeMode) setDischargeInfo(null); if (dischargeMode) setDischargeInfo(null);
@ -886,7 +888,7 @@ export function IncidentsView() {
className="absolute z-[500] cursor-pointer rounded-md text-caption font-bold font-korean" className="absolute z-[500] cursor-pointer rounded-md text-caption font-bold font-korean"
style={{ style={{
top: 10, top: 10,
right: 180, right: 230,
padding: '6px 10px', padding: '6px 10px',
background: 'var(--bg-base)', background: 'var(--bg-base)',
border: '1px solid var(--stroke-default)', border: '1px solid var(--stroke-default)',
@ -896,10 +898,10 @@ export function IncidentsView() {
}} }}
> >
{dischargeMode ? 'ON' : 'OFF'} {dischargeMode ? 'ON' : 'OFF'}
</button> </button>}
{/* 오염물 배출 규정 패널 */} {/* 오염물 배출 규정 패널 */}
{dischargeMode && dischargeInfo && ( {isOverlayMode && dischargeMode && dischargeInfo && (
<DischargeZonePanel <DischargeZonePanel
lat={dischargeInfo.lat} lat={dischargeInfo.lat}
lon={dischargeInfo.lon} lon={dischargeInfo.lon}
@ -910,7 +912,7 @@ export function IncidentsView() {
)} )}
{/* 배출규정 모드 안내 */} {/* 배출규정 모드 안내 */}
{dischargeMode && !dischargeInfo && ( {isOverlayMode && dischargeMode && !dischargeInfo && (
<div <div
className="absolute z-[500] rounded-md text-label-2 font-korean font-semibold" className="absolute z-[500] rounded-md text-label-2 font-korean font-semibold"
style={{ style={{
@ -930,7 +932,7 @@ export function IncidentsView() {
)} )}
{/* AIS Live Badge */} {/* AIS Live Badge */}
<div {isOverlayMode && <div
className="absolute top-[10px] right-[10px] z-[500] rounded-md" className="absolute top-[10px] right-[10px] z-[500] rounded-md"
style={{ style={{
background: 'var(--bg-base)', background: 'var(--bg-base)',
@ -956,11 +958,11 @@ export function IncidentsView() {
<div className="text-fg-sub"> {filteredIncidents.length}</div> <div className="text-fg-sub"> {filteredIncidents.length}</div>
<div className="text-fg-sub"> {vesselStatus?.bangjeCount ?? 0}</div> <div className="text-fg-sub"> {vesselStatus?.bangjeCount ?? 0}</div>
</div> </div>
</div> </div>}
{/* Legend */} {/* Legend */}
<div {isOverlayMode && <div
className="absolute bottom-[10px] left-[10px] z-[500] rounded-md flex flex-col gap-1.5" className="absolute bottom-[10px] right-[10px] z-[500] rounded-md flex flex-col gap-1.5"
style={{ style={{
background: 'var(--bg-base)', background: 'var(--bg-base)',
border: '1px solid var(--stroke-default)', border: '1px solid var(--stroke-default)',
@ -998,10 +1000,10 @@ export function IncidentsView() {
</div> </div>
))} ))}
</div> </div>
</div> </div>}
{/* 선박 팝업 패널 */} {/* 선박 팝업 패널 */}
{vesselPopup && selectedVessel && !detailVessel && ( {isOverlayMode && vesselPopup && selectedVessel && !detailVessel && (
<VesselPopupPanel <VesselPopupPanel
vessel={selectedVessel} vessel={selectedVessel}
onClose={() => { onClose={() => {
@ -1015,7 +1017,7 @@ export function IncidentsView() {
}} }}
/> />
)} )}
{detailVessel && ( {isOverlayMode && detailVessel && (
<VesselDetailModal vessel={detailVessel} onClose={() => setDetailVessel(null)} /> <VesselDetailModal vessel={detailVessel} onClose={() => setDetailVessel(null)} />
)} )}
</div> </div>

파일 보기

@ -324,7 +324,7 @@ export function WeatherView() {
</BaseMap> </BaseMap>
{/* 레이어 컨트롤 */} {/* 레이어 컨트롤 */}
<div className="absolute top-4 left-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5"> <div className="absolute top-4 right-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5">
<div className="text-caption font-semibold text-fg mb-1.5 font-korean"> </div> <div className="text-caption font-semibold text-fg mb-1.5 font-korean"> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<label className="flex items-center gap-1.5 cursor-pointer"> <label className="flex items-center gap-1.5 cursor-pointer">
@ -385,7 +385,7 @@ export function WeatherView() {
</div> </div>
{/* 범례 */} {/* 범례 */}
<div className="absolute bottom-4 left-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5 max-w-[180px]"> <div className="absolute bottom-4 right-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5 max-w-[180px]">
<div className="text-caption text-fg mb-1.5 font-korean"> </div> <div className="text-caption text-fg mb-1.5 font-korean"> </div>
<div className="flex flex-col gap-1.5 text-[8px]"> <div className="flex flex-col gap-1.5 text-[8px]">
{/* 바람 */} {/* 바람 */}