refactor(realestate): 상수·데이터·순수헬퍼를 realEstateUtils로 추출 + 테스트
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHXzpsZQxKG9hQmNRfZjRS
This commit is contained in:
141
src/pages/realestate/realEstateUtils.js
Normal file
141
src/pages/realestate/realEstateUtils.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import L from 'leaflet';
|
||||
|
||||
// ── 샘플 데이터 ────────────────────────────────────────────────────────────────
|
||||
export const SAMPLE_COMPLEXES = [
|
||||
{
|
||||
id: 1,
|
||||
name: '래미안 원베일리',
|
||||
address: '서울 서초구 반포동',
|
||||
lat: 37.5065,
|
||||
lng: 126.9942,
|
||||
units: 2990,
|
||||
types: ['59㎡', '84㎡', '114㎡'],
|
||||
avgPricePerPyeong: 9500,
|
||||
subscriptionStart: '2024-01-08',
|
||||
subscriptionEnd: '2024-01-10',
|
||||
resultDate: '2024-01-15',
|
||||
status: '완료',
|
||||
priority: 'high',
|
||||
tags: ['강남권', '한강뷰', '역세권', '브랜드'],
|
||||
naverUrl: '',
|
||||
floorPlanUrl: '',
|
||||
memo: '반포동 재건축 단지. 경쟁률 수백대 1 예상.',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '올림픽파크 포레온',
|
||||
address: '서울 강동구 둔촌동',
|
||||
lat: 37.5284,
|
||||
lng: 127.1340,
|
||||
units: 12032,
|
||||
types: ['39㎡', '49㎡', '59㎡', '84㎡'],
|
||||
avgPricePerPyeong: 3800,
|
||||
subscriptionStart: '2022-12-05',
|
||||
subscriptionEnd: '2022-12-07',
|
||||
resultDate: '2022-12-12',
|
||||
status: '완료',
|
||||
priority: 'high',
|
||||
tags: ['대단지', '역세권', '재건축'],
|
||||
naverUrl: '',
|
||||
floorPlanUrl: '',
|
||||
memo: '역대 최대 규모 재건축 단지.',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '힐스테이트 동탄',
|
||||
address: '경기 화성시 동탄2신도시',
|
||||
lat: 37.2001,
|
||||
lng: 127.0724,
|
||||
units: 1534,
|
||||
types: ['59㎡', '74㎡', '84㎡'],
|
||||
avgPricePerPyeong: 1850,
|
||||
subscriptionStart: '2026-04-10',
|
||||
subscriptionEnd: '2026-04-12',
|
||||
resultDate: '2026-04-17',
|
||||
status: '청약예정',
|
||||
priority: 'normal',
|
||||
tags: ['동탄2', '신도시', 'SRT'],
|
||||
naverUrl: '',
|
||||
floorPlanUrl: '',
|
||||
memo: '동탄 핵심 입지. 교통 개선 기대.',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '롯데캐슬 마곡',
|
||||
address: '서울 강서구 마곡동',
|
||||
lat: 37.5626,
|
||||
lng: 126.8295,
|
||||
units: 868,
|
||||
types: ['59㎡', '84㎡'],
|
||||
avgPricePerPyeong: 4200,
|
||||
subscriptionStart: '2026-03-20',
|
||||
subscriptionEnd: '2026-03-22',
|
||||
resultDate: '2026-03-27',
|
||||
status: '청약중',
|
||||
priority: 'high',
|
||||
tags: ['마곡', '9호선', '공항철도'],
|
||||
naverUrl: '',
|
||||
floorPlanUrl: '',
|
||||
memo: '마곡 업무지구 직주근접. 강서 핵심 입지.',
|
||||
},
|
||||
];
|
||||
|
||||
export const STATUS_CONFIG = {
|
||||
'청약예정': { color: '#00d4ff', bg: 'rgba(0,212,255,0.12)' },
|
||||
'청약중': { color: '#34d399', bg: 'rgba(52,211,153,0.12)' },
|
||||
'결과발표': { color: '#f59e0b', bg: 'rgba(245,158,11,0.12)' },
|
||||
'완료': { color: '#6b7280', bg: 'rgba(107,114,128,0.10)' },
|
||||
};
|
||||
|
||||
export const PRIORITY_LABELS = { high: '★ 최우선', normal: '보통', low: '낮음' };
|
||||
|
||||
export const EMPTY_FORM = {
|
||||
name: '', address: '', lat: '', lng: '',
|
||||
units: '', types: '', avgPricePerPyeong: '',
|
||||
subscriptionStart: '', subscriptionEnd: '', resultDate: '',
|
||||
status: '청약예정', priority: 'normal',
|
||||
tags: '', naverUrl: '', floorPlanUrl: '', memo: '',
|
||||
};
|
||||
|
||||
export const TABS = ['목록', '일정', '분석'];
|
||||
|
||||
// ── 유틸 함수 ──────────────────────────────────────────────────────────────────
|
||||
export const formatDate = (d) => {
|
||||
if (!d) return '-';
|
||||
return new Date(d).toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
};
|
||||
|
||||
export const formatPrice = (v) => {
|
||||
if (!v) return '-';
|
||||
return `${v.toLocaleString()}만원`;
|
||||
};
|
||||
|
||||
export const getDDays = (dateStr) => {
|
||||
if (!dateStr) return null;
|
||||
const target = new Date(dateStr);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
target.setHours(0, 0, 0, 0);
|
||||
const diff = Math.ceil((target - today) / (1000 * 60 * 60 * 24));
|
||||
if (diff === 0) return 'D-Day';
|
||||
if (diff > 0) return `D-${diff}`;
|
||||
return `D+${Math.abs(diff)}`;
|
||||
};
|
||||
|
||||
export const createMarkerIcon = (status, isSelected = false) => {
|
||||
const cfg = STATUS_CONFIG[status] || STATUS_CONFIG['완료'];
|
||||
const size = isSelected ? 18 : 12;
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
html: `<div style="
|
||||
width:${size}px;height:${size}px;border-radius:50%;
|
||||
background:${cfg.color};
|
||||
box-shadow:0 0 ${isSelected ? 16 : 8}px ${cfg.color};
|
||||
border:2px solid rgba(255,255,255,${isSelected ? 0.6 : 0.25});
|
||||
transition:all 0.2s;
|
||||
"></div>`,
|
||||
iconSize: [size, size],
|
||||
iconAnchor: [size / 2, size / 2],
|
||||
popupAnchor: [0, -(size / 2 + 4)],
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user