Files
Aislo/B01_Dashboard/B01_Dashboard_UI_MapPreview.ts
eomsangdonandClaude Opus 5 bca78ee6bd feat(B01): 회사 정보 수정 화면·중복 조회·주소 지도 미리보기
- 회사 정보 수정 API(PUT /dashboard/company) 및 수정 모달 신설, 회사 패널·회사 관리 표에 수정 진입점 추가
- 회사 등록·수정 입력칸 공용화 및 순서 조정 (사업자등록번호 > 회사명 > 대표자명 > 로고 > 주소)
- 회사 등록 시 사업자번호·회사명으로 기존 회사 조회, 있으면 가입 신청으로 유도
- 주소 지도 미리보기 추가 — VWorld 주소검색·배경지도 타일을 서버가 중계(키 노출 방지), 지도 라이브러리 없이 타일 3x3 조합

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 11:29:00 +09:00

73 lines
2.7 KiB
TypeScript

import { API_BASE_URL } from "@config/config_frontend";
import { geocodeAddress } from "./B01_Dashboard_Api_Fetch";
/**
* 주소 지도 미리보기 (2026-09-06 사용자 지시).
*
* 지도 라이브러리를 얹지 않는다 — 배경지도 타일 3×3 장을 붙이고 가운데에 표식만 찍는다.
* 등록·수정 화면에서 "이 주소가 여기 맞나" 를 눈으로 보는 것이 목적이다.
*/
const ZOOM = 15;
const TILE = 256;
const GRID = 3;
function tileIndex(lat: number, lon: number): { x: number; y: number; dx: number; dy: number } {
const n = 2 ** ZOOM;
const rad = (lat * Math.PI) / 180;
const fx = ((lon + 180) / 360) * n;
const fy = ((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2) * n;
return { x: Math.floor(fx), y: Math.floor(fy), dx: fx - Math.floor(fx), dy: fy - Math.floor(fy) };
}
export function buildAddressMap(): {
root: HTMLElement;
show: (address: string) => Promise<void>;
} {
const root = document.createElement("div");
root.className = "b01-dashboard__map";
const note = document.createElement("p");
note.className = "b01-dashboard__modal-text";
note.textContent = "주소를 넣고 「지도 확인」을 누르십시오.";
root.append(note);
const show = async (address: string): Promise<void> => {
root.innerHTML = "";
if (!address.trim()) {
note.textContent = "주소를 먼저 입력하십시오.";
root.append(note);
return;
}
const point = await geocodeAddress(address).catch(() => null);
if (!point) {
note.textContent = "그 주소를 찾지 못했습니다. 도로명 또는 지번 주소로 다시 넣으십시오.";
root.append(note);
return;
}
const center = tileIndex(point.lat, point.lon);
const grid = document.createElement("div");
grid.className = "b01-dashboard__map-grid";
const half = Math.floor(GRID / 2);
for (let row = -half; row <= half; row += 1) {
for (let col = -half; col <= half; col += 1) {
const img = document.createElement("img");
img.src = `${API_BASE_URL}/dashboard/map/tile/${ZOOM}/${center.x + col}/${center.y + row}`;
img.width = TILE;
img.height = TILE;
img.alt = "";
grid.append(img);
}
}
const marker = document.createElement("span");
marker.className = "b01-dashboard__map-marker";
// 타일 판은 CSS 에서 절반으로 줄여 붙이므로 표식 자리도 절반으로 잡는다.
marker.style.left = `${(half + center.dx) * TILE * 0.5}px`;
marker.style.top = `${(half + center.dy) * TILE * 0.5}px`;
const frame = document.createElement("div");
frame.className = "b01-dashboard__map-frame";
frame.append(grid, marker);
root.append(frame);
};
return { root, show };
}