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>
195 lines
6.6 KiB
TypeScript
195 lines
6.6 KiB
TypeScript
import { useState, useMemo } from 'react'
|
|
import { LayerTree } from '@common/components/layer/LayerTree'
|
|
import { useLayerTree } from '@common/hooks/useLayers'
|
|
import { layerData } from '@common/data/layerData'
|
|
import type { LayerNode } from '@common/data/layerData'
|
|
import type { Layer } from '@common/services/layerService'
|
|
|
|
interface InfoLayerSectionProps {
|
|
expanded: boolean
|
|
onToggle: () => void
|
|
enabledLayers: Set<string>
|
|
onToggleLayer: (layerId: string, enabled: boolean) => void
|
|
layerOpacity: number
|
|
onLayerOpacityChange: (val: number) => void
|
|
layerBrightness: number
|
|
onLayerBrightnessChange: (val: number) => void
|
|
}
|
|
|
|
const InfoLayerSection = ({
|
|
expanded,
|
|
onToggle,
|
|
enabledLayers,
|
|
onToggleLayer,
|
|
layerOpacity,
|
|
onLayerOpacityChange,
|
|
layerBrightness,
|
|
onLayerBrightnessChange,
|
|
}: InfoLayerSectionProps) => {
|
|
// API에서 레이어 트리 데이터 가져오기
|
|
const { data: layerTree, isLoading } = useLayerTree()
|
|
|
|
const [layerColors, setLayerColors] = useState<Record<string, string>>({})
|
|
|
|
// 정적 데이터를 Layer 형식으로 변환 (API 실패 시 폴백)
|
|
const staticLayers = useMemo(() => {
|
|
const convert = (node: LayerNode): Layer => ({
|
|
id: node.code,
|
|
parentId: node.parentCode,
|
|
name: node.name,
|
|
fullName: node.fullName,
|
|
level: node.level,
|
|
wmsLayer: node.layerName,
|
|
icon: node.icon,
|
|
count: node.count,
|
|
children: node.children?.map(convert),
|
|
})
|
|
return layerData.map(convert)
|
|
}, [])
|
|
|
|
// API 데이터 우선, 실패 시 정적 데이터 폴백
|
|
const effectiveLayers = (layerTree && layerTree.length > 0) ? layerTree : staticLayers
|
|
|
|
return (
|
|
<div className="border-b border-border">
|
|
<div
|
|
className="flex items-center justify-between p-4 hover:bg-[rgba(255,255,255,0.02)]"
|
|
>
|
|
<h3
|
|
onClick={onToggle}
|
|
className="text-[13px] font-bold text-text-1 font-korean cursor-pointer"
|
|
>
|
|
📂 정보 레이어
|
|
</h3>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
// Get all layer IDs from layerTree recursively
|
|
const getAllLayerIds = (layers: Layer[]): string[] => {
|
|
const ids: string[] = []
|
|
layers?.forEach(layer => {
|
|
ids.push(layer.id)
|
|
if (layer.children) {
|
|
ids.push(...getAllLayerIds(layer.children))
|
|
}
|
|
})
|
|
return ids
|
|
}
|
|
const allIds = getAllLayerIds(effectiveLayers)
|
|
allIds.forEach(id => onToggleLayer(id, true))
|
|
}}
|
|
style={{
|
|
padding: '4px 8px',
|
|
fontSize: '10px',
|
|
fontWeight: 600,
|
|
border: '1px solid var(--cyan)',
|
|
borderRadius: 'var(--rS)',
|
|
background: 'transparent',
|
|
color: 'var(--cyan)',
|
|
cursor: 'pointer',
|
|
transition: '0.15s'
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = 'rgba(6,182,212,0.1)'
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'transparent'
|
|
}}
|
|
>
|
|
전체 켜기
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
// Get all layer IDs from layerTree recursively
|
|
const getAllLayerIds = (layers: Layer[]): string[] => {
|
|
const ids: string[] = []
|
|
layers?.forEach(layer => {
|
|
ids.push(layer.id)
|
|
if (layer.children) {
|
|
ids.push(...getAllLayerIds(layer.children))
|
|
}
|
|
})
|
|
return ids
|
|
}
|
|
const allIds = getAllLayerIds(effectiveLayers)
|
|
allIds.forEach(id => onToggleLayer(id, false))
|
|
}}
|
|
style={{
|
|
padding: '4px 8px',
|
|
fontSize: '10px',
|
|
fontWeight: 600,
|
|
border: '1px solid var(--red)',
|
|
borderRadius: 'var(--rS)',
|
|
background: 'transparent',
|
|
color: 'var(--red)',
|
|
cursor: 'pointer',
|
|
transition: '0.15s'
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = 'rgba(239,68,68,0.1)'
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'transparent'
|
|
}}
|
|
>
|
|
전체 끄기
|
|
</button>
|
|
<span
|
|
onClick={onToggle}
|
|
className="text-[10px] text-text-3 cursor-pointer"
|
|
>
|
|
{expanded ? '▼' : '▶'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{expanded && (
|
|
<div className="px-4 pb-2">
|
|
{isLoading && effectiveLayers.length === 0 ? (
|
|
<p className="text-[11px] text-text-3 py-2">레이어 로딩 중...</p>
|
|
) : effectiveLayers.length === 0 ? (
|
|
<p className="text-[11px] text-text-3 py-2">레이어 데이터가 없습니다.</p>
|
|
) : (
|
|
<LayerTree
|
|
layers={effectiveLayers}
|
|
enabledLayers={enabledLayers}
|
|
onToggleLayer={onToggleLayer}
|
|
layerColors={layerColors}
|
|
onColorChange={(id, color) => setLayerColors(prev => ({ ...prev, [id]: color }))}
|
|
/>
|
|
)}
|
|
|
|
{/* 레이어 스타일 조절 */}
|
|
<div className="lyr-style-box">
|
|
<div className="lyr-style-label">레이어 스타일</div>
|
|
<div className="lyr-style-row">
|
|
<span className="lyr-style-name">투명도</span>
|
|
<input
|
|
type="range"
|
|
className="lyr-style-slider"
|
|
min={0} max={100} value={layerOpacity}
|
|
onChange={e => onLayerOpacityChange(Number(e.target.value))}
|
|
/>
|
|
<span className="lyr-style-val">{layerOpacity}%</span>
|
|
</div>
|
|
<div className="lyr-style-row">
|
|
<span className="lyr-style-name">밝기</span>
|
|
<input
|
|
type="range"
|
|
className="lyr-style-slider"
|
|
min={0} max={100} value={layerBrightness}
|
|
onChange={e => onLayerBrightnessChange(Number(e.target.value))}
|
|
/>
|
|
<span className="lyr-style-val">{layerBrightness}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default InfoLayerSection
|