const { useState, useEffect, useCallback, useMemo, useRef } = React;

// ═══════════════════════════════════════════════════════
// ⚠️ 아래 2줄을 본인의 Supabase 정보로 교체하세요!
// ═══════════════════════════════════════════════════════
const SUPABASE_URL = "https://ytacvmzmbgqbfesiwdug.supabase.co";
const SUPABASE_KEY = "sb_publishable_yarYlcB7sD-NpKeWdsPPXA_bW8mtM8V";
// ═══════════════════════════════════════════════════════

// ── Supabase REST API Helper ──
const supabase = {
  getHeaders(prefer) {
    const h = {
      "apikey": SUPABASE_KEY,
      "Authorization": `Bearer ${SUPABASE_KEY}`,
      "Content-Type": "application/json",
    };
    if (prefer) h["Prefer"] = prefer;
    return h;
  },
  async fetch(table, params = "") {
    const url = `${SUPABASE_URL}/rest/v1/${table}?select=*&${params}`;
    const res = await window.fetch(url, { headers: this.getHeaders() });
    if (!res.ok) {
      const errText = await res.text();
      throw new Error(`Fetch error ${res.status}: ${errText}`);
    }
    return res.json();
  },
  async insert(table, data) {
    const res = await window.fetch(`${SUPABASE_URL}/rest/v1/${table}`, {
      method: "POST", headers: this.getHeaders("return=representation"), body: JSON.stringify(data),
    });
    if (!res.ok) { const e = await res.text(); throw new Error(e); }
    return res.json();
  },
  async update(table, id, data) {
    const res = await window.fetch(`${SUPABASE_URL}/rest/v1/${table}?id=eq.${id}`, {
      method: "PATCH", headers: this.getHeaders("return=representation"), body: JSON.stringify(data),
    });
    if (!res.ok) { const e = await res.text(); throw new Error(`Update error: ${e}`); }
    return res.json();
  },
  async remove(table, id) {
    const res = await window.fetch(`${SUPABASE_URL}/rest/v1/${table}?id=eq.${id}`, {
      method: "DELETE", headers: this.getHeaders(),
    });
    if (!res.ok) throw new Error(`Delete error: ${res.status}`);
    return true;
  },
  async updateByKey(table, keyCol, keyVal, data) {
    const res = await window.fetch(`${SUPABASE_URL}/rest/v1/${table}?${keyCol}=eq.${encodeURIComponent(keyVal)}`, {
      method: "PATCH", headers: this.getHeaders("return=representation"), body: JSON.stringify(data),
    });
    if (!res.ok) { const e = await res.text(); throw new Error(`UpdateByKey error: ${e}`); }
    return res.json();
  },
  async count(table, params = "") {
    const res = await window.fetch(`${SUPABASE_URL}/rest/v1/${table}?${params}&select=id`, {
      headers: { ...this.getHeaders("count=exact"), "Range-Unit": "items", "Range": "0-0" },
    });
    const cnt = res.headers.get("content-range");
    return cnt ? parseInt(cnt.split("/")[1]) : 0;
  },
  async uploadPhoto(file, path) {
    const res = await window.fetch(`${SUPABASE_URL}/storage/v1/object/delivery-photos/${path}`, {
      method: "POST",
      headers: {
        "apikey": SUPABASE_KEY,
        "Authorization": `Bearer ${SUPABASE_KEY}`,
        "Content-Type": file.type,
        "x-upsert": "true",
      },
      body: file,
    });
    if (!res.ok) throw new Error("Upload failed");
    return `${SUPABASE_URL}/storage/v1/object/public/delivery-photos/${path}`;
  },
};

// ── Route codes & Districts ──
const ROUTE_CODES = ["강남금융기관1","강남금융기관2","강남금융기관3","강남금융기관4","강남대로1","강남대로2","강남대로3","강남호텔1","강남호텔2","강북1","강북2","강북3","강북4","강북주상복합","강북호텔1","강북호텔2","강북호텔3","김포공항","남부순환로1","남부순환로2","대형타워","도산대로1","도산대로1-1","도산대로2","도산대로2-1","도산대로3","도산대로3-1","도산대로4","도산대로4-1","도산대로5","도산대로5-1","마포용산","목동","방배빌라","분당1","분당2","분당3","분당4","서초고급주거지","서초방배1","서초방배2","서초방배3","서초방배4","여의도","영동대로","영등포","용답/성수/건대","인천공항","잠실1","잠실2","청담빌라","테헤란로1","테헤란로2","테헤란로3","한남동"];
const GU_LIST = [
  "강남구","강동구","강북구","강서구","관악구","광진구","구로구","금천구",
  "노원구","도봉구","동대문구","동작구","마포구","서대문구","서초구","성동구",
  "성북구","송파구","양천구","영등포구","용산구","은평구","종로구","중구","중랑구",
  "수정구","중원구","분당구","수지구","기흥구","처인구","덕양구","일산동구","일산서구",
  "중구","남동구","부평구","계양구","서구","연수구"
];
const VEHICLES = ["A", "B", "C", "D"];

// ── TSP 경로 최적화 (Nearest-Neighbor + 2-opt) ──
function tspOptimize(coords, startCoord) {
  // coords: [{key, lat, lng}, ...], startCoord: {lat, lng}
  if (coords.length <= 1) return coords;
  
  // 거리 계산 (Haversine)
  function dist(a, b) {
    var R = 6371000;
    var dLat = (b.lat - a.lat) * Math.PI / 180;
    var dLng = (b.lng - a.lng) * Math.PI / 180;
    var x = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(a.lat*Math.PI/180) * Math.cos(b.lat*Math.PI/180) * Math.sin(dLng/2) * Math.sin(dLng/2);
    return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x));
  }
  
  // Nearest Neighbor
  var visited = new Array(coords.length).fill(false);
  var order = [];
  var cur = startCoord;
  for (var i = 0; i < coords.length; i++) {
    var best = -1, bestD = Infinity;
    for (var j = 0; j < coords.length; j++) {
      if (visited[j]) continue;
      var d = dist(cur, coords[j]);
      if (d < bestD) { bestD = d; best = j; }
    }
    visited[best] = true;
    order.push(best);
    cur = coords[best];
  }
  
  // 2-opt improvement (최대 50회)
  var route = order.map(function(idx) { return coords[idx]; });
  var improved = true;
  var iter = 0;
  while (improved && iter < 50) {
    improved = false; iter++;
    for (var i = 0; i < route.length - 1; i++) {
      for (var j = i + 2; j < route.length; j++) {
        var a = i === 0 ? startCoord : route[i-1];
        var d1 = dist(a, route[i]) + dist(route[j], j+1 < route.length ? route[j+1] : startCoord);
        var d2 = dist(a, route[j]) + dist(route[i], j+1 < route.length ? route[j+1] : startCoord);
        if (d2 < d1 - 0.1) {
          // reverse segment i..j
          var seg = route.slice(i, j+1).reverse();
          for (var k = 0; k < seg.length; k++) route[i+k] = seg[k];
          improved = true;
        }
      }
    }
  }
  
  // 총 거리 계산
  var totalDist = 0;
  var prev = startCoord;
  route.forEach(function(c) { totalDist += dist(prev, c); prev = c; });
  totalDist += dist(prev, startCoord); // 복귀
  
  return { route: route, totalDist: totalDist };
}

// 기본 출발지: 영동대교 북단
var DEFAULT_START = { lat: 37.5403, lng: 127.0483, name: "본부 (뚝섬로 338)" };


// ── Icon component ──
const Icon = ({ d, size = 20, color = "currentColor" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d={d}/></svg>
);
const I = {
  truck: "M1 3h15v13H1zM16 8h4l3 3v5h-7V8z",
  map: "M1 6v16l7-4 8 4 7-4V2l-7 4-8-4-7 4z",
  list: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01",
  camera: "M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z",
  nav: "M12 2L2 22l10-6 10 6L12 2z",
  plus: "M12 5v14M5 12h14",
  check: "M20 6L9 17l-5-5",
  x: "M18 6L6 18M6 6l12 12",
  search: "M11 3a8 8 0 100 16 8 8 0 000-16zM21 21l-4.35-4.35",
  pin: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z",
  external: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3",
  edit: "M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7",
  upload: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12",
  chevDown: "M6 9l6 6 6-6",
  chevRight: "M9 18l6-6-6-6",
  refresh: "M23 4v6h-6M1 20v-6h6M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15",
  loader: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83",
  route: "M9 18l6-6-6-6",
  arrowUp: "M12 19V5M5 12l7-7 7 7",
  arrowDown: "M12 5v14M19 12l-7 7-7-7",
  save: "M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2zM17 21v-8H7v8M7 3v5h8",
  chart: "M18 20V10M12 20V4M6 20v-6",
  mapView: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0zM12 13a3 3 0 100-6 3 3 0 000 6z",
  calendar: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V6a2 2 0 012-2z",
};

// ── 차량 기본값 (DB 로드 전 fallback) ──
const DEFAULT_VEHICLES = [
  { id: "A", num: "9467", name: "이원호" },
  { id: "B", num: "6723", name: "이종우" },
  { id: "C", num: "9468", name: "유대웅" },
  { id: "D", num: "7594", name: "임시직1" },
];
const VH_COLORS_GLOBAL = ["#0891B2","#D97706","#059669","#DB2777"];
const vcById = (id, vehicleInfo) => {
  const idx = ["A","B","C","D"].indexOf(id);
  return idx >= 0 ? VH_COLORS_GLOBAL[idx] : "#94A3B8";
};

// ── Theme (Light Mid) ──
const T = {
  bg: "#CBD5E1", sf: "#E2E8F0", card: "#F1F5F9", border: "#94A3B8",
  primary: "#3B82F6", accent: "#059669", warning: "#D97706", danger: "#DC2626",
  text: "#0F172A", muted: "#334155", dim: "#475569",
  vA: "#0891B2", vB: "#D97706", vC: "#059669", vD: "#DB2777",
};
const vc = v => v === "A" ? T.vA : v === "B" ? T.vB : v === "C" ? T.vC : v === "D" ? T.vD : T.dim;
const MQ_NORMALIZE = {
  "노블": "노블레스", "NOBLESSE": "노블레스", "noblesse": "노블레스",
  "MAN": "맨", "man": "맨", "MEN": "맨", "men": "맨",
  "ART": "아트", "art": "아트",
  "y": "Y",
  "워치&주얼리": "워치", "WATCH": "워치", "watch": "워치",
  "WEDDING": "웨딩", "wedding": "웨딩",
  "타임": "타임북", "TIMEBOOK": "타임북", "timebook": "타임북",
};
const safeMq = (mq) => {
  if (!mq) return {};
  if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { return {}; } }
  if (typeof mq !== "object" || Array.isArray(mq)) return {};
  // 키 정규화: 약어/영문 → 표준 한글명으로 통합
  const result = {};
  Object.entries(mq).forEach(([k, v]) => {
    const normalized = MQ_NORMALIZE[k] || k;
    result[normalized] = (result[normalized] || 0) + (typeof v === "number" ? v : 0);
  });
  return result;
};
const mqTotal = (mq) => Object.values(safeMq(mq)).reduce((s, v) => s + (typeof v === "number" ? v : 0), 0);

// ═══════════════════════════════════════
// Main App
// ═══════════════════════════════════════
function DeliveryApp() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  // ── 상태 저장/복원 (앱 닫혀도 유지) ──
  const ss = (k, v) => { try { localStorage.setItem("dm_" + k, JSON.stringify(v)); } catch(e) {} };
  const sl2 = (k, d) => { try { const v = localStorage.getItem("dm_" + k); return v ? JSON.parse(v) : d; } catch(e) { return d; } };

  const [tab, setTab] = useState(() => sl2("tab", "route"));
  const [vehicle, setVehicle] = useState(() => sl2("vehicle", "all"));
  const [resetMapKey, setResetMapKey] = useState(0);
  const [searchTerm, setSearchTerm] = useState("");
  const [filterCodes, setFilterCodes] = useState(() => sl2("filterCodes", []));
  const [filterGus, setFilterGus] = useState(() => sl2("filterGus", []));
  const [vehicleFilters, setVehicleFilters] = useState(() => sl2("vehicleFilters", { A: { codes: [], gus: [] }, B: { codes: [], gus: [] }, C: { codes: [], gus: [] }, D: { codes: [], gus: [] } }));
  const [openFilter, setOpenFilter] = useState(null);
  const [setupAssignVehicle, setSetupAssignVehicle] = useState(() => sl2("setupVehicle", null));
  const [filterGu, setFilterGu] = useState("all");
  const [page, setPage] = useState(0);

  // 상태 변경 시 자동 저장
  useEffect(() => { ss("tab", tab); }, [tab]);
  useEffect(() => { ss("vehicle", vehicle); }, [vehicle]);
  useEffect(() => { ss("filterCodes", filterCodes); }, [filterCodes]);
  useEffect(() => { ss("filterGus", filterGus); }, [filterGus]);
  useEffect(() => { ss("vehicleFilters", vehicleFilters); }, [vehicleFilters]);
  useEffect(() => { ss("setupVehicle", setupAssignVehicle); }, [setupAssignVehicle]);
  const [expandedRoute, setExpandedRoute] = useState(null);
  const [selectedStop, setSelectedStop] = useState(null);
  const [showAddForm, setShowAddForm] = useState(false);
  const [showBulkUpload, setShowBulkUpload] = useState(false);
  const [editingItem, setEditingItem] = useState(null);
  const [photos, setPhotos] = useState({});
  const [memos, setMemos] = useState({});
  const [photoViewer, setPhotoViewer] = useState(null);
  const [syncing, setSyncing] = useState(false);
  const [planVehicle, setPlanVehicle] = useState(() => sl2("planVehicle", "A"));
  useEffect(() => { ss("planVehicle", planVehicle); }, [planVehicle]);
  const [planItems, setPlanItems] = useState([]);
  const [planSaved, setPlanSaved] = useState(false);
  const [planFilterCode, setPlanFilterCode] = useState("all");
  const [deliveryDetail, setDeliveryDetail] = useState(null); // 기사용 업체 상세
  const [editMq, setEditMq] = useState({}); // 수량 임시 편집값
  const [editRejection, setEditRejection] = useState(null); // 거부/폐업 임시값
  const [editChanged, setEditChanged] = useState(false); // 변경 여부
  const [editName, setEditName] = useState(null); // 업장명 임시값
  const [editAddr, setEditAddr] = useState(null); // 주소 임시값
  const [editFloor, setEditFloor] = useState(null); // 층수 임시값
  const [quantities, setQuantities] = useState({}); // { addressId: { mag0: 0, mag1: 0, ... } }
  const [fieldMemos, setFieldMemos] = useState({}); // { addressId: "메모내용" }
  const [deliveryHistory, setDeliveryHistory] = useState([]);
  const [historyLoading, setHistoryLoading] = useState(false);
  const BASE_MAGS = ["노블레스", "맨", "아트", "Y", "워치", "웨딩", "타임북"];
  const [magNames, setMagNames] = useState(BASE_MAGS);
  const [extraMags, setExtraMags] = useState([]);
  const MAGAZINES = magNames;
  const [mapExtraMagGlobal, setMapExtraMagGlobal] = useState(null); // 지도 브랜드북 필터
  // MagMgmt 상태 - 부모에서 관리해야 탭 이동시 유지됨
  const [mgSearch, setMgSearch] = useState("");
  const [mgCodes, setMgCodes] = useState([]);
  const [mgBulkMag, setMgBulkMag] = useState("");
  const [mgBulkQty, setMgBulkQty] = useState(1);
  const [mgBulkItems, setMgBulkItems] = useState([]);
  const [mgBulkLoading, setMgBulkLoading] = useState(false);
  const [mgBulkResult, setMgBulkResult] = useState("");
  const [mgNewMag, setMgNewMag] = useState("");
  const [expandedBuilding, setExpandedBuilding] = useState(null); // 펼친 건물 주소
  const [buildingView, setBuildingView] = useState(null); // 기사 건물 상세 화면
  const planScrollRef = useRef(0); // 건물 목록 스크롤 위치 저장
  const buildingScrollRef = useRef(0); // 건물 상세 스크롤 위치 저장
  const [lastVisitedAddr, setLastVisitedAddr] = useState(null); // 마지막 방문 건물
  const [lastVisitedItem, setLastVisitedItem] = useState(null); // 마지막 방문 업장
  const [setupBuildingView, setSetupBuildingView] = useState(null); // 본부 건물 상세 화면
  const [setupItemView, setSetupItemView] = useState(null); // 본부 업장 수량 설정 화면
  const [hqUnlocked, setHqUnlocked] = useState(false);
  const [hqPwInput, setHqPwInput] = useState("");
  const [hqSubTab, setHqSubTab] = useState("vehicles");
  const [adminPassword, setAdminPassword] = useState(null);
  const [mapUnlocked, setMapUnlocked] = useState(false);
  const [mapPwInput, setMapPwInput] = useState("");
  const [scheduleUnlocked, setScheduleUnlocked] = useState(false);
  const [vehicleInfo, setVehicleInfo] = useState(DEFAULT_VEHICLES);
  const [hqDateFrom, setHqDateFrom] = useState(new Date().toISOString().split("T")[0]);
  const [hqDateTo, setHqDateTo] = useState(new Date().toISOString().split("T")[0]);
  const [hqData, setHqData] = useState([]);
  const [hqDetailItem, setHqDetailItem] = useState(null); // 배송내역 상세 보기
  const [photoZoom, setPhotoZoom] = useState(1);
  const [photoPos, setPhotoPos] = useState({ x: 0, y: 0 });
  const photoLastTap = useRef(0);
  const photoDragStart = useRef(null);
  const [hqLoading, setHqLoading] = useState(false);
  const [hqVehicle, setHqVehicle] = useState("all");
  const [vehicleTimes, setVehicleTimes] = useState({
    A: { depart: "09:00", arrive: "18:00" },
    B: { depart: "09:00", arrive: "18:00" },
    C: { depart: "09:00", arrive: "18:00" },
    D: { depart: "09:00", arrive: "18:00" },
  });
  const PAGE_SIZE = 200;

  // ── 전체 데이터 (모든 탭 공유 - 단일 진실의 원천) ──
  const [allData, setAllData] = useState([]);
  const [allDataReady, setAllDataReady] = useState(false);
  const allDataRef = useRef([]);
  useEffect(() => { allDataRef.current = allData; }, [allData]);

  const allDataLoadingRef = useRef(false);
  const loadAllData = useCallback(async () => {
    if (allDataLoadingRef.current) return;
    allDataLoadingRef.current = true;
    setAllDataReady(false);
    setLoading(true);
    setError(null);
    try {
      var acc = [];
      const fetchPage = async (offset) => {
        const data = await supabase.fetch("addresses", "is_active=eq.true&order=id.asc&offset=" + offset + "&limit=1000");
        const safe = data.map(a => {
          let mq = a.magazine_qty || {};
          if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
          return { ...a, magazine_qty: mq };
        });
        acc = acc.concat(safe);
        if (data.length === 1000) await fetchPage(offset + 1000);
      };
      await fetchPage(0);
      setAllData(acc);
      allDataRef.current = acc;
      // 사진 로드 (실패해도 무시)
      try {
        const photoData = await supabase.fetch("photos", "order=id.desc");
        const photoMap = {};
        photoData.forEach(p => {
          if (!photoMap[p.address_id]) photoMap[p.address_id] = [];
          photoMap[p.address_id].push({ id: p.id, url: p.photo_url, time: p.created_at ? new Date(p.created_at).toLocaleString("ko-KR") : "", memo: p.memo || "" });
        });
        setPhotos(photoMap);
      } catch(e) { console.log("Photos load skipped:", e.message); }
      // 차량별 시간 불러오기 (실패해도 무시)
      try {
        const today = new Date().toISOString().split("T")[0];
        const routeData = await supabase.fetch("routes", `route_date=eq.${today}`);
        if (routeData.length > 0) {
          setVehicleTimes(prev => {
            const newTimes = { ...prev };
            routeData.forEach(r => {
              try {
                const rd = typeof r.route_data === "string" ? JSON.parse(r.route_data) : r.route_data;
                if (rd && rd.depart && rd.arrive && r.vehicle) {
                  newTimes[r.vehicle] = { depart: rd.depart, arrive: rd.arrive };
                }
              } catch(e) {}
            });
            return newTimes;
          });
        }
      } catch(e) { console.log("Routes load skipped:", e.message); }
    } catch(e) {
      setError(e.message);
    } finally {
      setAllDataReady(true);
      allDataLoadingRef.current = false;
      setLoading(false);
    }
  }, []);

  useEffect(() => { loadAllData(); }, [loadAllData]);

  // ── allData 파생 계산 (단일 상태에서 모두 계산) ──
  const vehicleCounts = useMemo(() => {
    const c = { A: 0, B: 0, C: 0, D: 0, none: 0 };
    allData.forEach(a => { if (a.vehicle && c[a.vehicle] !== undefined) c[a.vehicle]++; else if (!a.vehicle) c.none++; });
    return c;
  }, [allData]);

  const vehicleMqTotals = useMemo(() => {
    const m = { A: 0, B: 0, C: 0, D: 0 };
    allData.forEach(a => { if (a.vehicle && m[a.vehicle] !== undefined) m[a.vehicle] += mqTotal(a.magazine_qty); });
    return m;
  }, [allData]);

  const vehicleDoneCounts = useMemo(() => {
    const c = { A: 0, B: 0, C: 0, D: 0 };
    allData.forEach(a => { if (a.vehicle && c[a.vehicle] !== undefined && a.status === "done") c[a.vehicle]++; });
    return c;
  }, [allData]);

  const vehicleDoneMqTotals = useMemo(() => {
    const m = { A: 0, B: 0, C: 0, D: 0 };
    allData.forEach(a => { if (a.vehicle && m[a.vehicle] !== undefined && a.status === "done") m[a.vehicle] += mqTotal(a.magazine_qty); });
    return m;
  }, [allData]);

  // HQ 탭 필터 + 페이지네이션 적용된 addresses (본부 설정 탭 전용)
  const addresses = useMemo(() => {
    let d = allData;
    if (tab === "hq") {
      if (filterCodes.length > 0) d = d.filter(a => filterCodes.includes(a.route_code));
      if (filterGus.length > 0) d = d.filter(a => filterGus.includes(a.gu));
      else if (filterGu !== "all") d = d.filter(a => a.gu === filterGu);
      if (searchTerm) {
        const s = searchTerm.toLowerCase();
        d = d.filter(a => (a.name||"").toLowerCase().includes(s) || (a.road_address||"").toLowerCase().includes(s) || (a.dong||"").toLowerCase().includes(s));
      }
    }
    return d.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
  }, [allData, tab, filterCodes, filterGus, filterGu, searchTerm, page]);

  const totalCount = allData.length;

  // 호차 배정 목록 (setupAssignVehicle 기반)
  const assignedItems = useMemo(() => {
    if (!setupAssignVehicle) return [];
    return allData
      .filter(a => a.vehicle === setupAssignVehicle)
      .sort((a, b) => (a.sort_order || 9999) - (b.sort_order || 9999));
  }, [allData, setupAssignVehicle]);

  // ── DB에서 관리자 비밀번호 로드 ──
  useEffect(() => {
    // 추가 잡지 로드
    supabase.fetch("settings", "key=eq.extra_mags").then(data => {
      if (data && data[0] && data[0].value) {
        try {
          const em = JSON.parse(data[0].value);
          if (Array.isArray(em) && em.length > 0) {
            setExtraMags(em);
            setMagNames(["노블레스","맨","아트","Y","워치","웨딩","타임북"].concat(em));
          }
        } catch(e) {}
      }
    });
    supabase.fetch("settings", "key=eq.admin_password").then(data => {
      if (data && data.length > 0) setAdminPassword(data[0].value);
      else setAdminPassword("0320");
    }).catch(() => setAdminPassword("0320"));
    supabase.fetch("settings", "key=eq.vehicles").then(data => {
      if (data && data[0] && data[0].value) {
        try {
          const v = JSON.parse(data[0].value);
          if (Array.isArray(v) && v.length === 4) setVehicleInfo(v);
        } catch(e) {}
      }
    }).catch(() => {});
  }, []);

  // ── 모바일 뒤로가기 처리 ──
  const backStateRef = useRef({});
  useEffect(() => {
    backStateRef.current = { setupItemView, setupBuildingView, selectedStop, deliveryDetail, photoViewer, hqDetailItem };
  });
  useEffect(() => {
    for (let i = 0; i < 5; i++) window.history.pushState({ app: true }, "");
    const handleBack = () => {
      const s = backStateRef.current;
      if (s.photoViewer) { setPhotoViewer(null); }
      else if (s.hqDetailItem) { setHqDetailItem(null); }
      else if (s.setupItemView) { setSetupItemView(null); }
      else if (s.setupBuildingView) { setSetupBuildingView(null); }
      else if (s.selectedStop || s.deliveryDetail) { setSelectedStop(null); setDeliveryDetail(null); }
      for (let i = 0; i < 3; i++) window.history.pushState({ app: true }, "");
    };
    window.addEventListener("popstate", handleBack);
    return () => window.removeEventListener("popstate", handleBack);
  }, []);

  // ── Stats ──
  const stats = useMemo(() => {
    const done = allData.filter(a => a.status === "done").length;
    const progress = allData.filter(a => a.status === "progress").length;
    return { total: allData.length, done, progress, page: addresses.length };
  }, [allData, addresses]);

  // ── Route groups ──
  const routeGroups = useMemo(() => {
    const g = {};
    addresses.forEach(a => {
      const k = a.route_code || "미분류";
      if (!g[k]) g[k] = [];
      g[k].push(a);
    });
    // 각 그룹 내에서 sort_order로 정렬
    Object.values(g).forEach(items => items.sort((a, b) => {
      if (a.sort_order != null && b.sort_order != null) return a.sort_order - b.sort_order;
      if (a.sort_order != null) return -1;
      if (b.sort_order != null) return 1;
      return a.id - b.id;
    }));
    return Object.entries(g).sort((a, b) => b[1].length - a[1].length);
  }, [addresses]);

  // ── Handlers ──
  const handleStatusToggle = async (item) => {
    const next = item.status === "pending" ? "progress" : item.status === "progress" ? "done" : "pending";
    try {
      await supabase.update("addresses", item.id, { status: next });
      setAllData(prev => prev.map(a => a.id === item.id ? { ...a, status: next } : a));
    } catch (e) { alert("상태 변경 실패: " + e.message); }
  };

  const handleSave = async (formData) => {
    try {
      setSyncing(true);
      if (editingItem) {
        await supabase.update("addresses", editingItem.id, formData);
        setAllData(prev => prev.map(a => a.id === editingItem.id ? { ...a, ...formData } : a));
        setEditingItem(null);
      } else {
        const [created] = await supabase.insert("addresses", formData);
        setAllData(prev => [...prev, created]);
        setShowAddForm(false);
      }
    } catch (e) { alert("저장 실패: " + e.message); }
    finally { setSyncing(false); }
  };

  const handleDelete = async (id) => {
    if (!confirm("이 주소를 삭제하시겠습니까?")) return;
    try {
      await supabase.update("addresses", id, { is_active: false });
      setAllData(prev => prev.filter(a => a.id !== id));
    } catch (e) { alert("삭제 실패: " + e.message); }
  };

  const handleBulkUpload = async (text) => {
    const lines = text.trim().split("\n").filter(l => l.trim());
    const items = lines.map(line => {
      const p = line.split(/[,\t]/).map(s => s.trim());
      return { name: p[0]||"", road_address: p[1]||"", gu: p[2]||"", dong: p[3]||"", route_code: p[4]||"기타", vehicle: p[5]||"A", status: "pending", is_active: true };
    });
    try {
      setSyncing(true);
      const created = await supabase.insert("addresses", items);
      setAllData(prev => [...prev, ...created]);
      setShowBulkUpload(false);
      alert(`${created.length}건 추가 완료!`);
    } catch (e) { alert("일괄 추가 실패: " + e.message); }
    finally { setSyncing(false); }
  };

  const handlePhotoCapture = (id) => {
    const input = document.createElement("input");
    input.type = "file"; input.accept = "image/*"; input.capture = "environment"; input.multiple = true;
    input.onchange = async (e) => {
      for (const file of e.target.files) {
        // base64로 변환
        const reader = new FileReader();
        reader.onload = async (ev) => {
          const base64url = ev.target.result;
          const now = new Date().toLocaleString("ko-KR");
          // DB 저장 시도
          try {
            const [saved] = await supabase.insert("photos", { address_id: id, photo_url: base64url, memo: "" });
            setPhotos(prev => ({ ...prev, [id]: [...(prev[id] || []), { id: saved.id, url: base64url, time: now, memo: "" }] }));
          } catch (dbErr) {
            // DB 실패해도 화면에는 표시
            setPhotos(prev => ({ ...prev, [id]: [...(prev[id] || []), { id: null, url: base64url, time: now, memo: "" }] }));
            console.log("Photo DB save failed:", dbErr.message);
          }
        };
        reader.readAsDataURL(file);
      }
    };
    input.click();
  };

  const handleDeletePhoto = async (addressId, photoId, photoIndex) => {
    if (!confirm("이 사진을 삭제하시겠습니까?")) return;
    try {
      if (photoId) await supabase.remove("photos", photoId);
      setPhotos(prev => ({
        ...prev,
        [addressId]: (prev[addressId] || []).filter((_, i) => i !== photoIndex),
      }));
    } catch (err) {
      alert("사진 삭제 실패: " + err.message);
    }
  };

  const openNavi = (address, name, type = "naver") => {
    const q = encodeURIComponent(`서울시 ${address}`);
    if (type === "kakao") window.open(`https://map.kakao.com/?q=${q}`, "_blank");
    else if (type === "naver") window.open(`https://map.naver.com/v5/search/${q}`, "_blank");
    else window.open(`https://tmap.life/search?q=${q}`, "_blank");
  };

  const handleChangeVehicle = async (item, newVehicle) => {
    try {
      await supabase.update("addresses", item.id, { vehicle: newVehicle, sort_order: null });
      setAllData(prev => prev.map(a => a.id === item.id ? { ...a, vehicle: newVehicle, sort_order: null } : a));
      if (deliveryDetail?.id === item.id) setDeliveryDetail(null);
    } catch (e) { alert("차량 변경 실패: " + e.message); }
  };

  const handleRemoveFromPlan = async (item) => {
    if (!confirm(`"${item.name}"을(를) ${planVehicle}호차 동선에서 제외하시겠습니까?`)) return;
    try {
      await supabase.update("addresses", item.id, { vehicle: "", sort_order: null });
      setAllData(prev => prev.map(a => a.id === item.id ? { ...a, vehicle: "", sort_order: null } : a));
      if (deliveryDetail?.id === item.id) setDeliveryDetail(null);
    } catch (e) { alert("제외 실패: " + e.message); }
  };

  // 잡지 수량 변경
  const handleQuantityChange = (addressId, magIdx, delta) => {
    setQuantities(prev => {
      const cur = prev[addressId] || {};
      const key = `mag${magIdx}`;
      const val = Math.max(0, (cur[key] || 0) + delta);
      return { ...prev, [addressId]: { ...cur, [key]: val } };
    });
  };

  // 잡지 수량 직접 입력
  const handleQuantityDirect = (addressId, magIdx, value) => {
    const num = parseInt(value) || 0;
    setQuantities(prev => {
      const cur = prev[addressId] || {};
      return { ...prev, [addressId]: { ...cur, [`mag${magIdx}`]: Math.max(0, num) } };
    });
  };

  // 현장 메모 변경
  const handleFieldMemo = (addressId, text) => {
    setFieldMemos(prev => ({ ...prev, [addressId]: text }));
  };

  // ── 일일 배송 마감 (완전 초기화) ──
  const handleDailyReset = async () => {
    if (!confirm("오늘 배송을 마감하시겠습니까?\n\n🔄 전체 업장 상태 → 대기\n🔄 호차 배정 → 초기화\n🔄 잡지 수량 → 0\n✅ 배송 기록(리포트)은 유지됩니다\n\n내일 새로운 스케줄을 입력하세요.")) return;
    if (!confirm("정말 마감하시겠습니까? 되돌릴 수 없습니다!")) return;
    try {
      setSyncing(true);
      // 전체 active 주소 페이지네이션으로 로드
      let allAddrs = [];
      let offset = 0;
      while (true) {
        const chunk = await supabase.fetch("addresses", `is_active=eq.true&select=id&order=id.asc&offset=${offset}&limit=1000`);
        allAddrs = allAddrs.concat(chunk);
        if (chunk.length < 1000) break;
        offset += 1000;
      }
      // 50건씩 일괄 초기화
      for (let i = 0; i < allAddrs.length; i += 50) {
        const chunk = allAddrs.slice(i, i + 50);
        await Promise.all(chunk.map(a =>
          supabase.update("addresses", a.id, { status: "pending", vehicle: null, sort_order: null })
        ));
      }
      // 로컬 상태 초기화 (allData 단일 업데이트)
      setAllData(prev => prev.map(a => ({ ...a, status: "pending", vehicle: null, sort_order: null })));
      setSetupAssignVehicle(null);
      setFilterCodes([]);
      setFilterGus([]);
      setVehicleFilters({ A: { codes: [], gus: [] }, B: { codes: [], gus: [] }, C: { codes: [], gus: [] }, D: { codes: [], gus: [] } });
      setHqData([]);
      setHqDetailItem(null);
      ["filterCodes", "filterGus", "vehicleFilters", "setupVehicle", "planVehicle", "zoneColorMap"].forEach(k => { try { localStorage.removeItem("dm_" + k); } catch(e) {} });
      allDataLoadingRef.current = false;
      await loadAllData();
      setResetMapKey(prev => prev + 1);
      alert(`배송 마감 완료! 🚛 (${allAddrs.length}건 초기화)\n내일 새로운 스케줄을 설정하세요.`);
    } catch(e) { alert("리셋 실패: " + e.message); }
    finally { setSyncing(false); }
  };

  const handleDeliveryComplete = async (item) => {
    // 이미 완료된 건이면 취소
    if (item.status === "done") {
      try {
        setSyncing(true);
        try {
          const records = await supabase.fetch("deliveries", `address_id=eq.${item.id}&order=id.desc&limit=1`);
          if (records.length > 0) await supabase.remove("deliveries", records[0].id);
        } catch(e) { console.log("Delivery delete:", e.message); }
        await supabase.update("addresses", item.id, { status: "pending" });
        setAllData(prev => prev.map(a => a.id === item.id ? { ...a, status: "pending" } : a));
        setDeliveryDetail(prev => prev ? { ...prev, status: "pending" } : null);
      } catch (e) { alert("취소 실패: " + e.message); }
      finally { setSyncing(false); }
      return;
    }
    // 배송완료 처리
    try {
      setSyncing(true);
      const qty = quantities[item.id] || {};
      const memo = fieldMemos[item.id] || "";
      const mq = item.magazine_qty || {};
      var fallbackQty = {};
      MAGAZINES.forEach(function(_, i) { fallbackQty["mag" + i] = mq["mag" + i] || 0; });
      const finalQty = Object.keys(qty).length > 0 ? qty : fallbackQty;
      try {
        const today = new Date().toISOString().split("T")[0];
        const existing = await supabase.fetch("deliveries", `address_id=eq.${item.id}&delivery_date=eq.${today}&limit=1`);
        const deliveryData = JSON.stringify({ quantities: finalQty, memo, magazines: MAGAZINES });
        if (existing.length > 0) {
          await supabase.update("deliveries", existing[0].id, { vehicle: item.vehicle, status: "done", delivery_data: deliveryData });
        } else {
          await supabase.insert("deliveries", { address_id: item.id, vehicle: item.vehicle, status: "done", delivery_data: deliveryData });
        }
      } catch(e) { console.log("Delivery record save:", e.message); }
      await supabase.update("addresses", item.id, { status: "done", memo });
      setAllData(prev => prev.map(a => a.id === item.id ? { ...a, status: "done", memo } : a));
      setDeliveryDetail(prev => prev ? { ...prev, status: "done" } : null);
    } catch (e) { alert("저장 실패: " + e.message); }
    finally { setSyncing(false); }
  };

  // 업체 상세 열기
  const openDeliveryDetail = async (item) => {
    setDeliveryDetail(item);
    setEditMq(item.magazine_qty || {});
    setEditRejection(item.rejection_status || null);
    setEditChanged(false);
    setEditName(null);
    setEditAddr(null);
    setEditFloor(null);
    if (item.memo && !fieldMemos[item.id]) {
      setFieldMemos(prev => ({ ...prev, [item.id]: item.memo }));
    }
    // 배송 이력 불러오기
    setDeliveryHistory([]);
    setHistoryLoading(true);
    try {
      const records = await supabase.fetch("deliveries", `address_id=eq.${item.id}&order=delivery_date.desc,id.desc&limit=30`);
      const parsed = records.map(r => {
        let dd = {};
        try { dd = typeof r.delivery_data === "string" ? JSON.parse(r.delivery_data) : (r.delivery_data || {}); } catch(e) {}
        return { ...r, parsed: dd };
      });
      setDeliveryHistory(parsed);
    } catch(e) { console.log("History load:", e.message); }
    finally { setHistoryLoading(false); }
  };

  // ── 본부 기능 ──
  const loadHqData = async () => {
    try {
      setHqLoading(true);
      let params = `delivery_date=gte.${hqDateFrom}&delivery_date=lte.${hqDateTo}&order=delivery_date.asc,vehicle.asc`;
      if (hqVehicle !== "all") params += `&vehicle=eq.${hqVehicle}`;
      let deliveries = [];
      try { deliveries = await supabase.fetch("deliveries", params); } catch(e) { deliveries = []; }
      if (!Array.isArray(deliveries)) deliveries = [];
      const addrIds = [...new Set(deliveries.map(d => d.address_id).filter(Boolean))];
      let addrMap = {};
      if (addrIds.length > 0) {
        for (let i = 0; i < addrIds.length; i += 50) {
          const chunk = addrIds.slice(i, i + 50);
          try {
            const addrs = await supabase.fetch("addresses", `id=in.(${chunk.join(",")})`);
            addrs.forEach(a => { addrMap[a.id] = a; });
          } catch(e) {}
        }
      }
      const combined = deliveries.map(d => {
        const addr = addrMap[d.address_id] || {};
        let dd = {};
        try { dd = typeof d.delivery_data === "string" ? JSON.parse(d.delivery_data) : (d.delivery_data || {}); } catch(e) {}
        return { ...d, addr, parsed: dd };
      });
      setHqData(combined);
    } catch (e) { alert("조회 실패: " + e.message); }
    finally { setHqLoading(false); }
  };

  const downloadExcel = () => {
    if (hqData.length === 0) { alert("조회된 데이터가 없습니다."); return; }
    const XLSX = window.XLSX;
    if (!XLSX) { alert("엑셀 라이브러리 로딩중... 잠시 후 다시 시도해주세요."); return; }
    const wb = XLSX.utils.book_new();

    // 시트1: 일별 배송 내역
    const sheet1Data = hqData.map(d => {
      const qty = d.parsed.quantities || {};
      const mags = d.parsed.magazines || MAGAZINES;
      const row = { "날짜": d.delivery_date, "차량": d.vehicle, "업체명": d.addr.name || "", "주소": d.addr.road_address || "", "구": d.addr.gu || "", "배송코드": d.addr.route_code || "", "상태": d.status };
      mags.forEach((m, i) => { row[m] = qty[`mag${i}`] || 0; });
      row["합계"] = Object.values(qty).reduce((s, v) => s + (v || 0), 0);
      row["메모"] = d.parsed.memo || d.memo || "";
      return row;
    });
    XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(sheet1Data), "배송내역");

    // 시트2: 업체별 합계
    const byAddr = {};
    hqData.forEach(d => {
      const key = d.address_id;
      if (!byAddr[key]) byAddr[key] = { name: d.addr.name || "", address: d.addr.road_address || "", gu: d.addr.gu || "", code: d.addr.route_code || "", count: 0, qty: {}, memos: [] };
      byAddr[key].count++;
      const qty = d.parsed.quantities || {};
      Object.entries(qty).forEach(([k, v]) => { byAddr[key].qty[k] = (byAddr[key].qty[k] || 0) + (v || 0); });
      if (d.parsed.memo) byAddr[key].memos.push(d.parsed.memo);
    });
    const sheet2Data = Object.values(byAddr).map(a => {
      const row = { "업체명": a.name, "주소": a.address, "구": a.gu, "배송코드": a.code, "배송횟수": a.count };
      MAGAZINES.forEach((m, i) => { row[m] = a.qty[`mag${i}`] || 0; });
      row["합계"] = Object.values(a.qty).reduce((s, v) => s + v, 0);
      row["메모"] = a.memos.join(" / ");
      return row;
    });
    XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(sheet2Data), "업체별합계");

    // 시트3: 잡지별 총 수량
    const magTotals = {};
    const vmt = { A: {}, B: {}, C: {}, D: {} };
    hqData.forEach(d => {
      const qty = d.parsed.quantities || {};
      Object.entries(qty).forEach(([k, v]) => {
        magTotals[k] = (magTotals[k] || 0) + (v || 0);
        if (vmt[d.vehicle]) vmt[d.vehicle][k] = (vmt[d.vehicle][k] || 0) + (v || 0);
      });
    });
    const sheet3Data = MAGAZINES.map((m, i) => {
      const k = `mag${i}`;
      return { "잡지명": m, "A호차": vmt.A[k] || 0, "B호차": vmt.B[k] || 0, "C호차": vmt.C[k] || 0, "D호차": vmt.D[k] || 0, "전체합계": magTotals[k] || 0 };
    });
    sheet3Data.push({ "잡지명": "총합계", "A호차": Object.values(vmt.A).reduce((s,v)=>s+v,0), "B호차": Object.values(vmt.B).reduce((s,v)=>s+v,0), "C호차": Object.values(vmt.C).reduce((s,v)=>s+v,0), "D호차": Object.values(vmt.D).reduce((s,v)=>s+v,0), "전체합계": Object.values(magTotals).reduce((s,v)=>s+v,0) });
    XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(sheet3Data), "잡지별수량");

    // 시트4: 변동 업체
    const sheet4Data = Object.values(byAddr).filter(a => a.memos.length > 0).map(a => ({
      "업체명": a.name, "주소": a.address, "구": a.gu, "메모": a.memos.join(" / "),
    }));
    XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(sheet4Data.length > 0 ? sheet4Data : [{ "업체명": "변동사항 없음" }]), "변동업체");

    XLSX.writeFile(wb, `배송리포트_${hqDateFrom}_${hqDateTo}.xlsx`);
  };

  // ── Status helpers ──
  const sc = (s) => s === "done" ? T.accent : s === "progress" ? T.warning : T.dim;
  const sl = (s) => s === "done" ? "완료" : s === "progress" ? "진행중" : "대기";

  // ── Plan helpers ──
  // planItems: allData에서 파생 (수동 재정렬 시에만 local override)
  const loadPlan = useCallback((v) => {
    const items = allData.filter(a => a.vehicle === v);
    const filtered = planFilterCode === "all" ? items : items.filter(a => a.route_code === planFilterCode);
    const sorted = [...filtered].sort((a, b) => {
      const aD = a.status === "done" ? 1 : 0;
      const bD = b.status === "done" ? 1 : 0;
      if (aD !== bD) return aD - bD;
      if (a.sort_order != null && b.sort_order != null) return a.sort_order - b.sort_order;
      if (a.sort_order != null) return -1;
      if (b.sort_order != null) return 1;
      return (a.route_code || "").localeCompare(b.route_code || "") || a.id - b.id;
    });
    setPlanItems(sorted);
    setPlanSaved(false);
  }, [allData, planFilterCode]);

  useEffect(() => { if (tab === "plan") loadPlan(planVehicle); }, [tab, planVehicle, loadPlan]);

  // allData 변경 시 planItems의 status/mq/rejection_status만 실시간 반영 + 완료 항목 아래로
  useEffect(() => {
    if (planItems.length === 0) return;
    setPlanItems(prev => {
      const updated = prev.map(p => {
        const u = allData.find(a => a.id === p.id);
        if (!u) return p;
        if (u.status !== p.status || u.memo !== p.memo ||
            JSON.stringify(u.magazine_qty) !== JSON.stringify(p.magazine_qty) ||
            u.rejection_status !== p.rejection_status) {
          return { ...p, status: u.status, memo: u.memo, magazine_qty: u.magazine_qty, rejection_status: u.rejection_status };
        }
        return p;
      });
      // 완료 항목 아래로 정렬 (미완료 순서는 유지)
      const notDone = updated.filter(a => a.status !== "done");
      const done = updated.filter(a => a.status === "done");
      return [...notDone, ...done];
    });
  }, [allData]); // eslint-disable-line react-hooks/exhaustive-deps

  const movePlanItem = (fromIdx, toIdx) => {
    if (toIdx < 0 || toIdx >= planItems.length) return;
    const items = [...planItems];
    const [moved] = items.splice(fromIdx, 1);
    items.splice(toIdx, 0, moved);
    setPlanItems(items);
    setPlanSaved(false);
  };

  const savePlanOrder = async () => {
    try {
      setSyncing(true);
      // 순서 저장
      for (let i = 0; i < planItems.length; i++) {
        await supabase.update("addresses", planItems[i].id, { sort_order: i + 1 });
      }
      // 시간 저장 (routes 테이블에)
      const today = new Date().toISOString().split("T")[0];
      const timeData = {
        vehicle: planVehicle,
        route_date: today,
        route_data: JSON.stringify({ depart: vehicleTimes[planVehicle].depart, arrive: vehicleTimes[planVehicle].arrive, count: planItems.length }),
        total_time: 0,
      };
      // 기존 데이터 확인 후 업데이트 또는 삽입
      try {
        const existing = await supabase.fetch("routes", `vehicle=eq.${planVehicle}&route_date=eq.${today}&limit=1`);
        if (existing.length > 0) {
          await supabase.update("routes", existing[0].id, timeData);
        } else {
          await supabase.insert("routes", timeData);
        }
      } catch(e) { /* routes 저장 실패해도 순서는 저장됨 */ }

      // 로컬 state 업데이트 (allData 기반)
      setAllData(prev => {
        const updated = [...prev];
        planItems.forEach((item, i) => {
          const idx = updated.findIndex(a => a.id === item.id);
          if (idx >= 0) updated[idx] = { ...updated[idx], sort_order: i + 1 };
        });
        return updated;
      });
      setPlanSaved(true);
      alert(`${planVehicle}호차 동선 ${planItems.length}건 저장 완료!`);
    } catch (e) {
      alert("저장 실패: " + e.message);
    } finally { setSyncing(false); }
  };

  // ── Render ──
  return (
    <div style={{ minHeight: "100vh", background: T.bg, color: T.text, fontFamily: "'Pretendard',-apple-system,'Noto Sans KR',sans-serif", maxWidth: 480, margin: "0 auto" }}>

      {/* ── Header ── */}
      <div style={{ background: T.bg, borderBottom: `1px solid ${T.border}`, padding: "12px 16px 10px", position: "sticky", top: 0, zIndex: 100, boxShadow: "0 1px 20px rgba(0,0,0,0.4)" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{ width: 36, height: 36, borderRadius: 10, background: "linear-gradient(135deg,#3B82F6,#8B5CF6)", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 4px 12px rgba(59,130,246,0.4)" }}>
              <Icon d={I.truck} size={17} color="#fff"/>
            </div>
            <div>
              <div style={{ fontSize: 15, fontWeight: 700, color: T.text }}>배송 매니저</div>
              <div style={{ fontSize: 10, color: T.dim }}>총 {totalCount.toLocaleString()}건</div>
            </div>
          </div>
          <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
            <button
              onClick={loadAllData}
              onTouchStart={e => { window._pressTimer = setTimeout(() => { window.location.reload(true); }, 800); }}
              onTouchEnd={() => { clearTimeout(window._pressTimer); }}
              onTouchMove={() => { clearTimeout(window._pressTimer); }}
              disabled={loading} style={{ width: 32, height: 32, borderRadius: 8, border: `1px solid ${T.border}`, background: T.sf, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <Icon d={loading ? I.loader : I.refresh} size={14} color={loading ? T.warning : T.muted}/>
            </button>
            {/* 호차별 완료 현황 */}
            <div style={{ display: "flex", gap: 4 }}>
              {vehicleInfo.map((v, i) => {
                const done = vehicleDoneCounts[v.id];
                const total = vehicleCounts[v.id];
                const color = VH_COLORS_GLOBAL[i];
                return (
                  <div key={v.id} style={{ padding: "3px 7px", borderRadius: 6, background: done === total && total > 0 ? color + "33" : T.sf, border: `1px solid ${done === total && total > 0 ? color + "66" : T.border}`, textAlign: "center", minWidth: 36 }}>
                    <div style={{ fontSize: 11, fontWeight: 800, color }}>{v.num}</div>
                    <div style={{ fontSize: 7, color: T.dim }}>{done}/{total}</div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
        {/* 전체 진행 바 */}
        <div style={{ background: T.border, borderRadius: 4, height: 4, overflow: "hidden" }}>
          <div style={{ height: "100%", borderRadius: 4, background: `linear-gradient(90deg,${T.accent},#34D399)`, width: `${totalCount ? (stats.done / totalCount * 100) : 0}%`, transition: "width 0.5s" }}/>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", marginTop: 4 }}>
          <span style={{ fontSize: 10, color: T.dim }}>완료 {stats.done} / {totalCount}</span>
          <span style={{ fontSize: 10, color: T.accent, fontWeight: 600 }}>{totalCount ? Math.round(stats.done / totalCount * 100) : 0}%</span>
        </div>
      </div>

      {/* ── Tabs ── */}
      <div style={{ display: "flex", padding: "6px 12px", gap: 4, background: T.sf, borderBottom: `1px solid ${T.border}` }}>
        {[{ id: "plan", icon: I.route, label: "배송일정" }, { id: "map", icon: I.mapView, label: "지도" }, { id: "hq", icon: I.chart, label: "본부" }, { id: "schedule", icon: I.calendar, label: "배본일정" }].map(t => (
          <button key={t.id} onClick={() => {
            setTab(t.id);
            if (t.id === "map") {
              setTimeout(function() {
                if (window.naver && window.naver.maps && window.__mapInstance) {
                  window.naver.maps.Event.trigger(window.__mapInstance, "resize");
                }
              }, 300);
            }
          }} style={{
            flex: 1, padding: "8px 0", border: "none", cursor: "pointer", borderRadius: 8,
            background: tab === t.id ? T.primary : "transparent",
            color: tab === t.id ? "#fff" : T.muted,
            fontWeight: tab === t.id ? 700 : 500, fontSize: 12,
            display: "flex", alignItems: "center", justifyContent: "center", gap: 4,
            transition: "all 0.15s",
          }}>
            <Icon d={t.icon} size={14} color={tab === t.id ? "#fff" : T.muted}/>{t.label}
          </button>
        ))}
      </div>

      {/* ── Loading / Error ── */}
      {loading && (
        <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "60px 20px", gap: 16 }}>
          <div style={{ width: 48, height: 48, borderRadius: "50%", border: `3px solid ${T.border}`, borderTop: `3px solid ${T.primary}`, animation: "spin 0.8s linear infinite" }}/>
          <div style={{ fontSize: 14, color: T.muted, fontWeight: 600 }}>데이터 로딩중...</div>
          <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
        </div>
      )}
      {error && (
        <div style={{ margin: "16px", padding: "16px", borderRadius: 12, background: T.danger + "18", border: `1px solid ${T.danger}44` }}>
          <div style={{ fontSize: 13, fontWeight: 700, color: T.danger, marginBottom: 6 }}>⚠ 연결 오류</div>
          <div style={{ fontSize: 11, color: T.muted, marginBottom: 8 }}>{error}</div>
          <button onClick={loadAllData} style={{ padding: "8px 16px", borderRadius: 8, border: "none", background: T.primary, color: "#fff", fontSize: 12, fontWeight: 700, cursor: "pointer" }}>다시 시도</button>
        </div>
      )}

      {/* ── Content ── */}
      {!loading && (
        <div>
          {/* 지도 탭은 패딩 없이 full width */}
          <div style={{ display: tab === "map" ? "block" : "none" }}>
            {!mapUnlocked ? (
              <div style={{ textAlign: "center", padding: "40px 20px" }}>
                <div style={{ fontSize: 40, marginBottom: 12 }}>🗺️</div>
                <div style={{ fontSize: 16, fontWeight: 700, marginBottom: 4 }}>배송지 지도</div>
                <div style={{ fontSize: 11, color: T.muted, marginBottom: 20 }}>비밀번호를 입력해주세요</div>
                <input type="password" inputMode="numeric" value={mapPwInput}
                  onChange={e => setMapPwInput(e.target.value)}
                  onKeyDown={e => { if (e.key === "Enter") { if (mapPwInput === adminPassword) setMapUnlocked(true); else { alert("비밀번호가 틀렸습니다."); setMapPwInput(""); } } }}
                  placeholder="비밀번호" style={{ width: 160, padding: "12px", borderRadius: 10, border: "2px solid #E2E8F0", background: "#F8FAFC", color: "#0F172A", fontSize: 20, textAlign: "center", outline: "none", letterSpacing: 8 }}
                />
                <div style={{ marginTop: 12 }}>
                  <button onClick={() => { if (mapPwInput === adminPassword) setMapUnlocked(true); else { alert("비밀번호가 틀렸습니다."); setMapPwInput(""); } }} style={{
                    padding: "10px 40px", borderRadius: 10, border: "none", cursor: "pointer",
                    background: "linear-gradient(135deg,#3B82F6,#2563EB)", color: "#fff", fontSize: 14, fontWeight: 700,
                  }}>확인</button>
                </div>
              </div>
            ) : (
              <MapTab addresses={addresses} assignedItems={assignedItems} vehicleCounts={vehicleCounts} VEHICLES={VEHICLES} MAGAZINES={MAGAZINES} ROUTE_CODES={ROUTE_CODES} GU_LIST={GU_LIST} vc={vc} T={T} I={I} Icon={Icon} supabase={supabase} extraMags={extraMags} mapExtraMagGlobal={mapExtraMagGlobal} setMapExtraMagGlobal={setMapExtraMagGlobal} allData={allData} setAllData={setAllData} allDataRef={allDataRef} loadAllData={loadAllData} allDataReady={allDataReady} resetMapKey={resetMapKey} vehicleInfo={vehicleInfo} />
            )}
          </div>

        <div style={{ padding: "10px 16px 120px" }}>

          {/* ═══ PLAN TAB ═══ */}
          {tab === "plan" && (
            <div>
              {(
              /* ── 건물 리스트 화면 (메인) ── */
              <div>
                <div style={{ marginBottom: 8 }}>
                  <div style={{ fontSize: 15, fontWeight: 700 }}>📋 동선 계획</div>
                </div>

                {/* 차량 선택 */}
                <div style={{ display: "flex", gap: 5, marginBottom: 8 }}>
                  {vehicleInfo.map((vi, i) => {
                    const v = vi.id;
                    const color = VH_COLORS_GLOBAL[i];
                    const all = vehicleCounts[v];
                    const done = v === planVehicle ? planItems.filter(a => a.status === "done").length : vehicleDoneCounts[v];
                    const isActive = planVehicle === v;
                    return (
                      <button key={v} onClick={() => setPlanVehicle(v)} style={{
                        flex: 1, padding: "8px 4px", borderRadius: 8,
                        border: isActive ? "none" : `1px solid ${color}44`,
                        cursor: "pointer", fontWeight: 600,
                        background: isActive ? color : color + "15",
                        color: isActive ? "#fff" : color,
                        boxShadow: isActive ? `0 2px 10px ${color}55` : "none",
                        transition: "all 0.15s",
                      }}>
                        <div style={{ fontSize: 13, fontWeight: 800 }}>{vi.num}</div>
                        <div style={{ fontSize: 10, marginTop: 1, opacity: 0.9 }}>{vi.name}</div>
                        <div style={{ fontSize: 9, color: isActive ? "#ffffffbb" : color + "bb", marginTop: 2 }}>
                          {done}/{all} · {vehicleMqTotals[v]}권
                        </div>
                      </button>
                    );
                  })}
                </div>

                {/* 잡지별 합산 배너 */}
                {(() => {
                  const color = VH_COLORS_GLOBAL[["A","B","C","D"].indexOf(planVehicle)];
                  const bg = ["#EFF6FF","#FFFBEB","#F0FDF4","#FDF2F8"][["A","B","C","D"].indexOf(planVehicle)];
                  const MAG_ORDER = ["노블레스","맨","Y","웨딩","아트","워치","타임북"];
                  const MAG_SHORT = {"노블레스":"노블","맨":"맨","아트":"아트","Y":"Y","워치":"워치","웨딩":"웨딩","타임북":"타임"};
                  const totals = {};
                  // allData에서 직접 계산 → 차량 전환 시 즉시 반영
                  allData.filter(a => a.vehicle === planVehicle).forEach(a => {
                    const mq = safeMq(a.magazine_qty);
                    Object.entries(mq).forEach(([k,v]) => { if(v>0) totals[k]=(totals[k]||0)+v; });
                  });
                  const entries = MAG_ORDER.filter(k => totals[k] > 0).map(k => [k, totals[k]]);
                  if(entries.length===0) return null;
                  const grand = entries.reduce((s,[,v])=>s+v,0);
                  return (
                    <div style={{ padding:"8px 12px", borderRadius:8, marginBottom:8, background:bg, border:`1px solid ${color}33` }}>
                      <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:5 }}>
                        <span style={{ fontSize:11, fontWeight:700, color }}>📦 총 배송 수량</span>
                        <span style={{ fontSize:12, fontWeight:700, color }}>{grand}권</span>
                      </div>
                      <div style={{ display:"grid", gridTemplateColumns:"repeat(4,1fr)", gap:3 }}>
                        {entries.map(([k,v])=>(
                          <div key={k} style={{ display:"flex", justifyContent:"space-between", padding:"3px 6px", background:"#ffffff88", borderRadius:5, border:`0.5px solid ${color}22` }}>
                            <span style={{ fontSize:10, color:T.muted }}>{MAG_SHORT[k]||k}</span>
                            <span style={{ fontSize:10, fontWeight:700, color }}>{v}</span>
                          </div>
                        ))}
                      </div>
                    </div>
                  );
                })()}

                {/* 출발지 */}
                <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", marginBottom: 4, borderRadius: 8, background: "#DCFCE7", border: `1px solid ${T.accent}33` }}>
                  <div style={{ width: 24, height: 24, borderRadius: "50%", background: T.accent, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 700, color: "#fff", flexShrink: 0 }}>출</div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 12, fontWeight: 600, color: T.accent }}>본부 · 뚝섬로 338 (출발)</div>
                  </div>
                  <input type="time" value={vehicleTimes[planVehicle].depart}
                    onChange={e => { setVehicleTimes(prev => ({ ...prev, [planVehicle]: { ...prev[planVehicle], depart: e.target.value } })); setPlanSaved(false); }}
                    style={{ padding: "4px 6px", borderRadius: 6, border: `1px solid ${T.accent}44`, background: T.sf, color: T.accent, fontSize: 13, fontWeight: 700, width: 90, textAlign: "center" }}
                  />
                </div>

                {/* 건물(도로명) 리스트 */}
                {(() => {
                  const groups = {};
                  const groupOrder = [];
                  planItems.forEach((item) => {
                    const key = item.road_address || item.name;
                    if (!groups[key]) { groups[key] = []; groupOrder.push(key); }
                    groups[key].push(item);
                  });
                  let buildingNum = 0;
                  return groupOrder.map((addr, gi) => {
                    const items = groups[addr];
                    const allDone = items.every(a => a.status === "done");
                    const doneCount = items.filter(a => a.status === "done").length;
                    buildingNum++;
                    return (
                      <div key={addr} style={{ marginBottom: 4 }}>
                        <div onClick={() => {
                          setLastVisitedAddr(addr);
                          setExpandedBuilding(expandedBuilding === addr ? null : addr);
                          // 헤더 아래에 카드가 오도록 스크롤 (숫자 조정 가능)
                          setTimeout(() => {
                            const el = document.getElementById(`building-${addr}`);
                            if (el) { const top = el.getBoundingClientRect().top + window.scrollY - 80; window.scrollTo({ top, behavior: "smooth" }); }
                          }, 50);
                        }}
                          style={{
                          width: "100%", padding: "10px 12px", borderRadius: 10, cursor: "pointer",
                          background: allDone ? "#DCFCE7" : T.card,
                          border: addr === lastVisitedAddr ? "2px solid #EF4444" : `1px solid ${allDone ? T.accent + "33" : T.border}`,
                          opacity: items.filter(a=>a.rejection_status).length === items.length && items.length > 0 ? 0.55 : (allDone ? 0.85 : 1),
                          display: "flex", alignItems: "center", gap: 8, boxSizing: "border-box",
                        }} id={`building-${addr}`}>
                          <div onClick={(e) => {
                            e.stopPropagation();
                            const newStatus = allDone ? "pending" : "done";
                            items.forEach(function(item) {
                              supabase.update("addresses", item.id, { status: newStatus });
                              if (newStatus === "done") {
                                var mq = item.magazine_qty || {};
                                var qObj = {};
                                MAGAZINES.forEach(function(_, i) { qObj["mag" + i] = mq["mag" + i] || 0; });
                                var dd = JSON.stringify({ quantities: qObj, memo: item.memo || fieldMemos[item.id] || "", magazines: MAGAZINES });
                                var today = new Date().toISOString().split("T")[0];
                                supabase.fetch("deliveries", "address_id=eq." + item.id + "&delivery_date=eq." + today + "&limit=1").then(function(ex) {
                                  if (ex.length > 0) supabase.update("deliveries", ex[0].id, { vehicle: item.vehicle, status: "done", delivery_data: dd });
                                  else supabase.insert("deliveries", { address_id: item.id, vehicle: item.vehicle, status: "done", delivery_data: dd });
                                });
                              }
                            });
                            setAllData(prev => prev.map(a => items.find(it => it.id === a.id) ? { ...a, status: newStatus } : a));
                          }} style={{
                            width: 28, height: 28, borderRadius: "50%", flexShrink: 0, cursor: "pointer",
                            background: allDone ? T.accent : `${vc(planVehicle)}22`,
                            border: `2px solid ${allDone ? T.accent : vc(planVehicle)}`,
                            display: "flex", alignItems: "center", justifyContent: "center",
                            fontSize: allDone ? 13 : 11, fontWeight: 700, color: allDone ? "#fff" : vc(planVehicle),
                          }}>
                            {allDone ? "✓" : buildingNum}
                          </div>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            {/* 전체 거부/폐업/중지 여부 계산 */}
                            {(() => {
                              const rejItems = items.filter(a => a.rejection_status);
                              const activeItems = items.filter(a => !a.rejection_status);
                              const rejCount = rejItems.length;
                              const allRejected = rejCount > 0 && rejCount === items.length;
                              const rejBadge = items.filter(a => a.rejection_status === "거부").length;
                              const closedBadge = items.filter(a => a.rejection_status === "폐업").length;
                              const stopBadge = items.filter(a => a.rejection_status === "중지").length;
                              const activeDone = activeItems.filter(a => a.status === "done").length;
                              const rejLabel = allRejected
                                ? (rejBadge > 0 && closedBadge > 0 ? "전체 거부/폐업" : rejBadge > 0 ? "전체 거부" : closedBadge > 0 ? "전체 폐업" : "전체 중지")
                                : "";
                              const rejColor = rejBadge > 0 ? "#EF4444" : "#94a3b8";
                              return (
                            <React.Fragment>
                            {/* 1줄: 주소 + 업체/건물명 + 펜 */}
                            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 5, minWidth: 0 }}>
                              <div style={{ fontSize: 20, fontWeight: 700, whiteSpace: "nowrap", flexShrink: 0,
                                textDecoration: (allDone || allRejected) ? "line-through" : "none",
                                color: (allDone || allRejected) ? T.muted : T.text,
                              }}>{addr}</div>
                              <div style={{ flex: 1, minWidth: 0 }}>
                                <BizNameEditor items={items} T={T} supabase={supabase} setPlanItems={setPlanItems} setAllData={setAllData} rightAlign={true}/>
                              </div>
                            </div>
                            {/* 2줄: 업장+권수+지도+화살표 */}
                            <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 5 }}>
                              <span style={{ fontSize: 13, fontWeight: 700, color: T.text, flex: 1, display: "flex", alignItems: "center", gap: 4, flexWrap: "wrap" }}>
                                <span style={{ background: "#e2e8f0", color: "#334155", padding: "2px 8px", borderRadius: 4, fontSize: 11, fontWeight: 600 }}>총 {items.length}개</span>
                                <span style={{ color: T.dim, fontSize: 11 }}>›</span>
                                {rejBadge > 0 && <span style={{ background: "#EF444422", color: "#EF4444", padding: "2px 8px", borderRadius: 4, fontSize: 11, fontWeight: 700 }}>거부 {rejBadge}</span>}
                                {closedBadge > 0 && <span style={{ background: "#94a3b822", color: "#94a3b8", padding: "2px 8px", borderRadius: 4, fontSize: 11, fontWeight: 700 }}>폐업 {closedBadge}</span>}
                                {stopBadge > 0 && <span style={{ background: "#F59E0B22", color: "#F59E0B", padding: "2px 8px", borderRadius: 4, fontSize: 11, fontWeight: 700 }}>중지 {stopBadge}</span>}
                                <span style={{ background: "#05966922", color: "#059669", padding: "2px 8px", borderRadius: 4, fontSize: 11, fontWeight: 700 }}>배부 {activeItems.length}개</span>
                                <span style={{ color: T.dim, fontSize: 11 }}>{activeDone}/{activeItems.length} 완료</span>
                                {items.some(a => photos[a.id]?.length > 0) && <span style={{ fontSize: 11 }}>📸{items.reduce((s, a) => s + (photos[a.id]?.length || 0), 0)}</span>}
                              </span>
                              <span style={{ padding: "2px 8px", borderRadius: 4, background: vc(planVehicle) + "22", fontSize: 12, fontWeight: 700, color: vc(planVehicle), flexShrink: 0 }}>
                                {activeItems.reduce((s, a) => s + mqTotal(a.magazine_qty), 0)}권
                              </span>
                              <button onClick={(e) => { e.stopPropagation(); openNavi(addr, items[0]?.name || addr); }} style={{
                                padding: "4px 8px", borderRadius: 6, border: `1px solid ${T.border}`, cursor: "pointer",
                                background: T.primary + "22", display: "flex", alignItems: "center", gap: 3, flexShrink: 0,
                              }}>
                                <Icon d={I.nav} size={13} color={T.primary}/>
                                <span style={{ fontSize: 11, fontWeight: 600, color: T.primary }}>지도</span>
                              </button>
                              <div style={{ display: "flex", gap: 3, flexShrink: 0 }} onClick={e => e.stopPropagation()}>
                                <button onClick={() => {
                                  if (gi > 0) {
                                    const prevAddr = groupOrder[gi-1]; const curItems = groups[addr];
                                    const newPlan = [...planItems];
                                    const prevStart = newPlan.findIndex(a => (a.road_address||a.name)===prevAddr);
                                    const curStart = newPlan.findIndex(a => (a.road_address||a.name)===addr);
                                    if (prevStart>=0 && curStart>=0) { const removed=newPlan.splice(curStart,curItems.length); newPlan.splice(prevStart,0,...removed); setPlanItems(newPlan); setPlanSaved(false); }
                                  }
                                }} style={{ width:28,height:28,borderRadius:6,border:"none",background:gi>0?`${T.primary}22`:T.sf,cursor:gi>0?"pointer":"default",display:"flex",alignItems:"center",justifyContent:"center" }}>
                                  <Icon d={I.arrowUp} size={12} color={gi>0?T.primary:T.dim}/>
                                </button>
                                <button onClick={() => {
                                  if (gi < groupOrder.length-1) {
                                    const nextAddr = groupOrder[gi+1]; const nextItems=groups[nextAddr]; const curItems=groups[addr];
                                    const newPlan=[...planItems];
                                    const curStart=newPlan.findIndex(a=>(a.road_address||a.name)===addr);
                                    const nextStart=newPlan.findIndex(a=>(a.road_address||a.name)===nextAddr);
                                    if (curStart>=0 && nextStart>=0) { const removed=newPlan.splice(nextStart,nextItems.length); newPlan.splice(curStart,0,...removed); setPlanItems(newPlan); setPlanSaved(false); }
                                  }
                                }} style={{ width:28,height:28,borderRadius:6,border:"none",background:gi<groupOrder.length-1?`${T.primary}22`:T.sf,cursor:gi<groupOrder.length-1?"pointer":"default",display:"flex",alignItems:"center",justifyContent:"center" }}>
                                  <Icon d={I.arrowDown} size={12} color={gi<groupOrder.length-1?T.primary:T.dim}/>
                                </button>
                              </div>
                            </div>
                            {/* 3줄: 잡지태그 */}
                            <div style={{ display: "flex", flexWrap: "wrap", gap: 3 }}>
                              {(() => { const MAG_KEYS=["노블레스","맨","Y","웨딩","아트","워치","타임북"]; const MAG_SHORT={"노블레스":"노블","맨":"맨","아트":"아트","Y":"Y","워치":"워치","웨딩":"웨딩","타임북":"타임"}; const MAG_COLOR={"노블레스":"#1D4ED8","맨":"#6D28D9","아트":"#B45309","Y":"#047857","워치":"#B91C1C","웨딩":"#9D174D","타임북":"#4338CA"}; const totals={}; items.forEach(a=>{const mq=safeMq(a.magazine_qty); MAG_KEYS.forEach(k=>{if(mq[k] && typeof mq[k]==="number")totals[k]=(totals[k]||0)+mq[k];});}); return MAG_KEYS.filter(k=>totals[k]).map(k=><span key={k} style={{fontSize:13,padding:"3px 8px",borderRadius:4,background:allRejected?T.sf:MAG_COLOR[k]+"33",color:allRejected?T.dim:MAG_COLOR[k],fontWeight:700,textDecoration:allRejected?"line-through":"none"}}>{MAG_SHORT[k]}{totals[k]}</span>); })()}
                            </div>
                            </React.Fragment>
                              );
                            })()}
                          </div>

                        </div>
                        {/* 아코디언 업장 목록 */}
                        {expandedBuilding === addr && (
                          <div style={{ borderTop: `1px solid ${T.border}` }}>
                            {items.map((item, i) => {
                              const isDone = item.status === "done";
                              const mq = item.magazine_qty || {};
                              const totalQty = mqTotal(mq);
                              return (
                                <div key={item.id} style={{
                                  background: isDone ? "#F0FDF4" : T.sf,
                                  border: item.id === lastVisitedItem ? "2px solid #EF4444" : "none",
                                  borderBottom: `0.5px solid ${T.border}`,
                                  opacity: isDone ? 0.75 : 1,
                                }}>
                                  {/* 업장 헤더 행 */}
                                  <div onClick={() => { buildingScrollRef.current = window.scrollY; setLastVisitedItem(item.id); openDeliveryDetail(item); }} style={{
                                    padding: "9px 12px", cursor: "pointer",
                                    display: "flex", alignItems: "center", gap: 8,
                                  }}>
                                    <div style={{
                                      width: 22, height: 22, borderRadius: "50%", flexShrink: 0,
                                      background: isDone ? T.accent : T.card,
                                      border: `1.5px solid ${isDone ? T.accent : T.border}`,
                                      display: "flex", alignItems: "center", justifyContent: "center",
                                      fontSize: 9, fontWeight: 700, color: isDone ? "#fff" : T.dim,
                                    }}>{isDone ? "✓" : i + 1}</div>
                                    <div style={{ flex: 1, minWidth: 0 }}>
                                      <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
                                        <span style={{ fontSize: 13, fontWeight: 600, flex: 1,
                                          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                                          textDecoration: (isDone || item.rejection_status) ? "line-through" : "none",
                                          color: (isDone || item.rejection_status) ? T.dim : T.text,
                                        }}>{item.name}{item.detail_addr ? <span style={{ fontSize: 11, color: T.primary, marginLeft: 3 }}>{item.detail_addr}</span> : null}</span>
                                        <span onClick={e => e.stopPropagation()}>
                                          <FloorEditor item={item} supabase={supabase} setAllData={setAllData} setDeliveryDetail={() => {}} T={T} compact={true} />
                                        </span>
                                        {item.rejection_status && <span style={{ fontSize: 10, fontWeight: 700, color: item.rejection_status === "거부" ? "#EF4444" : "#94a3b8", flexShrink: 0 }}>{item.rejection_status}</span>}
                                        {totalQty > 0 && !item.rejection_status && <span style={{ fontSize: 11, fontWeight: 700, color: T.accent, flexShrink: 0 }}>{totalQty}권</span>}
                                      </div>
                                    </div>
                                    <span style={{ fontSize: 11, color: T.dim, flexShrink: 0 }}>›</span>
                                  </div>
                                  {/* 잡지 태그 탭 → 팝업 수정 */}
                                  {!isDone && !item.rejection_status && (() => {
                                    const MAG_KEYS = ["노블레스","맨","Y","웨딩","아트","워치","타임북"];
                                    const MAG_SHORT = {"노블레스":"노블","맨":"맨","Y":"Y","웨딩":"웨딩","아트":"아트","워치":"워치","타임북":"타임"};
                                    const MAG_COLOR = {"노블레스":"#1D4ED8","맨":"#6D28D9","Y":"#047857","웨딩":"#9D174D","아트":"#B45309","워치":"#B91C1C","타임북":"#4338CA"};
                                    const itemMq = safeMq(item.magazine_qty);
                                    const active = MAG_KEYS.filter(k => (itemMq[k]||0) > 0);
                                    const inactive = MAG_KEYS.filter(k => !(itemMq[k]||0));
                                    const updateMq = (mag, delta) => {
                                      const newMq = { ...safeMq(item.magazine_qty) };
                                      newMq[mag] = Math.max(0, (newMq[mag]||0) + delta);
                                      if (newMq[mag] === 0) delete newMq[mag];
                                      supabase.update("addresses", item.id, { magazine_qty: newMq });
                                      setAllData(prev => prev.map(a => a.id === item.id ? { ...a, magazine_qty: newMq } : a));
                                    };
                                    const popupId = `mag-popup-${item.id}`;
                                    const addId = `mag-add-${item.id}`;
                                    const openPopup = (k) => {
                                      const el = document.getElementById(popupId);
                                      if (!el) return;
                                      // 같은 태그 다시 클릭 시 닫기
                                      if (el.style.display === "block" && el.dataset.mag === k) {
                                        el.style.display = "none";
                                        return;
                                      }
                                      document.querySelectorAll('[id^="mag-popup-"],[id^="mag-add-"]').forEach(el => el.style.display = "none");
                                      el.dataset.mag = k; el.querySelector(".popup-label").textContent = k; el.querySelector(".popup-label").style.color = MAG_COLOR[k]; el.querySelector(".popup-val").textContent = itemMq[k]||0; el.style.display = "block";
                                    };
                                    return (
                                      <React.Fragment>
                                        <div onClick={e => e.stopPropagation()} style={{ padding: "0 12px 6px 42px", display: "flex", flexWrap: "wrap", gap: 3, alignItems: "center" }}>
                                          {active.map(k => (
                                            <span key={k} onClick={() => openPopup(k)} style={{ padding: "3px 8px", borderRadius: 5, cursor: "pointer", background: MAG_COLOR[k]+"33", color: MAG_COLOR[k], fontSize: 12, fontWeight: 700, border: `1px solid ${MAG_COLOR[k]}44` }}>{MAG_SHORT[k]}{itemMq[k]}</span>
                                          ))}
                                          {inactive.length > 0 && (
                                            <button onClick={() => { const el=document.getElementById(addId); const isOpen = el && el.style.display !== "none"; document.querySelectorAll('[id^="mag-popup-"],[id^="mag-add-"]').forEach(el=>el.style.display="none"); if(!isOpen && el) el.style.display="flex"; }} style={{ padding: "3px 7px", borderRadius: 5, border: `1px dashed ${T.border}`, background: "transparent", color: T.dim, fontSize: 11, cursor: "pointer" }}>＋</button>
                                          )}
                                        </div>
                                        {/* 수정 팝업 */}
                                        <div id={popupId} data-mag="" onClick={e=>e.stopPropagation()} style={{ display:"none", margin:"0 12px 8px 42px", padding:"12px", borderRadius:10, border:`1px solid ${T.border}`, background:T.card }}>
                                          <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:10 }}>
                                            <span className="popup-label" style={{ fontSize:13, fontWeight:700 }}></span>
                                            <button onClick={() => document.getElementById(popupId).style.display="none"} style={{ border:"none", background:"transparent", fontSize:14, color:T.dim, cursor:"pointer" }}>✕</button>
                                          </div>
                                          <div style={{ display:"flex", alignItems:"center", justifyContent:"center", gap:16 }}>
                                            <button onClick={() => { const el=document.getElementById(popupId); const k=el.dataset.mag; const cur=parseInt(el.querySelector(".popup-val").textContent)||0; const nv=Math.max(0,cur-1); el.querySelector(".popup-val").textContent=nv; updateMq(k, -1); }} style={{ width:44, height:44, borderRadius:10, border:`1px solid ${T.border}`, background:T.sf, fontSize:22, cursor:"pointer" }}>-</button>
                                            <span className="popup-val" style={{ fontSize:28, fontWeight:700, minWidth:48, textAlign:"center" }}>0</span>
                                            <button onClick={() => { const el=document.getElementById(popupId); const k=el.dataset.mag; const cur=parseInt(el.querySelector(".popup-val").textContent)||0; el.querySelector(".popup-val").textContent=cur+1; updateMq(k, 1); }} style={{ width:44, height:44, borderRadius:10, border:`1px solid ${T.border}`, background:T.sf, fontSize:22, cursor:"pointer" }}>+</button>
                                          </div>
                                          <button onClick={() => document.getElementById(popupId).style.display="none"} style={{ marginTop:10, width:"100%", padding:"10px", borderRadius:8, border:"none", background:T.accent, color:"#fff", fontSize:13, fontWeight:700, cursor:"pointer" }}>확인</button>
                                        </div>
                                        {/* 추가 패널 */}
                                        <div id={addId} onClick={e=>e.stopPropagation()} style={{ display:"none", margin:"0 12px 8px 42px", padding:"10px", borderRadius:10, border:`1px solid ${T.border}`, background:T.card, flexWrap:"wrap", gap:6 }}>
                                          {inactive.map(k => (
                                            <button key={k} onClick={() => { updateMq(k,1); document.getElementById(addId).style.display="none"; }} style={{ padding:"6px 12px", borderRadius:8, border:`1px solid ${MAG_COLOR[k]}44`, background:MAG_COLOR[k]+"22", color:MAG_COLOR[k], fontSize:13, fontWeight:700, cursor:"pointer" }}>{MAG_SHORT[k]}</button>
                                          ))}
                                        </div>
                                      </React.Fragment>
                                    );
                                  })()}
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    );
                  });
                })()}

                {/* 복귀 */}
                {planItems.length > 0 && (
                  <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", marginTop: 4, borderRadius: 8, background: `${T.warning}15`, border: `1px solid ${T.warning}33` }}>
                    <div style={{ width: 24, height: 24, borderRadius: "50%", background: T.warning, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 700, color: "#fff", flexShrink: 0 }}>복</div>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 12, fontWeight: 600, color: T.warning }}>본부 · 뚝섬로 338 (복귀)</div>
                    </div>
                    <input type="time" value={vehicleTimes[planVehicle].arrive}
                      onChange={e => { setVehicleTimes(prev => ({ ...prev, [planVehicle]: { ...prev[planVehicle], arrive: e.target.value } })); setPlanSaved(false); }}
                      style={{ padding: "4px 6px", borderRadius: 6, border: `1px solid ${T.warning}44`, background: T.sf, color: T.warning, fontSize: 13, fontWeight: 700, width: 90, textAlign: "center" }}
                    />
                  </div>
                )}

                {planItems.length === 0 && (
                  <div style={{ textAlign: "center", padding: 30, color: T.dim, fontSize: 13 }}>
                    {planVehicle}호차에 배송 건이 없습니다.
                  </div>
                )}
              </div>
              )}
            </div>
          )}

          {/* ═══ 배본일정 TAB ═══ */}
          <div style={{ display: tab === "schedule" ? "block" : "none" }}>
            <ScheduleTab T={T} I={I} Icon={Icon}
              dbAddresses={allData}
              setDbAddresses={setAllData}
              ROUTE_CODES={ROUTE_CODES}
              supabase={supabase}
              vehicleInfo={vehicleInfo}
              adminPassword={adminPassword}
              scheduleUnlocked={scheduleUnlocked}
              setScheduleUnlocked={setScheduleUnlocked}
            />
          </div>

          {/* ═══ HQ TAB (본부) ═══ */}
          <div style={{ display: tab === "hq" ? "block" : "none" }}>
            <div>
              {!hqUnlocked ? (
                <div style={{ textAlign: "center", padding: "40px 20px", background: T.bg }}>
                  <div style={{ fontSize: 40, marginBottom: 12 }}>🔒</div>
                  <div style={{ fontSize: 16, fontWeight: 700, marginBottom: 4 }}>본부 관리</div>
                  <div style={{ fontSize: 11, color: T.muted, marginBottom: 20 }}>비밀번호를 입력해주세요</div>
                  <input type="password" inputMode="numeric" value={hqPwInput}
                    onChange={e => setHqPwInput(e.target.value)}
                    onKeyDown={e => { if (e.key === "Enter") { if (hqPwInput === adminPassword) { setHqUnlocked(true); } else { alert("비밀번호가 틀렸습니다."); setHqPwInput(""); } } }}
                    placeholder="비밀번호" style={{ width: 160, padding: "12px", borderRadius: 10, border: "2px solid #E2E8F0", background: "#F8FAFC", color: "#0F172A", fontSize: 20, textAlign: "center", outline: "none", letterSpacing: 8 }}
                  />
                  <div style={{ marginTop: 12 }}>
                    <button onClick={() => { if (hqPwInput === adminPassword) { setHqUnlocked(true); } else { alert("비밀번호가 틀렸습니다."); setHqPwInput(""); } }} style={{
                      padding: "10px 40px", borderRadius: 10, border: "none", cursor: "pointer",
                      background: "linear-gradient(135deg,#3B82F6,#2563EB)", color: "#fff", fontSize: 14, fontWeight: 700,
                    }}>확인</button>
                  </div>
                </div>
              ) : (
                <div>
                  {/* 서브탭 */}
                  <div style={{ display: "flex", gap: 4, marginBottom: 10 }}>
                    {[{ id: "vehicles", label: "🚗 차량지정" }, { id: "addrmgmt", label: "🏢 업체관리" }, { id: "magmgmt", label: "📚 잡지관리" }, { id: "changelog", label: "📋 변경내역" }, { id: "report", label: "📊 리포트" }, { id: "pwchange", label: "🔑 비밀번호" }].map(st => (
                      <button key={st.id} onClick={() => setHqSubTab(st.id)} style={{
                        flex: 1, padding: "9px 0", borderRadius: 8, border: "none", cursor: "pointer",
                        background: hqSubTab === st.id ? `${T.primary}22` : T.sf,
                        color: hqSubTab === st.id ? T.primary : T.text, fontSize: 12, fontWeight: 600,
                      }}>{st.label}</button>
                    ))}
                  </div>

                  {/* 배송설정 서브탭 */}
                  {/* 🚗 차량지정 서브탭 */}
                  {hqSubTab === "vehicles" && (
                    <VehicleAssignTab
                      vehicleInfo={vehicleInfo}
                      setVehicleInfo={setVehicleInfo}
                      supabase={supabase}
                      T={T}
                    />
                  )}

                  {/* 리포트 서브탭 */}
                                    {/* 리포트 서브탭 */}
                  {hqSubTab === "report" && (
                    <div>

              {/* 기간 선택 */}
              <div style={{ padding: "12px", borderRadius: 10, background: T.sf, border: `1px solid ${T.border}`, marginBottom: 10 }}>
                <div style={{ fontSize: 11, fontWeight: 600, color: T.dim, marginBottom: 8 }}>조회 기간</div>
                <div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 8 }}>
                  <input type="date" value={hqDateFrom} onChange={e => setHqDateFrom(e.target.value)}
                    style={{ flex: 1, padding: "8px", borderRadius: 8, border: `1px solid ${T.border}`, background: T.card, color: T.text, fontSize: 13 }}/>
                  <span style={{ color: T.dim, fontSize: 12 }}>~</span>
                  <input type="date" value={hqDateTo} onChange={e => setHqDateTo(e.target.value)}
                    style={{ flex: 1, padding: "8px", borderRadius: 8, border: `1px solid ${T.border}`, background: T.card, color: T.text, fontSize: 13 }}/>
                </div>
                {/* 빠른 선택 */}
                <div style={{ display: "flex", gap: 4, marginBottom: 8 }}>
                  {[
                    { label: "오늘", fn: () => { const t = new Date().toISOString().split("T")[0]; setHqDateFrom(t); setHqDateTo(t); } },
                    { label: "이번주", fn: () => { const t = new Date(); const d = t.getDay(); const mon = new Date(t); mon.setDate(t.getDate() - (d === 0 ? 6 : d - 1)); setHqDateFrom(mon.toISOString().split("T")[0]); setHqDateTo(t.toISOString().split("T")[0]); } },
                    { label: "이번달", fn: () => { const t = new Date(); setHqDateFrom(`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-01`); setHqDateTo(t.toISOString().split("T")[0]); } },
                    { label: "저번달", fn: () => { const t = new Date(); t.setMonth(t.getMonth()-1); const y = t.getFullYear(); const m = String(t.getMonth()+1).padStart(2,"0"); const last = new Date(y, t.getMonth()+1, 0).getDate(); setHqDateFrom(`${y}-${m}-01`); setHqDateTo(`${y}-${m}-${last}`); } },
                  ].map((b, i) => (
                    <button key={i} onClick={b.fn} style={{
                      flex: 1, padding: "6px 0", borderRadius: 6, border: "none", cursor: "pointer",
                      background: T.card, color: T.muted, fontSize: 10, fontWeight: 600,
                    }}>{b.label}</button>
                  ))}
                </div>
                {/* 차량 필터 */}
                <div style={{ display: "flex", gap: 4, marginBottom: 10 }}>
                  {["all", ...VEHICLES].map((v, i) => {
                    const vIdx = ["A","B","C","D"].indexOf(v);
                    const vNum = v !== "all" && vehicleInfo ? (vehicleInfo[vIdx]?.num || v) : null;
                    return (
                      <button key={v} onClick={() => setHqVehicle(v)} style={{
                        flex: 1, padding: "7px 0", borderRadius: 7, border: "none", cursor: "pointer",
                        fontSize: 11, fontWeight: 700,
                        background: hqVehicle === v ? (v === "all" ? T.primary : vc(v)) : T.card,
                        color: hqVehicle === v ? "#fff" : "#0F172A",
                      }}>{v === "all" ? "전체" : vNum}</button>
                    );
                  })}
                </div>
                <button onClick={loadHqData} disabled={hqLoading} style={{
                  width: "100%", padding: "11px", borderRadius: 10, border: "none", cursor: hqLoading ? "wait" : "pointer",
                  background: "linear-gradient(135deg,#3B82F6,#2563EB)", color: "#fff",
                  fontSize: 13, fontWeight: 700,
                }}>
                  {hqLoading ? "조회중..." : "📋 데이터 조회"}
                </button>
              </div>

              {/* 조회 결과 */}
              {hqData.length > 0 && (
                <div>
                  {/* 요약 카드 */}
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 6, marginBottom: 10 }}>
                    {(() => {
                      const done = hqData.filter(d => d.status === "done").length;
                      const totalQty = hqData.reduce((s, d) => s + Object.values(d.parsed.quantities || {}).reduce((a, v) => a + (v||0), 0), 0);
                      const memoCount = hqData.filter(d => d.parsed.memo).length;
                      return [
                        { label: "배송건수", value: `${done}건`, color: T.accent },
                        { label: "총 배송수량", value: `${totalQty}권`, color: T.primary },
                        { label: "메모업체", value: `${memoCount}건`, color: T.warning },
                      ].map((c, i) => (
                        <div key={i} style={{ padding: "10px", borderRadius: 10, background: T.card, border: `1px solid ${T.border}`, textAlign: "center" }}>
                          <div style={{ fontSize: 18, fontWeight: 700, color: c.color }}>{c.value}</div>
                          <div style={{ fontSize: 9, color: T.dim, marginTop: 2 }}>{c.label}</div>
                        </div>
                      ));
                    })()}
                  </div>

                  {/* 차량별 진행률 */}
                  <div style={{ padding: "10px", borderRadius: 10, background: T.card, border: `1px solid ${T.border}`, marginBottom: 10 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8 }}>차량별 현황</div>
                    {VEHICLES.map(v => {
                      const vData = hqData.filter(d => d.vehicle === v);
                      const vDone = vData.filter(d => d.status === "done").length;
                      const vQty = vData.reduce((s, d) => s + Object.values(d.parsed.quantities || {}).reduce((a, val) => a + (val||0), 0), 0);
                      return (
                        <div key={v} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
                          <div style={{ width: 50, fontSize: 11, fontWeight: 600, color: vc(v) }}>{v}호차</div>
                          <div style={{ flex: 1, height: 6, borderRadius: 3, background: T.sf, overflow: "hidden" }}>
                            <div style={{ width: `${vData.length ? (vDone / vData.length * 100) : 0}%`, height: "100%", borderRadius: 3, background: vc(v), transition: "width 0.3s" }}/>
                          </div>
                          <div style={{ fontSize: 10, color: T.dim, minWidth: 70, textAlign: "right" }}>{vDone}건 · {vQty}권</div>
                        </div>
                      );
                    })}
                  </div>

                  {/* 잡지별 수량 */}
                  <div style={{ padding: "10px", borderRadius: 10, background: T.card, border: `1px solid ${T.border}`, marginBottom: 10 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8 }}>잡지별 수량</div>
                    {MAGAZINES.map((m, i) => {
                      const key = `mag${i}`;
                      const total = hqData.reduce((s, d) => s + ((d.parsed.quantities || {})[key] || 0), 0);
                      const maxQty = Math.max(...MAGAZINES.map((_, j) => hqData.reduce((s, d) => s + ((d.parsed.quantities || {})[`mag${j}`] || 0), 0)), 1);
                      return (
                        <div key={i} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 5 }}>
                          <div style={{ width: 50, fontSize: 11, fontWeight: 600, color: T.text }}>{m}</div>
                          <div style={{ flex: 1, height: 6, borderRadius: 3, background: T.sf, overflow: "hidden" }}>
                            <div style={{ width: `${(total / maxQty) * 100}%`, height: "100%", borderRadius: 3, background: T.primary }}/>
                          </div>
                          <div style={{ fontSize: 11, fontWeight: 700, color: T.primary, minWidth: 40, textAlign: "right" }}>{total}권</div>
                        </div>
                      );
                    })}
                  </div>

                  {/* 메모 있는 업체 */}
                  {(() => {
                    const memoItems = hqData.filter(d => d.parsed.memo);
                    if (memoItems.length === 0) return null;
                    return (
                      <div style={{ padding: "10px", borderRadius: 10, background: T.card, border: `1px solid ${T.warning}33`, marginBottom: 10 }}>
                        <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8, color: T.warning }}>⚠️ 변동/특이사항 ({memoItems.length}건)</div>
                        {memoItems.map((d, i) => (
                          <div key={i} style={{ padding: "8px", borderRadius: 8, background: T.sf, marginBottom: 4, border: `1px solid ${T.border}` }}>
                            <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
                              <span style={{ fontSize: 11, fontWeight: 600 }}>{d.addr.name}</span>
                              <span style={{ fontSize: 9, color: T.dim }}>{d.delivery_date} · {d.vehicle}호차</span>
                            </div>
                            <div style={{ fontSize: 11, color: T.muted, lineHeight: 1.4 }}>{d.parsed.memo}</div>
                          </div>
                        ))}
                      </div>
                    );
                  })()}

                  {/* 배송 내역 리스트 */}
                  <div style={{ padding: "10px", borderRadius: 10, background: T.card, border: `1px solid ${T.border}`, marginBottom: 10 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8 }}>📋 배송 내역 ({hqData.length}건)</div>
                    <div style={{ maxHeight: 300, overflowY: "auto" }}>
                      {hqData.map((d, i) => {
                        const qty = d.parsed.quantities || {};
                        const total = Object.values(qty).reduce((s, v) => s + (v||0), 0);
                        return (
                          <div key={i} onClick={() => setHqDetailItem(d)} style={{ display: "flex", alignItems: "center", gap: 6, padding: "7px 8px", marginBottom: 3, borderRadius: 8, background: T.sf, border: `1px solid ${T.border}`, cursor: "pointer" }}>
                            <div style={{ flex: 1, minWidth: 0 }}>
                              <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
                                <span style={{ fontSize: 11, fontWeight: 600 }}>{d.addr.name || "업체"}</span>
                                <span style={{ fontSize: 8, padding: "1px 4px", borderRadius: 3, background: `${vc(d.vehicle)}22`, color: vc(d.vehicle), fontWeight: 600 }}>{d.vehicle}</span>
                                <span style={{ fontSize: 9, fontWeight: 700, color: T.accent }}>{total}권</span>
                              </div>
                              <div style={{ fontSize: 8, color: T.dim, marginTop: 2 }}>{d.addr.road_address || ""}</div>
                              <div style={{ fontSize: 8, color: T.muted, marginTop: 1 }}>{d.delivery_date}{d.parsed.memo ? " · 📝" : ""}{photos[d.address_id]?.length > 0 ? " · 📸" + photos[d.address_id].length : ""}</div>
                              {d.parsed.memo && <div style={{ fontSize: 8, color: T.warning, marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>📝 {d.parsed.memo}</div>}
                              {total > 0 && (
                                <div style={{ display: "flex", gap: 3, marginTop: 3, flexWrap: "wrap" }}>
                                  {MAGAZINES.map((mag, mi) => {
                                    const q = (qty[`mag${mi}`] || 0);
                                    return q > 0 ? <span key={mi} style={{ fontSize: 7, padding: "1px 4px", borderRadius: 3, background: T.accent + "22", color: T.accent }}>{mag}:{q}</span> : null;
                                  })}
                                </div>
                              )}
                            </div>
                            <Icon d={I.chevRight} size={12} color={T.dim}/>
                          </div>
                        );
                      })}
                    </div>
                  </div>

                  {/* 엑셀 다운로드 */}
                  <button onClick={downloadExcel} style={{
                    width: "100%", padding: "14px", borderRadius: 12, border: "none", cursor: "pointer",
                    background: "linear-gradient(135deg,#10B981,#059669)", color: "#fff",
                    fontSize: 14, fontWeight: 700, marginBottom: 10,
                    boxShadow: "0 4px 15px rgba(16,185,129,0.3)",
                  }}>
                    📥 엑셀 다운로드
                  </button>
                  <div style={{ fontSize: 9, color: T.dim, textAlign: "center" }}>
                    배송내역 · 업체별합계 · 잡지별수량 · 변동업체 (4개 시트)
                  </div>

                  {/* 배송 마감 */}
                  <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid " + T.border }}>
                    <button onClick={handleDailyReset} disabled={syncing} style={{
                      width: "100%", padding: "14px", borderRadius: 12, border: "none", cursor: "pointer",
                      background: "linear-gradient(135deg,#EF4444,#DC2626)", color: "#fff",
                      fontSize: 14, fontWeight: 700,
                      boxShadow: "0 4px 15px rgba(239,68,68,0.3)",
                    }}>
                      🔄 배송 마감 (일일 초기화)
                    </button>
                    <div style={{ fontSize: 9, color: T.dim, textAlign: "center", marginTop: 4 }}>
                      호차배정 · 잡지수량 · 완료상태 초기화 (배송기록 유지)
                    </div>
                  </div>
                </div>
              )}

              {hqData.length === 0 && !hqLoading && (
                <div style={{ textAlign: "center", padding: 30, color: T.dim, fontSize: 12 }}>
                  기간을 선택하고 "데이터 조회"를 눌러주세요.
                </div>
              )}

                    </div>
                  )}

                  {/* 업체관리 서브탭 */}
                  {hqSubTab === "addrmgmt" && React.createElement(AddrMgmt, { setAllData, supabase, syncing, vehicleInfo })}
                  {React.createElement("div", { style: { display: hqSubTab === "magmgmt" ? "block" : "none" } }, React.createElement(MagMgmt, { extraMags, setExtraMags, setMagNames, supabase, addresses, setAllData, mgSearch, setMgSearch, mgCodes, setMgCodes, mgBulkMag, setMgBulkMag, mgBulkQty, setMgBulkQty, mgBulkItems, setMgBulkItems, mgBulkLoading, setMgBulkLoading, mgBulkResult, setMgBulkResult, mgNewMag, setMgNewMag, allData, allDataRef }))}

                                    {/* 변경내역 서브탭 */}
                  {hqSubTab === "changelog" && (
                    <ChangeLogTab supabase={supabase} T={T} VEHICLES={VEHICLES} vc={vc}/>
                  )}

                                    {/* 비밀번호 변경 서브탭 */}
                  {hqSubTab === "pwchange" && (
                    <div style={{ padding: "20px", textAlign: "center" }}>
                      <div style={{ fontSize: 40, marginBottom: 12 }}>🔑</div>
                      <div style={{ fontSize: 16, fontWeight: 700, marginBottom: 4 }}>비밀번호 변경</div>
                      <div style={{ fontSize: 11, color: T.muted, marginBottom: 20 }}>지도 · 본부 공용 비밀번호</div>
                      <div style={{ maxWidth: 250, margin: "0 auto" }}>
                        <input type="password" inputMode="numeric" placeholder="현재 비밀번호" id="pw_current"
                          style={{ width: "100%", padding: "10px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 14, textAlign: "center", marginBottom: 8, outline: "none" }}/>
                        <input type="password" inputMode="numeric" placeholder="새 비밀번호" id="pw_new"
                          style={{ width: "100%", padding: "10px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 14, textAlign: "center", marginBottom: 8, outline: "none" }}/>
                        <input type="password" inputMode="numeric" placeholder="새 비밀번호 확인" id="pw_confirm"
                          style={{ width: "100%", padding: "10px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 14, textAlign: "center", marginBottom: 16, outline: "none" }}/>
                        <button onClick={async () => {
                          var cur = document.getElementById("pw_current").value;
                          var nw = document.getElementById("pw_new").value;
                          var cf = document.getElementById("pw_confirm").value;
                          if (!cur || !nw || !cf) { alert("모든 항목을 입력해주세요."); return; }
                          if (cur !== adminPassword) { alert("현재 비밀번호가 틀렸습니다."); return; }
                          if (nw !== cf) { alert("새 비밀번호가 일치하지 않습니다."); return; }
                          if (nw.length < 4) { alert("비밀번호는 4자리 이상이어야 합니다."); return; }
                          try {
                            await window.fetch(SUPABASE_URL + "/rest/v1/settings?key=eq.admin_password", {
                              method: "PATCH",
                              headers: { "apikey": SUPABASE_KEY, "Authorization": "Bearer " + SUPABASE_KEY, "Content-Type": "application/json", "Prefer": "return=representation" },
                              body: JSON.stringify({ value: nw })
                            });
                            setAdminPassword(nw);
                            document.getElementById("pw_current").value = "";
                            document.getElementById("pw_new").value = "";
                            document.getElementById("pw_confirm").value = "";
                            alert("비밀번호가 변경되었습니다.");
                          } catch(e) { alert("변경 실패: " + e.message); }
                        }} style={{
                          width: "100%", padding: "12px", borderRadius: 10, border: "none", cursor: "pointer",
                          background: "linear-gradient(135deg,#3B82F6,#2563EB)", color: "#fff", fontSize: 14, fontWeight: 700,
                        }}>비밀번호 변경</button>
                      </div>
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>

        </div>
        </div>
      )}

      {/* ═══ MODALS ═══ */}

      {/* Add/Edit Form */}
      {showAddForm && <FormModal initial={editingItem} onSave={handleSave} onClose={() => { setShowAddForm(false); setEditingItem(null); }} onDelete={editingItem ? () => { handleDelete(editingItem.id); setShowAddForm(false); setEditingItem(null); } : null} syncing={syncing} vehicleInfo={vehicleInfo}/>}

      {/* Bulk Upload */}
      {showBulkUpload && <BulkModal onUpload={handleBulkUpload} onClose={() => setShowBulkUpload(false)} syncing={syncing}/>}

      {/* Photo Viewer */}
      {photoViewer && (
        <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.95)", display: "flex", flexDirection: "column", zIndex: 999 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 16px" }}>
            <div style={{ color: "#fff", fontSize: 13, fontWeight: 600, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{photoViewer.name}</div>
            <button onClick={() => { setPhotoViewer(null); setPhotoZoom(1); setPhotoPos({ x: 0, y: 0 }); }} style={{ width: 36, height: 36, borderRadius: "50%", border: "none", background: "rgba(255,255,255,0.15)", color: "#fff", fontSize: 18, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>✕</button>
          </div>
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden", padding: "0 8px" }}
            onClick={() => { const now = Date.now(); if (now - photoLastTap.current < 300) { if (photoZoom > 1) { setPhotoZoom(1); setPhotoPos({ x: 0, y: 0 }); } else { setPhotoZoom(3); } } photoLastTap.current = now; }}
            onTouchStart={(e) => { if (photoZoom > 1 && e.touches.length === 1) { photoDragStart.current = { x: e.touches[0].clientX - photoPos.x, y: e.touches[0].clientY - photoPos.y }; } }}
            onTouchMove={(e) => { if (photoZoom > 1 && photoDragStart.current && e.touches.length === 1) { setPhotoPos({ x: e.touches[0].clientX - photoDragStart.current.x, y: e.touches[0].clientY - photoDragStart.current.y }); } }}
            onTouchEnd={() => { photoDragStart.current = null; }}>
            <img src={photoViewer.url} alt="" style={{ maxWidth: "100%", maxHeight: "80vh", borderRadius: 8, objectFit: "contain", transform: `scale(${photoZoom}) translate(${photoPos.x / photoZoom}px, ${photoPos.y / photoZoom}px)`, transition: photoDragStart.current ? "none" : "transform 0.2s" }}/>
          </div>
          <div style={{ padding: "8px 16px 20px", display: "flex", justifyContent: "center", gap: 10, alignItems: "center" }}>
            <div style={{ color: T.muted, fontSize: 9 }}>{photoViewer.time} · 더블탭 확대</div>
          </div>
          <div style={{ display: "flex", gap: 8, padding: "0 16px 20px", justifyContent: "center" }}>
            <button onClick={() => { if (photoZoom > 1) { setPhotoZoom(1); setPhotoPos({ x: 0, y: 0 }); } else setPhotoZoom(3); }} style={{ padding: "8px 20px", borderRadius: 8, border: "none", background: "rgba(255,255,255,0.15)", color: "#fff", fontSize: 12, fontWeight: 600, cursor: "pointer" }}>{photoZoom > 1 ? "축소" : "확대"}</button>
            <button onClick={() => { setPhotoViewer(null); setPhotoZoom(1); setPhotoPos({ x: 0, y: 0 }); }} style={{ padding: "8px 30px", borderRadius: 8, border: "none", background: "rgba(255,255,255,0.15)", color: "#fff", fontSize: 12, fontWeight: 600, cursor: "pointer" }}>닫기</button>
          </div>
        </div>
      )}

      {/* 배송내역 상세 패널 */}
      {hqDetailItem && (() => {
        const d = hqDetailItem;
        const qty = d.parsed.quantities || {};
        const total = Object.values(qty).reduce((s, v) => s + (v||0), 0);
        const ph = photos[d.address_id] || [];
        return (
        <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "flex-end", justifyContent: "center", zIndex: 200 }} onClick={() => setHqDetailItem(null)}>
          <div onClick={e => e.stopPropagation()} style={{ width: "100%", maxWidth: 480, maxHeight: "85vh", overflowY: "auto", background: "#F1F5F9", borderRadius: "18px 18px 0 0", padding: "16px", border: `1px solid ${T.border}` }}>
            <div style={{ width: 36, height: 4, borderRadius: 2, background: T.border, margin: "0 auto 10px" }}/>
            
            <button onClick={() => setHqDetailItem(null)} style={{ padding: "6px 14px", borderRadius: 8, border: "none", cursor: "pointer", background: T.sf, color: T.muted, fontSize: 12, fontWeight: 600, marginBottom: 10 }}>← 뒤로</button>

            <div style={{ fontSize: 15, fontWeight: 700, marginBottom: 4 }}>{d.addr.name || "업체"}</div>
            <div style={{ fontSize: 11, color: T.muted, marginBottom: 2 }}>{d.addr.road_address || ""}</div>
            <div style={{ display: "flex", gap: 6, marginBottom: 10 }}>
              <span style={{ fontSize: 10, padding: "2px 8px", borderRadius: 4, background: vc(d.vehicle) + "22", color: vc(d.vehicle), fontWeight: 600 }}>{d.vehicle}호차</span>
              <span style={{ fontSize: 10, padding: "2px 8px", borderRadius: 4, background: T.accent + "22", color: T.accent, fontWeight: 600 }}>{total}권</span>
              <span style={{ fontSize: 10, color: T.dim }}>{d.delivery_date}</span>
            </div>

            {/* 잡지별 수량 */}
            <div style={{ padding: "10px", borderRadius: 8, background: T.sf, marginBottom: 10 }}>
              <div style={{ fontSize: 11, fontWeight: 700, marginBottom: 6 }}>📦 잡지별 수량</div>
              {MAGAZINES.map((mag, mi) => {
                const q = qty[`mag${mi}`] || 0;
                return q > 0 ? (
                  <div key={mi} style={{ display: "flex", justifyContent: "space-between", padding: "3px 0", fontSize: 11 }}>
                    <span style={{ color: T.text }}>{mag}</span>
                    <span style={{ fontWeight: 700, color: T.accent }}>{q}권</span>
                  </div>
                ) : null;
              })}
              {total === 0 && <div style={{ fontSize: 10, color: T.dim }}>수량 없음</div>}
            </div>

            {/* 메모 */}
            {(d.parsed.memo || d.addr.memo) && (
              <div style={{ padding: "10px", borderRadius: 8, background: T.sf, marginBottom: 10 }}>
                <div style={{ fontSize: 11, fontWeight: 700, marginBottom: 6 }}>📝 현장 메모</div>
                <div style={{ fontSize: 12, color: T.text, lineHeight: 1.5 }}>{d.parsed.memo || d.addr.memo}</div>
              </div>
            )}

            {/* 사진 */}
            <div style={{ padding: "10px", borderRadius: 8, background: T.sf, marginBottom: 10 }}>
              <div style={{ fontSize: 11, fontWeight: 700, marginBottom: 6 }}>📸 현장 사진 ({ph.length}장)</div>
              {ph.length > 0 ? (
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
                  {ph.map((p, pi) => (
                    <div key={pi} onClick={() => setPhotoViewer({ url: p.url, name: d.addr.name, time: p.time })} style={{ cursor: "pointer" }}>
                      <img src={p.url} alt="" style={{ width: "100%", height: 100, objectFit: "cover", borderRadius: 8, border: `1px solid ${T.border}` }}/>
                      <div style={{ fontSize: 8, color: T.dim, marginTop: 2 }}>{p.time}</div>
                    </div>
                  ))}
                </div>
              ) : (
                <div style={{ fontSize: 10, color: T.dim }}>사진 없음</div>
              )}
            </div>

            {/* 삭제 */}
            <button onClick={async () => {
              if (!confirm(`"${d.addr.name || "이 기록"}" 배송 기록을 삭제하시겠습니까?`)) return;
              try {
                await supabase.remove("deliveries", d.id);
                await supabase.update("addresses", d.address_id, { status: "pending" });
                setAllData(prev => prev.map(a => a.id === d.address_id ? { ...a, status: "pending" } : a));
                setHqData(prev => prev.filter(x => x.id !== d.id));
                setHqDetailItem(null);
              } catch(e) { alert("삭제 실패: " + e.message); }
            }} style={{ width: "100%", padding: "12px", borderRadius: 10, border: "none", cursor: "pointer", background: T.danger + "22", color: T.danger, fontSize: 12, fontWeight: 600 }}>
              🗑 이 배송 기록 삭제
            </button>
          </div>
        </div>
        );
      })()}

      {/* Stop Detail - 기사용 업체 상세 패널 */}
      {(selectedStop || deliveryDetail) && (() => {
        const item = deliveryDetail || selectedStop;
        const qty = quantities[item.id] || {};
        const ph = photos[item.id] || [];
        return (
        <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "flex-end", justifyContent: "center", zIndex: 200 }} onClick={() => { setSelectedStop(null); setDeliveryDetail(null); }}>
          <div onClick={e => e.stopPropagation()} style={{ width: "100%", maxWidth: 480, maxHeight: "90vh", overflowY: "auto", background: "#F1F5F9", borderRadius: "18px 18px 0 0", padding: "16px", border: `1px solid ${T.border}` }}>
            <div style={{ width: 36, height: 4, borderRadius: 2, background: T.border, margin: "0 auto 8px" }}/>
            <div style={{ display: "flex", alignItems: "center", marginBottom: 10, gap: 8 }}>
              <button onClick={() => { setSelectedStop(null); setDeliveryDetail(null); setTimeout(() => window.scrollTo(0, buildingScrollRef.current), 50); }} style={{
                padding: "6px 12px", borderRadius: 8, border: "none", cursor: "pointer",
                background: T.sf, color: T.muted, fontSize: 12, fontWeight: 600,
                display: "flex", alignItems: "center", gap: 4, flexShrink: 0,
              }}>← 뒤로</button>
              {editChanged && (
                <button onClick={async () => {
                  try {
                    setSyncing(true);
                    const memo = (item.id in fieldMemos) ? (fieldMemos[item.id] || "") : (item.memo || "");
                    const changes = [];
                    const oldMq = item.magazine_qty || {};
                    const allMqKeys = [...new Set([...Object.keys(oldMq), ...Object.keys(editMq)])];
                    const mqChanged = allMqKeys.some(k => (oldMq[k]||0) !== (editMq[k]||0));
                    const rejChanged = editRejection !== (item.rejection_status || null);
                    const memoChanged = memo !== (item.memo || "");
                    const updateData = {};
                    const MAG_SHORT2 = {"노블레스":"노블","MEN":"맨","ART":"아트","Y":"Y","워치&주얼리":"워치","웨딩":"웨딩","타임북":"타임"};
                    if (mqChanged) {
                      updateData.magazine_qty = editMq;
                      const diffParts = allMqKeys.filter(k => (oldMq[k]||0) !== (editMq[k]||0)).map(k => (MAG_SHORT2[k]||k)+(oldMq[k]||0)+"→"+(editMq[k]||0));
                      changes.push("수량변경: " + diffParts.join(" "));
                    }
                    if (rejChanged) {
                      updateData.rejection_status = editRejection;
                      changes.push("상태변경: " + (item.rejection_status||"정상") + " → " + (editRejection||"정상"));
                    }
                    if (memoChanged) {
                      updateData.memo = memo;
                      changes.push("메모: " + ((item.memo||"").substring(0,20)||"없음") + " → " + (memo.substring(0,20)||"없음"));
                    }
                    if (editName !== null) {
                      updateData.name = editName;
                      changes.push("업장명: " + item.name + " → " + editName);
                    }
                    if (editAddr !== null) {
                      updateData.road_address = editAddr;
                      changes.push("주소: " + item.road_address + " → " + editAddr);
                    }
                    if (editFloor !== null) {
                      updateData.detail_addr = editFloor;
                      changes.push("층수: " + (item.detail_addr || "(없음)") + " → " + editFloor);
                    }
                    if (Object.keys(updateData).length > 0) {
                      await supabase.update("addresses", item.id, updateData);
                      await supabase.insert("change_log", { vehicle: item.vehicle, address_id: item.id, name: editName || item.name, road_address: editAddr || item.road_address, change_type: changes.map(c => c.split(":")[0]).join(", "), change_detail: changes.join(" / ") });
                      // 주소 변경 시 좌표 캐시 삭제
                      if (editAddr) { try { const cc = JSON.parse(localStorage.getItem("dm_coordCache")||"{}"); delete cc[item.road_address]; localStorage.setItem("dm_coordCache", JSON.stringify(cc)); } catch(e) {} }
                      const updatedItem = {...item, ...updateData};
                      setDeliveryDetail(updatedItem);
                      setAllData(prev => prev.map(a => a.id === item.id ? updatedItem : a));
                      setEditChanged(false);
                      setEditName(null); setEditAddr(null); setEditFloor(null);
                    }
                  } catch(e) { alert("저장 실패: " + e.message); }
                  finally { setSyncing(false); }
                }} disabled={syncing} style={{
                  flex: 1, padding: "8px 12px", borderRadius: 8, border: "none", cursor: "pointer",
                  background: "linear-gradient(135deg,#3B82F6,#2563EB)", color: "#fff",
                  fontSize: 13, fontWeight: 700, display: "flex", alignItems: "center", justifyContent: "center", gap: 4,
                }}>
                  💾 {syncing ? "저장중..." : "변경사항 저장"}
                </button>
              )}
              <button onClick={() => handleDeliveryComplete(item)} disabled={syncing} style={{
                flex: 1, padding: "7px 10px", borderRadius: 8, border: "none", cursor: syncing ? "wait" : "pointer",
                background: item.status === "done" ? T.danger : T.accent,
                color: "#fff", fontSize: 12, fontWeight: 700,
              }}>
                {syncing ? "처리중..." : item.status === "done" ? "↩ 완료취소" : "✓ 배송완료"}
              </button>
              <button onClick={() => { setSelectedStop(null); setDeliveryDetail(null); }} style={{
                width: 30, height: 30, borderRadius: 8, border: "none", cursor: "pointer",
                background: T.sf, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
              }}>
                <Icon d={I.x} size={15} color={T.muted}/>
              </button>
            </div>
            
            {/* 업체 정보 */}
            <div style={{ marginBottom: 12 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 3 }}>
                <InlineEditor
                  value={item.name}
                  pendingValue={editName}
                  fontSize={17} fontWeight={700} flex={1}
                  textDecoration={item.rejection_status ? "line-through" : "none"}
                  color={item.rejection_status ? T.muted : T.text}
                  placeholder="업장명"
                  onSave={async (val) => {
                    if (val === item.name) return;
                    setEditName(val);
                    setEditChanged(true);
                  }}
                  T={T}
                />
                {/* 층수 인라인 편집 */}
                <FloorEditor item={item} supabase={supabase} setAllData={setAllData} setDeliveryDetail={setDeliveryDetail} T={T}
                  onPending={(val) => { setEditFloor(val); setEditChanged(true); }}
                />
                <button onClick={() => {
                  const next = editRejection === "거부" ? null : "거부";
                  setEditRejection(next); setEditChanged(true);
                }} style={{
                  padding: "4px 10px", borderRadius: 6, fontSize: 12, fontWeight: 700, cursor: "pointer",
                  border: editRejection === "거부" ? "2px solid #EF4444" : "1px solid #EF444466",
                  background: editRejection === "거부" ? "#EF444422" : "transparent",
                  color: "#EF4444",
                }}>거부{editRejection === "거부" ? " ✓" : ""}</button>
                <button onClick={() => {
                  const next = editRejection === "폐업" ? null : "폐업";
                  setEditRejection(next); setEditChanged(true);
                }} style={{
                  padding: "4px 10px", borderRadius: 6, fontSize: 12, fontWeight: 700, cursor: "pointer",
                  border: editRejection === "폐업" ? "2px solid #94a3b8" : "1px solid #94a3b866",
                  background: editRejection === "폐업" ? "#94a3b822" : "transparent",
                  color: "#94a3b8",
                }}>폐업{editRejection === "폐업" ? " ✓" : ""}</button>
                <button onClick={() => {
                  const next = editRejection === "중지" ? null : "중지";
                  setEditRejection(next); setEditChanged(true);
                }} style={{
                  padding: "4px 10px", borderRadius: 6, fontSize: 12, fontWeight: 700, cursor: "pointer",
                  border: editRejection === "중지" ? "2px solid #F59E0B" : "1px solid #F59E0B66",
                  background: editRejection === "중지" ? "#F59E0B22" : "transparent",
                  color: "#F59E0B",
                }}>중지{editRejection === "중지" ? " ✓" : ""}</button>
              </div>
              <InlineEditor
                value={item.road_address}
                pendingValue={editAddr}
                fontSize={12} color={T.muted}
                placeholder="도로명주소"
                onSave={async (val) => {
                  if (val === item.road_address) return;
                  setEditAddr(val);
                  setEditChanged(true);
                }}
                T={T}
              />
            </div>

            {/* 잡지 수량 (수정/저장 가능) */}
            {(() => {
              const MAG_KEYS = ["노블레스","맨","아트","Y","워치","웨딩","타임북"];
              const mq = item.magazine_qty || {};
              const vals = MAG_KEYS.map(k => mq[k] || 0);
              const totalQty = vals.reduce((s,v) => s+v, 0);
              return (
                <React.Fragment>
            {/* 현장 메모 */}
            <div style={{ marginBottom: 10 }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: T.warning, marginBottom: 4 }}>📝 현장 메모</div>
              <textarea
                value={fieldMemos[item.id] || ""}
                onChange={e => handleFieldMemo(item.id, e.target.value)}
                placeholder="변동사항, 특이사항..."
                rows={2}
                style={{ width: "100%", padding: "8px 10px", borderRadius: 8, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 12, resize: "none", outline: "none", boxSizing: "border-box", lineHeight: 1.5 }}
              />
            </div>

                <div style={{ marginBottom: 10 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                    <div style={{ fontSize: 12, fontWeight: 700, color: T.primary }}>📦 배송 수량</div>
                    <div style={{ fontSize: 12, fontWeight: 700, color: T.accent }}>{totalQty}권</div>
                  </div>
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }}>
                    {MAG_KEYS.map((mag, i) => (
                      <div key={i} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "5px 8px", borderRadius: 7, background: T.sf, border: `1px solid ${T.border}` }}>
                        <span style={{ fontSize: 12, color: T.text }}>{mag}</span>
                        <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
                          <button onClick={() => {
                            const newMq = {...editMq};
                            newMq[mag] = Math.max(0, (newMq[mag]||0) - 1);
                            setEditMq(newMq); setEditChanged(true);
                          }} style={{ width: 22, height: 22, borderRadius: 5, border: `1px solid ${T.border}`, background: T.card, color: T.text, fontSize: 14, cursor: "pointer", fontWeight: 700, padding: 0 }}>-</button>
                          <span style={{ fontSize: 13, fontWeight: 700, color: T.accent, minWidth: 18, textAlign: "center" }}>{editMq[mag] || 0}</span>
                          <button onClick={() => {
                            const newMq = {...editMq};
                            newMq[mag] = (newMq[mag]||0) + 1;
                            setEditMq(newMq); setEditChanged(true);
                          }} style={{ width: 22, height: 22, borderRadius: 5, border: `1px solid ${T.border}`, background: T.card, color: T.text, fontSize: 14, cursor: "pointer", fontWeight: 700, padding: 0 }}>+</button>
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
                </React.Fragment>
              );
            })()}


            {/* 배송 이력 삭제 - 본부 리포트에서 확인 */}

            {/* 배송완료 버튼 - 헤더로 이동됨 */}
          </div>
        </div>
        );
      })()}
    </div>
  );
}


// ── AddrMgmt 컴포넌트 ──
function MagMgmt({ extraMags, setExtraMags, setMagNames, supabase, addresses, setAllData, allData, allDataRef,
  mgSearch: search, setMgSearch: setSearch, mgCodes: codes, setMgCodes: setCodes,
  mgBulkMag: bulkMag, setMgBulkMag: setBulkMag, mgBulkQty: bulkQty, setMgBulkQty: setBulkQty,
  mgBulkItems: bulkItems, setMgBulkItems: setBulkItems, mgBulkLoading: bulkLoading, setMgBulkLoading: setBulkLoading,
  mgBulkResult: bulkResult, setMgBulkResult: setBulkResult, mgNewMag: newMag, setMgNewMag: setNewMag }) {
  const BASE_MAGS = ["노블레스","맨","아트","Y","워치","웨딩","타임북"];

  const saveExtraMags = function(mags) {
    // settings에 extra_mags 저장 (있으면 update, 없으면 insert)
    window.fetch(SUPABASE_URL + "/rest/v1/settings?key=eq.extra_mags", {
      method: "PATCH",
      headers: { "apikey": SUPABASE_KEY, "Authorization": "Bearer " + SUPABASE_KEY, "Content-Type": "application/json", "Prefer": "return=minimal" },
      body: JSON.stringify({ value: JSON.stringify(mags) })
    }).then(function(r) {
      if (r.status === 200 || r.status === 204) return;
      // 없으면 insert
      return window.fetch(SUPABASE_URL + "/rest/v1/settings", {
        method: "POST",
        headers: { "apikey": SUPABASE_KEY, "Authorization": "Bearer " + SUPABASE_KEY, "Content-Type": "application/json" },
        body: JSON.stringify({ key: "extra_mags", value: JSON.stringify(mags) })
      });
    }).catch(function(e) { console.error("설정 저장 실패:", e); });
    setExtraMags(mags);
    setMagNames(BASE_MAGS.concat(mags));
  };

  const addMag = function() {
    if (!newMag.trim()) return;
    if (BASE_MAGS.includes(newMag.trim()) || extraMags.includes(newMag.trim())) { alert("이미 존재하는 잡지입니다."); return; }
    saveExtraMags(extraMags.concat([newMag.trim()]));
    setNewMag("");
  };

  const deleteMag = function(mag) {
    if (!confirm(mag + " 삭제? 업체 수량 데이터는 유지됩니다.")) return;
    saveExtraMags(extraMags.filter(function(m) { return m !== mag; }));
    setBulkItems([]);
    setBulkResult("");
    setSearch("");
    setCodes([]);
  };

  // 검색 버튼으로 조회
  var doSearch = function() {
    if (!search.trim() && codes.length === 0) { setBulkItems([]); return; }
    setBulkLoading(true);
    var params = "is_active=eq.true&limit=1000";
    // 복수 배송코드 필터
    if (codes.length === 1) params += "&route_code=eq." + encodeURIComponent(codes[0]);
    else if (codes.length > 1) params += "&route_code=in.(" + codes.map(function(c){ return encodeURIComponent(c); }).join(",") + ")";
    // 복수 키워드 검색 (쉼표로 구분)
    var keywords = search.trim() ? search.trim().split(",").map(function(k){ return k.trim(); }).filter(Boolean) : [];
    if (keywords.length === 1) {
      params += "&name=ilike.*" + encodeURIComponent(keywords[0]) + "*";
    } else if (keywords.length > 1) {
      params += "&or=(" + keywords.map(function(k){ return "name.ilike.*" + encodeURIComponent(k) + "*"; }).join(",") + ")";
    }
    supabase.fetch("addresses", params + "&select=id,name,road_address,route_code,magazine_qty")
      .then(function(rows) { setBulkItems(rows||[]); setBulkLoading(false); })
      .catch(function() { setBulkLoading(false); });
  };

  const applyBulk = function() {
    if (!bulkMag) { setBulkResult("❌ 잡지를 선택하세요."); return; }
    if (bulkItems.length === 0) { setBulkResult("❌ 업체를 먼저 검색하세요."); return; }
    if (!confirm("기존 " + bulkMag + " 설정이 전부 삭제되고 검색된 " + bulkItems.length + "개 업체만 새로 적용됩니다. 계속하시겠습니까?")) return;
    setBulkResult("초기화 중...");
    var newIds = bulkItems.map(function(a) { return a.id; });
    // 1단계: DB에서 기존 해당 잡지 보유 업체 전부 조회 (페이지네이션)
    var fetchAllForReset = function(offset, acc) {
      return supabase.fetch("addresses", "is_active=eq.true&select=id,magazine_qty&order=id.asc&offset=" + offset + "&limit=1000").then(function(data) {
        var combined = acc.concat(data);
        if (data.length === 1000) return fetchAllForReset(offset + 1000, combined);
        return combined;
      });
    };
    fetchAllForReset(0, []).then(function(allData) {
      var resetTargets = allData.filter(function(a) {
        var mq = a.magazine_qty || {};
        if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
        return mq[bulkMag] > 0 && newIds.indexOf(a.id) === -1;
      });
      // 2단계: 기존 업체 0으로 초기화
      var resetPromises = resetTargets.map(function(a) {
        var mq = Object.assign({}, a.magazine_qty || {});
        if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
        mq[bulkMag] = 0;
        return supabase.update("addresses", a.id, { magazine_qty: mq });
      });
      // 3단계: 새 업체 적용
      var applyPromises = bulkItems.map(function(a) {
        var mq = Object.assign({}, a.magazine_qty || {});
        mq[bulkMag] = bulkQty;
        return supabase.update("addresses", a.id, { magazine_qty: mq });
      });
      setBulkResult("적용 중... (기존 " + resetTargets.length + "개 초기화)");
      return Promise.all(resetPromises.concat(applyPromises)).then(function() {
        // allData 즉시 업데이트 (모든 탭 공유)
        var resetIds = resetTargets.map(function(a){return a.id;});
        setAllData(function(prev) {
          return prev.map(function(a) {
            var mq = Object.assign({}, a.magazine_qty || {});
            if (resetIds.indexOf(a.id) >= 0) { mq[bulkMag] = 0; }
            if (newIds.indexOf(a.id) >= 0) { mq[bulkMag] = bulkQty; }
            return Object.assign({}, a, { magazine_qty: mq });
          });
        });
        setBulkResult("✅ 완료: 기존 " + resetTargets.length + "개 초기화 → " + bulkItems.length + "개 업체 새로 적용됨");
      });
    }).catch(function(e) { setBulkResult("❌ 실패: " + e.message); });
  };

  return React.createElement("div", { style: { padding: "0 4px" } },
    // 잡지 목록
    React.createElement("div", { style: { fontSize: 14, fontWeight: 700, marginBottom: 10 } }, "📚 잡지 관리"),
    React.createElement("div", { style: { marginBottom: 16, padding: 12, borderRadius: 10, background: T.sf, border: "1px solid " + T.border } },
      React.createElement("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 8, color: T.muted } }, "기본 잡지"),
      React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 12 } },
        BASE_MAGS.map(function(m) {
          return React.createElement("span", { key: m, style: { padding: "4px 10px", borderRadius: 6, background: T.card, border: "1px solid " + T.border, fontSize: 12 } }, m);
        })
      ),
      React.createElement("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 8, color: T.muted } }, "추가 잡지"),
      extraMags.length === 0
        ? React.createElement("div", { style: { fontSize: 11, color: T.muted, marginBottom: 8 } }, "추가된 잡지 없음")
        : React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 } },
            extraMags.map(function(m) {
              return React.createElement("div", { key: m, style: { display: "flex", alignItems: "center", gap: 4, padding: "4px 8px", borderRadius: 6, background: T.primary + "22", border: "1px solid " + T.primary + "44" } },
                React.createElement("span", { style: { fontSize: 12, color: T.primary, fontWeight: 700 } }, m),
                React.createElement("button", { onClick: function() { deleteMag(m); }, style: { width: 16, height: 16, borderRadius: "50%", border: "none", background: T.danger, color: "#fff", fontSize: 10, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", padding: 0, lineHeight: 1 } }, "×")
              );
            })
          ),
      React.createElement("div", { style: { display: "flex", gap: 6 } },
        React.createElement("input", { value: newMag, onChange: function(e) { setNewMag(e.target.value); }, onKeyDown: function(e) { if(e.key==="Enter") addMag(); }, placeholder: "새 잡지명 입력... (예: 브랜드북)", style: { flex: 1, padding: "7px 10px", borderRadius: 7, border: "1px solid " + T.border, background: T.card, color: T.text, fontSize: 12, outline: "none" } }),
        React.createElement("button", { onClick: addMag, style: { padding: "7px 14px", borderRadius: 7, border: "none", background: T.primary, color: "#fff", fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "+ 추가")
      )
    ),

    // 일괄 적용
    React.createElement("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 8 } }, "📦 업체별 일괄 적용"),
    React.createElement("div", { style: { padding: 12, borderRadius: 10, background: T.sf, border: "1px solid " + T.border } },
      React.createElement("div", { style: { display: "flex", gap: 6, marginBottom: 8 } },
        React.createElement("select", { value: bulkMag, onChange: function(e) { setBulkMag(e.target.value); }, style: { flex: 1, padding: "7px", borderRadius: 7, background: T.card, border: "1px solid " + T.border, color: T.text, fontSize: 12 } },
          React.createElement("option", { value: "" }, "잡지 선택"),
          BASE_MAGS.concat(extraMags).map(function(m) { return React.createElement("option", { key: m, value: m }, m); })
        ),
        React.createElement("input", { type: "number", min: 0, max: 99, value: bulkQty, onChange: function(e) { setBulkQty(parseInt(e.target.value)||0); }, style: { width: 60, padding: "7px", borderRadius: 7, border: "1px solid " + T.border, background: T.card, color: T.text, fontSize: 12, textAlign: "center" } }),
        React.createElement("span", { style: { padding: "7px 4px", fontSize: 12, color: T.muted } }, "권")
      ),
      React.createElement("input", { value: search, onChange: function(e) { setSearch(e.target.value); }, onKeyDown: function(e) { if(e.key==="Enter") doSearch(); }, placeholder: "업체명 검색 (쉼표로 복수 입력: 성형외과,미용실,은행)", style: { width: "100%", padding: "7px 10px", borderRadius: 7, border: "1px solid " + T.border, background: T.card, color: T.text, fontSize: 12, outline: "none", boxSizing: "border-box", marginBottom: 6 } }),
      React.createElement("div", { style: { fontSize: 11, color: T.muted, marginBottom: 4 } }, "배송코드 선택 (복수 선택 가능):"),
      React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 6, maxHeight: 120, overflowY: "auto", padding: 6, borderRadius: 7, border: "1px solid " + T.border, background: T.card } },
        ROUTE_CODES.map(function(c) {
          var isSelected = codes.indexOf(c) >= 0;
          return React.createElement("button", {
            key: c,
            onClick: function() { setCodes(isSelected ? codes.filter(function(x){ return x !== c; }) : codes.concat([c])); },
            style: { padding: "3px 8px", borderRadius: 5, border: "1px solid " + (isSelected ? T.primary : T.border), background: isSelected ? T.primary + "22" : "transparent", color: isSelected ? T.primary : T.muted, fontSize: 10, fontWeight: isSelected ? 700 : 400, cursor: "pointer" }
          }, c);
        })
      ),
      codes.length > 0 && React.createElement("div", { style: { fontSize: 10, color: T.primary, marginBottom: 6 } }, "선택된 코드: " + codes.join(", ")),
      React.createElement("div", { style: { display: "flex", gap: 6, marginBottom: 8 } },
        React.createElement("button", { onClick: function(){ setCodes([]); }, style: { padding: "7px 10px", borderRadius: 7, border: "1px solid " + T.border, background: T.card, color: T.muted, fontSize: 11, cursor: "pointer" } }, "코드 초기화"),
        React.createElement("button", { onClick: doSearch, style: { flex: 1, padding: "7px 16px", borderRadius: 7, border: "none", background: T.primary, color: "#fff", fontSize: 12, fontWeight: 700, cursor: "pointer" } }, "검색")
      ),
      bulkLoading
        ? React.createElement("div", { style: { fontSize: 11, color: T.muted, textAlign: "center", padding: 8 } }, "검색중...")
        : bulkItems.length > 0
          ? React.createElement("div", null,
              React.createElement("div", { style: { fontSize: 11, color: T.muted, marginBottom: 6 } }, "검색된 업체: " + bulkItems.length + "개"),
              React.createElement("div", { style: { maxHeight: 150, overflowY: "auto", marginBottom: 8, borderRadius: 7, border: "1px solid " + T.border } },
                bulkItems.map(function(a) {
                  return React.createElement("div", { key: a.id, style: { padding: "6px 10px", fontSize: 11, borderBottom: "1px solid " + T.border + "44" } },
                    React.createElement("span", { style: { fontWeight: 600 } }, a.name),
                    React.createElement("span", { style: { color: T.muted, marginLeft: 6 } }, a.route_code)
                  );
                })
              ),
              React.createElement("button", { onClick: applyBulk, style: { width: "100%", padding: "10px", borderRadius: 8, border: "none", background: T.primary, color: "#fff", fontSize: 13, fontWeight: 700, cursor: "pointer" } }, bulkMag ? bulkMag + " " + bulkQty + "권 → " + bulkItems.length + "개 업체 일괄 적용" : "잡지를 선택하세요"),
              bulkResult && React.createElement("div", { style: { marginTop: 8, padding: "8px 10px", borderRadius: 7, background: bulkResult.startsWith("✅") ? T.accent + "22" : T.danger + "22", color: bulkResult.startsWith("✅") ? T.accent : T.danger, fontSize: 12, fontWeight: 600 } }, bulkResult)
            )
          : (search.trim() || codes.length > 0) && React.createElement("div", { style: { fontSize: 11, color: T.muted, textAlign: "center", padding: 8 } }, "검색 결과 없음")
    )
  );
}

function AddrMgmt({ setAllData, supabase, syncing, vehicleInfo }) {
  const [amSearch, setAmSearch] = React.useState("");
  const [amCode, setAmCode] = React.useState("");
  const [amEditItem, setAmEditItem] = React.useState(null);
  const [amShowAdd, setAmShowAdd] = React.useState(false);
  const [amShowInactive, setAmShowInactive] = React.useState(false);
  const [amItems, setAmItems] = React.useState([]);
  const [amLoading, setAmLoading] = React.useState(false);
  const [amTotal, setAmTotal] = React.useState(0);

  // 검색/필터 변경 시 Supabase에서 직접 조회
  React.useEffect(function() {
    // 배송코드 미선택 + 검색어 없으면 조회 안 함
    if (!amCode && !amSearch.trim()) {
      setAmItems([]);
      setAmTotal(0);
      setAmLoading(false);
      return;
    }
    setAmLoading(true);
    var params = "is_active=eq." + (amShowInactive ? "false" : "true");
    if (amCode) params += "&route_code=eq." + encodeURIComponent(amCode);
    if (!amCode && amShowInactive) params = "is_active=in.(true,false)";
    if (amCode && amShowInactive) params = "route_code=eq." + encodeURIComponent(amCode);

    // 검색어 있으면 name or road_address
    var searchParam = "";
    if (amSearch.trim()) {
      searchParam = "&or=(name.ilike.*" + encodeURIComponent(amSearch.trim()) + "*,road_address.ilike.*" + encodeURIComponent(amSearch.trim()) + "*)";
    }

    var activeParam = amShowInactive ? "" : "&is_active=eq.true";
    var codeParam = amCode ? "&route_code=eq." + encodeURIComponent(amCode) : "";

    supabase.fetch("addresses", "select=id,name,road_address,route_code,magazine_qty,is_active,memo,vehicle&order=route_code.asc,name.asc&limit=200" + activeParam + codeParam + searchParam)
      .then(function(rows) {
        setAmItems(rows || []);
        setAmTotal(rows ? rows.length : 0);
        setAmLoading(false);
      }).catch(function() { setAmLoading(false); });
  }, [amSearch, amCode, amShowInactive]);

  var filtered = amItems;

  return React.createElement("div", null,
    React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 10 } },
      React.createElement("span", { style: { fontSize: 14, fontWeight: 700 } }, "🏢 업체관리"),
      React.createElement("span", { style: { fontSize: 11, color: T.muted } }, amLoading ? "로딩중..." : (amCode || amSearch.trim()) ? amTotal + "개 업체" : "총 4,120개 업체"),
      React.createElement("div", { style: { flex: 1 } }),
      React.createElement("button", {
        onClick: function() { setAmShowAdd(true); setAmEditItem(null); },
        style: { padding: "6px 14px", borderRadius: 7, border: "none", background: T.primary, color: "#fff", fontSize: 12, fontWeight: 700, cursor: "pointer" }
      }, "+ 업체 추가")
    ),
    React.createElement("input", {
      value: amSearch,
      onChange: function(e) { setAmSearch(e.target.value); },
      placeholder: "업체명, 주소 검색...",
      style: { width: "100%", padding: "9px 10px", borderRadius: 8, background: T.sf, border: "1px solid " + T.border, color: T.text, fontSize: 12, outline: "none", boxSizing: "border-box", marginBottom: 6 }
    }),
    React.createElement("div", { style: { display: "flex", gap: 6, marginBottom: 8 } },
      React.createElement("select", {
        value: amCode,
        onChange: function(e) { setAmCode(e.target.value); },
        style: { flex: 1, padding: "7px", borderRadius: 7, background: T.sf, border: "1px solid " + T.border, color: T.text, fontSize: 11 }
      },
        React.createElement("option", { value: "" }, "배송코드 전체"),
        ROUTE_CODES.map(function(c) { return React.createElement("option", { key: c, value: c }, c); })
      ),
      React.createElement("button", {
        onClick: function() { setAmShowInactive(!amShowInactive); },
        style: { padding: "7px 10px", borderRadius: 7, border: "1px solid " + T.border, background: amShowInactive ? T.danger + "22" : T.sf, color: amShowInactive ? T.danger : T.muted, fontSize: 11, cursor: "pointer", whiteSpace: "nowrap" }
      }, amShowInactive ? "폐업포함" : "폐업제외")
    ),
    filtered.length === 0
      ? React.createElement("div", { style: { textAlign: "center", color: T.muted, padding: 20, fontSize: 13 } }, 
          !amCode && !amSearch.trim() ? "배송코드를 선택하거나 업체명을 검색하세요" : "검색 결과 없음")
      : React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4 } },
          filtered.map(function(a) {
            var mq = a.magazine_qty || {};
            var totalMq = Object.values(mq).reduce(function(s,v){ return s+(v||0); }, 0);
            return React.createElement("div", {
              key: a.id,
              onClick: function() { setAmEditItem(a); setAmShowAdd(true); },
              style: { padding: "10px 12px", borderRadius: 8, background: T.sf, border: "1px solid " + (a.is_active ? T.border : T.danger + "44"), cursor: "pointer", opacity: a.is_active ? 1 : 0.6 }
            },
              React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } },
                React.createElement("span", { style: { fontSize: 13, fontWeight: 700, flex: 1, textDecoration: a.rejection_status ? "line-through" : "none", color: a.rejection_status ? T.dim : T.text } }, a.name),
                a.rejection_status && React.createElement("span", { style: { fontSize: 10, color: a.rejection_status === "거부" ? "#EF4444" : "#94a3b8", fontWeight: 700 } }, a.rejection_status),
                !a.is_active && !a.rejection_status && React.createElement("span", { style: { fontSize: 10, color: T.danger, fontWeight: 700 } }, "폐업"),
                React.createElement("span", { style: { fontSize: 11, color: T.muted } }, totalMq + "권")
              ),
              React.createElement("div", { style: { fontSize: 11, color: T.muted, marginTop: 2 } }, (a.route_code || "") + " · " + (a.road_address || ""))
            );
          })
        ),
    amShowAdd && React.createElement(FormModal, {
      initial: amEditItem ? Object.assign({}, amEditItem) : { is_active: true, magazine_qty: {} },
      vehicleInfo: vehicleInfo,
      onSave: function(formData) {
        var payload = Object.assign({}, formData);
        payload.is_active = formData.is_active !== false;
        if (amEditItem) {
          supabase.update("addresses", amEditItem.id, payload).then(function() {
            setAllData(function(prev) { return prev.map(function(x) { return x.id === amEditItem.id ? Object.assign({}, x, payload) : x; }); });
            setAmItems(function(prev) { return prev.map(function(x) { return x.id === amEditItem.id ? Object.assign({}, x, payload) : x; }); });
            setAmShowAdd(false); setAmEditItem(null);
          }).catch(function(e) { alert("수정 실패: " + e.message); });
        } else {
          payload.status = "pending";
          supabase.insert("addresses", payload).then(function(rows) {
            if (rows && rows[0]) {
              setAllData(function(prev) { return prev.concat(rows); });
              setAmItems(function(prev) { return prev.concat(rows); });
            }
            setAmShowAdd(false);
          }).catch(function(e) { alert("추가 실패: " + e.message); });
        }
      },
      onClose: function() { setAmShowAdd(false); setAmEditItem(null); },
      onDelete: amEditItem ? function() {
        if (!confirm(amEditItem.name + "을(를) 삭제하시겠습니까?")) return;
        supabase.remove("addresses", amEditItem.id).then(function() {
          setAllData(function(prev) { return prev.filter(function(x) { return x.id !== amEditItem.id; }); });
          setAmItems(function(prev) { return prev.filter(function(x) { return x.id !== amEditItem.id; }); });
          setAmShowAdd(false); setAmEditItem(null);
        }).catch(function(e) { alert("삭제 실패: " + e.message); });
      } : null,
      syncing: syncing
    })
  );
}

// ── Form Modal ──
function FormModal({ initial, onSave, onClose, onDelete, syncing, vehicleInfo: vi }) {
  const vInfoList = vi || DEFAULT_VEHICLES;
  const [f, setF] = useState(initial || { name: "", road_address: "", detail_addr: "", gu: "강남구", dong: "", route_code: "기타", vehicle: "A" });
  const set = (k, v) => setF(p => ({ ...p, [k]: v }));
  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "flex-end", justifyContent: "center", zIndex: 200 }}>
      <div style={{ width: "100%", maxWidth: 480, background: "#F1F5F9", borderRadius: "18px 18px 0 0", padding: "18px", border: "1px solid #334155", maxHeight: "85vh", overflowY: "auto" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14 }}>
          <div style={{ fontSize: 16, fontWeight: 700 }}>{initial ? "주소 수정" : "주소 추가"}</div>
          <button onClick={onClose} style={{ width: 30, height: 30, borderRadius: 7, border: "none", background: T.sf, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><Icon d={I.x} size={14} color={T.dim}/></button>
        </div>
        {[{ k: "name", l: "업체명", p: "예: 신한은행 청담지점" }, { k: "road_address", l: "도로명주소 (층수 제외)", p: "예: 도산대로 506" }, { k: "floor", l: "층수", p: "예: 1층, 3층/5층" }, { k: "dong", l: "동", p: "예: 청담동" }].map(field => (
          <div key={field.k} style={{ marginBottom: 10 }}>
            <label style={{ fontSize: 11, fontWeight: 600, color: T.muted, display: "block", marginBottom: 3 }}>{field.l}</label>
            <input value={f[field.k] || ""} onChange={e => set(field.k, e.target.value)} placeholder={field.p} style={{ width: "100%", padding: "9px 10px", borderRadius: 7, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 12, outline: "none", boxSizing: "border-box" }}/>
          </div>
        ))}
        <div style={{ display: "flex", gap: 6, marginBottom: 10 }}>
          <div style={{ flex: 1 }}>
            <label style={{ fontSize: 11, fontWeight: 600, color: T.muted, display: "block", marginBottom: 3 }}>구</label>
            <select value={f.gu} onChange={e => set("gu", e.target.value)} style={{ width: "100%", padding: "9px", borderRadius: 7, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 12 }}>
              {GU_LIST.map(g => <option key={g} value={g}>{g}</option>)}
            </select>
          </div>
          <div style={{ flex: 1 }}>
            <label style={{ fontSize: 11, fontWeight: 600, color: T.muted, display: "block", marginBottom: 3 }}>배송코드</label>
            <select value={f.route_code} onChange={e => set("route_code", e.target.value)} style={{ width: "100%", padding: "9px", borderRadius: 7, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 12 }}>
              {ROUTE_CODES.map(c => <option key={c} value={c}>{c}</option>)}
            </select>
          </div>
        </div>
        <div style={{ marginBottom: 14 }}>
          <label style={{ fontSize: 11, fontWeight: 600, color: T.muted, display: "block", marginBottom: 5 }}>배정 차량</label>
          <div style={{ display: "flex", gap: 6 }}>
            {VEHICLES.map((v, i) => {
              const vInfo = vInfoList[i];
              const color = VH_COLORS_GLOBAL[i];
              return (
                <button key={v} onClick={() => set("vehicle", v)} style={{
                  flex: 1, padding: "8px 4px", borderRadius: 7, border: "none",
                  background: f.vehicle === v ? color : color + "15",
                  color: f.vehicle === v ? "#fff" : color,
                  cursor: "pointer",
                }}>
                  <div style={{ fontSize: 12, fontWeight: 700 }}>{vInfo.num}</div>
                  <div style={{ fontSize: 10, marginTop: 1, opacity: 0.9 }}>{vInfo.name}</div>
                </button>
              );
            })}
          </div>
        </div>
        {/* 잡지 수량 */}
        <div style={{ marginBottom: 10 }}>
          <label style={{ fontSize: 11, fontWeight: 600, color: T.muted, display: "block", marginBottom: 5 }}>잡지 수량</label>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {["노블레스","맨","아트","Y","워치","웨딩","타임북"].map(function(k) {
              var mq = f.magazine_qty || {};
              var val = mq[k] || "";
              return React.createElement("div", { key: k, style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 2 } },
                React.createElement("label", { style: { fontSize: 10, color: T.muted } }, k),
                React.createElement("input", {
                  type: "number", min: 0, value: val,
                  onChange: function(e) {
                    var v = parseInt(e.target.value) || 0;
                    var newMq = Object.assign({}, f.magazine_qty || {});
                    if (v > 0) newMq[k] = v; else delete newMq[k];
                    set("magazine_qty", newMq);
                  },
                  style: { width: 44, padding: "6px 4px", borderRadius: 6, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 12, textAlign: "center", outline: "none" }
                })
              );
            })}
          </div>
        </div>
        {/* 메모 */}
        <div style={{ marginBottom: 10 }}>
          <label style={{ fontSize: 11, fontWeight: 600, color: T.muted, display: "block", marginBottom: 3 }}>메모</label>
          <input value={f.memo || ""} onChange={e => set("memo", e.target.value)} placeholder="비고, 방문 시 참고사항" style={{ width: "100%", padding: "9px 10px", borderRadius: 7, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 12, outline: "none", boxSizing: "border-box" }}/>
        </div>
        {/* 폐업 여부 */}
        <div style={{ marginBottom: 14, display: "flex", alignItems: "center", gap: 8 }}>
          <label style={{ fontSize: 11, fontWeight: 600, color: T.muted }}>폐업/비활성</label>
          <button onClick={() => set("is_active", !f.is_active)} style={{
            padding: "6px 16px", borderRadius: 7, border: "none", cursor: "pointer", fontSize: 12, fontWeight: 700,
            background: f.is_active === false ? T.danger + "33" : T.sf,
            color: f.is_active === false ? T.danger : T.muted
          }}>{f.is_active === false ? "폐업 (비활성)" : "정상 운영"}</button>
        </div>
        <button onClick={() => { if (!f.name || !f.road_address) return alert("업체명과 주소를 입력하세요"); onSave(f); }} disabled={syncing} style={{ width: "100%", padding: "13px", borderRadius: 10, border: "none", background: syncing ? T.sf : "linear-gradient(135deg,#3B82F6,#2563EB)", color: syncing ? T.dim : "#fff", fontSize: 14, fontWeight: 700, cursor: syncing ? "wait" : "pointer" }}>
          {syncing ? "저장중..." : initial ? "수정 완료" : "추가하기"}
        </button>
        {onDelete && <button onClick={onDelete} style={{ width: "100%", padding: "11px", borderRadius: 10, border: `1px solid ${T.danger}33`, background: "transparent", color: T.danger, fontSize: 12, fontWeight: 600, cursor: "pointer", marginTop: 6 }}>이 주소 삭제</button>}
      </div>
    </div>
  );
}

// ── Bulk Upload Modal ──
function BulkModal({ onUpload, onClose, syncing }) {
  const [text, setText] = useState("");
  const lines = text.trim().split("\n").filter(l => l.trim()).length;
  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "flex-end", justifyContent: "center", zIndex: 200 }}>
      <div style={{ width: "100%", maxWidth: 480, background: "#F1F5F9", borderRadius: "18px 18px 0 0", padding: "18px", border: "1px solid #334155" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10 }}>
          <div style={{ fontSize: 16, fontWeight: 700 }}>📋 일괄 추가</div>
          <button onClick={onClose} style={{ width: 30, height: 30, borderRadius: 7, border: "none", background: T.sf, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><Icon d={I.x} size={14} color={T.dim}/></button>
        </div>
        <div style={{ padding: "8px 10px", borderRadius: 7, background: `${T.primary}22`, marginBottom: 10, fontSize: 10, color: T.primary, lineHeight: 1.6 }}>
          <strong>형식:</strong> 업체명, 주소, 구, 동, 배송코드, 차량(A/B/C)<br/>
          <strong>예:</strong> 스타벅스 청담점, 도산대로 520, 강남구, 청담동, 청담2, A
        </div>
        <textarea value={text} onChange={e => setText(e.target.value)} placeholder="여기에 붙여넣기..." rows={7} style={{ width: "100%", padding: "10px", borderRadius: 8, background: T.sf, border: `1px solid ${T.border}`, color: T.text, fontSize: 11, resize: "vertical", outline: "none", boxSizing: "border-box", fontFamily: "monospace", lineHeight: 1.6 }}/>
        <div style={{ display: "flex", gap: 6, marginTop: 10 }}>
          <button onClick={onClose} style={{ flex: 1, padding: "11px", borderRadius: 10, border: `1px solid ${T.border}`, background: T.sf, color: T.muted, fontSize: 12, fontWeight: 600, cursor: "pointer" }}>취소</button>
          <button onClick={() => text.trim() && onUpload(text)} disabled={!text.trim() || syncing} style={{ flex: 2, padding: "11px", borderRadius: 10, border: "none", background: text.trim() && !syncing ? "linear-gradient(135deg,#3B82F6,#2563EB)" : T.sf, color: text.trim() && !syncing ? "#fff" : T.dim, fontSize: 12, fontWeight: 600, cursor: text.trim() && !syncing ? "pointer" : "not-allowed" }}>
            {syncing ? "업로드중..." : text.trim() ? `${lines}건 추가` : "데이터를 입력하세요"}
          </button>
        </div>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════
// MapTab - 네이버 지도 호차 배정 뷰
// ═══════════════════════════════════════
function BizNameEditor({ items, T, supabase, setPlanItems, setAllData }) {
  const bizName = items[0]?.biz_name || "";
  const [editing, setEditing] = React.useState(false);
  const [bizInput, setBizInput] = React.useState(bizName);

  React.useEffect(() => {
    setBizInput(items[0]?.biz_name || "");
  }, [items[0]?.biz_name]);

  const saveBizName = () => {
    if (bizInput === bizName) { setEditing(false); return; }
    items.forEach(function(item) {
      supabase.update("addresses", item.id, { biz_name: bizInput });
    });
    const repItem = items[0];
    if (repItem) {
      supabase.insert("change_log", {
        vehicle: repItem.vehicle,
        address_id: repItem.id,
        name: repItem.name,
        road_address: repItem.road_address,
        change_type: "업체명",
        change_detail: "업체/건물명: " + (bizName || "없음") + " → " + (bizInput || "없음"),
      });
    }
    setPlanItems(prev => prev.map(a => items.find(it=>it.id===a.id) ? {...a, biz_name: bizInput} : a));
    setAllData(prev => prev.map(a => items.find(it=>it.id===a.id) ? {...a, biz_name: bizInput} : a));
    setEditing(false);
  };

  if (editing) return (
    <input
      autoFocus
      value={bizInput}
      onChange={e => setBizInput(e.target.value)}
      onBlur={saveBizName}
      onKeyDown={e => { e.stopPropagation(); if (e.key === "Enter") saveBizName(); if (e.key === "Escape") setEditing(false); }}
      onClick={e => e.stopPropagation()}

      placeholder="업체/건물명 입력..."
      style={{ flex: 1, minWidth: 0, padding: "3px 8px", borderRadius: 6, border: "1.5px solid " + T.primary, background: "transparent", color: T.text, fontSize: 14, fontWeight: 600, outline: "none" }}
    />
  );
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 4, width: "100%" }}>
      {bizName ? (
        <div onClick={e => { e.stopPropagation(); setEditing(true); setBizInput(bizName); }} style={{
          fontSize: 15, fontWeight: 700, color: "#1D4ED8", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, minWidth: 0, cursor: "pointer",
        }}>{bizName}</div>
      ) : <div style={{ flex: 1 }}/>}
      <div onClick={e => { e.stopPropagation(); setEditing(true); setBizInput(bizName); }} style={{
        fontSize: 14, color: T.dim, cursor: "pointer", flexShrink: 0, padding: "2px 4px",
      }}>✏️</div>
    </div>
  );
}

// ── 텍스트 인라인 편집 컴포넌트 ──
function InlineEditor({ value, onSave, placeholder, fontSize, fontWeight, color, textDecoration, flex, T, pendingValue }) {
  const [editing, setEditing] = React.useState(false);
  const [input, setInput] = React.useState(value || "");

  React.useEffect(() => { if (!editing) setInput(pendingValue || value || ""); }, [value, pendingValue]);

  const save = async () => {
    const val = input.trim();
    if (val === (pendingValue || value || "")) { setEditing(false); return; }
    if (!val) { setEditing(false); return; }
    try {
      await onSave(val);
      setEditing(false);
    } catch(e) { alert("저장 실패: " + e.message); }
  };

  if (editing) return (
    <input
      autoFocus
      value={input}
      onChange={e => setInput(e.target.value)}
      onBlur={save}
      onKeyDown={e => { if (e.key === "Enter") save(); if (e.key === "Escape") { setInput(value || ""); setEditing(false); } }}
      placeholder={placeholder}
      style={{
        flex: flex || "unset", width: flex ? "100%" : "auto",
        padding: "3px 6px", borderRadius: 6,
        border: `1.5px solid ${T.primary}`, background: T.sf,
        color: T.text, fontSize: fontSize || 14, fontWeight: fontWeight || 400,
        outline: "none",
      }}
    />
  );

  return (
    <div
      onClick={() => setEditing(true)}
      style={{
        flex: flex || "unset", cursor: "pointer",
        fontSize: fontSize || 14, fontWeight: fontWeight || 400,
        color: pendingValue ? "#B45309" : (color || T.text),
        textDecoration: textDecoration || "none",
        borderBottom: `1px dashed ${pendingValue ? "#B45309" : T.border}`,
        paddingBottom: 1, minWidth: 0,
      }}
    >
      {pendingValue || value || <span style={{ color: T.dim }}>{placeholder}</span>}
      {pendingValue && <span style={{ fontSize: 10, color: "#B45309", marginLeft: 4 }}>*</span>}
    </div>
  );
}

// ── 층수/위치 인라인 편집 컴포넌트 ──
function FloorEditor({ item, supabase, setAllData, setDeliveryDetail, T, compact, onPending }) {
  const [editing, setEditing] = React.useState(false);
  const [input, setInput] = React.useState("");

  const save = async () => {
    let val = input.trim();
    if (/^\d+$/.test(val)) val = val + "층";
    if (val === (item.detail_addr || "")) { setEditing(false); return; }
    try {
      // onPending이 있으면 로컬 변경만 (저장 버튼으로 통합 저장)
      if (onPending) {
        onPending(val);
        setDeliveryDetail && setDeliveryDetail(prev => prev ? { ...prev, detail_addr: val } : null);
        setEditing(false);
        return;
      }
      const prev = item.detail_addr || "(없음)";
      await supabase.update("addresses", item.id, { detail_addr: val });
      await supabase.insert("change_log", { vehicle: item.vehicle, address_id: item.id, name: item.name, road_address: item.road_address, change_type: "층수", change_detail: "층수: " + prev + " → " + val });
      setAllData(prev => prev.map(a => a.id === item.id ? { ...a, detail_addr: val } : a));
      if (setDeliveryDetail) setDeliveryDetail(prev => prev ? { ...prev, detail_addr: val } : null);
      setEditing(false);
    } catch(e) { alert("저장 실패: " + e.message); }
  };

  if (editing) return (
    <input
      autoFocus
      value={input}
      onChange={e => setInput(e.target.value)}
      onBlur={save}
      onKeyDown={e => { if (e.key === "Enter") save(); if (e.key === "Escape") setEditing(false); }}
      placeholder="3"
      style={{
        width: compact ? 44 : 72, padding: "3px 6px", borderRadius: 6,
        border: `1.5px solid ${T.primary}`, background: T.sf,
        color: T.text, fontSize: 12, fontWeight: 700, outline: "none", textAlign: "center",
      }}
    />
  );

  return (
    <div onClick={() => { setInput(item.detail_addr || ""); setEditing(true); }} style={{
      padding: compact ? "2px 6px" : "3px 8px", borderRadius: 6, cursor: "pointer", flexShrink: 0,
      background: item.detail_addr ? T.primary + "22" : T.sf,
      border: `1px solid ${item.detail_addr ? T.primary + "55" : T.border}`,
      color: item.detail_addr ? T.primary : T.dim,
      fontSize: 11, fontWeight: 700, whiteSpace: "nowrap",
    }}>
      {item.detail_addr || "층✏️"}
    </div>
  );
}

function printChangelog(clData, clDateFrom, clDateTo, clVehicle) {
  const dateRange = clDateFrom === clDateTo ? clDateFrom : clDateFrom + " ~ " + clDateTo;
  const vehicleLabel = clVehicle === "전체" ? "전체 호차" : clVehicle + "호차";
  const vColors = {A:"#3B82F6",B:"#F59E0B",C:"#10B981",D:"#EC4899"};
  const typeColors = {"수량변경":"#3B82F6","상태변경":"#EF4444","메모":"#10B981","업체명":"#534AB7","업장명":"#534AB7","층수":"#BA7517","주소":"#993C1D"};
  let body = "";
  ["A","B","C","D"].forEach(function(v) {
    const vData = clVehicle === "전체" ? clData.filter(function(c){return c.vehicle===v;}) : (clVehicle===v ? clData : []);
    if (!vData.length) return;
    const vColor = vColors[v] || "#666";
    body += "<div class='vehicle' style='border-left-color:" + vColor + "'>" + v + "호차 (" + vData.length + "건)</div>";
    vData.forEach(function(c) {
      const time = c.created_at ? new Date(c.created_at).toLocaleString("ko-KR",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}) : "";
      const types = (c.change_type||"").split(",");
      let badges = "";
      types.forEach(function(t) {
        const tc = t.trim();
        const col = typeColors[tc] || "#666";
        badges += "<span class='badge' style='background:" + col + "22;color:" + col + "'>" + tc + "</span>";
      });
      const parts = (c.change_detail||"").split(" / ");
      let details = "";
      parts.forEach(function(d) {
        if (d.indexOf("→") >= 0) {
          const sp = d.split("→");
          details += "<div>• " + sp[0].trim() + " → <span class='arrow'>" + (sp[1]||"").trim() + "</span></div>";
        } else {
          details += "<div>• " + d + "</div>";
        }
      });
      body += "<div class='item'><span class='time'>" + time + "</span><div class='name'>" + (c.name||"") + "</div><div class='addr'>" + (c.road_address||"") + "</div><div class='badges'>" + badges + "</div><div class='detail'>" + details + "</div></div>";
    });
  });
  const html = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>변경내역</title><style>body{font-family:sans-serif;font-size:13px;padding:20px;color:#0F172A;}.sub{font-size:12px;color:#666;margin-bottom:16px;}.vehicle{background:#f0f0f0;padding:8px 12px;font-weight:700;font-size:14px;margin:12px 0 6px;border-left:4px solid #333;}.item{border-bottom:1px solid #eee;padding:8px 0;overflow:hidden;page-break-inside:avoid;}.name{font-weight:700;font-size:13px;}.addr{color:#666;font-size:11px;margin:2px 0;}.time{color:#999;font-size:11px;float:right;}.badges{margin:4px 0;}.badge{display:inline-block;padding:1px 7px;border-radius:3px;font-size:11px;font-weight:600;margin-right:4px;}.detail{font-size:12px;color:#444;margin-top:4px;line-height:1.7;}.arrow{color:#2563eb;font-weight:700;}@media print{body{padding:10px;}}</style></head><body><h2>📋 변경 내역</h2><div class='sub'>" + dateRange + " · " + vehicleLabel + " · 총 " + clData.length + "건</div>" + body + "</body></html>";
  const w = window.open("","_blank","width=800,height=900");
  w.document.write(html);
  w.document.close();
  setTimeout(function(){w.print();}, 500);
}

// ═══════════════════════════════════════
// 🚗 차량지정 탭 컴포넌트
// ═══════════════════════════════════════
function VehicleAssignTab({ vehicleInfo, setVehicleInfo, supabase, T }) {
  const VH_COLORS = VH_COLORS_GLOBAL;
  const [editInfo, setEditInfo] = useState(vehicleInfo.map(v => ({...v})));
  const [saving, setSaving] = useState(false);
  const [toast, setToast] = useState("");

  const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(""), 2500); };

  const saveVehicleInfo = async () => {
    setSaving(true);
    try {
      const val = JSON.stringify(editInfo);
      const existing = await supabase.fetch("settings", "key=eq.vehicles");
      if (existing.length > 0) {
        await supabase.updateByKey("settings", "key", "vehicles", { value: val });
      } else {
        await supabase.insert("settings", { key: "vehicles", value: val });
      }
      setVehicleInfo(editInfo.map(v => ({...v})));
      showToast("✅ 저장됨 · 전체 탭 반영");
    } catch(e) { showToast("저장 실패: " + e.message); }
    finally { setSaving(false); }
  };

  return (
    <div style={{ paddingBottom: 40 }}>
      <div style={{ background: T.card, borderRadius: 12, border: `1px solid ${T.border}`, padding: 16, marginBottom: 14 }}>
        <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 14, color: T.text }}>🚗 차량번호 · 기사 이름</div>
        {editInfo.map((v, i) => (
          <div key={v.id} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
            <div style={{ width: 12, height: 12, borderRadius: "50%", background: VH_COLORS[i], flexShrink: 0 }} />
            <input
              value={v.num}
              onChange={e => { const n = [...editInfo]; n[i] = {...n[i], num: e.target.value}; setEditInfo(n); }}
              placeholder="차량번호"
              style={{ width: 90, padding: "9px 10px", borderRadius: 8, border: `1px solid ${T.border}`, fontSize: 14, fontWeight: 800, background: T.sf, color: T.text }}
            />
            <input
              value={v.name}
              onChange={e => { const n = [...editInfo]; n[i] = {...n[i], name: e.target.value}; setEditInfo(n); }}
              placeholder="기사 이름"
              style={{ flex: 1, padding: "9px 10px", borderRadius: 8, border: `1px solid ${T.border}`, fontSize: 13, background: T.sf, color: T.text }}
            />
          </div>
        ))}
        <button onClick={saveVehicleInfo} disabled={saving} style={{
          width: "100%", marginTop: 4, padding: "10px", borderRadius: 8, border: "none",
          background: T.primary, color: "#fff", fontSize: 13, fontWeight: 700, cursor: "pointer"
        }}>{saving ? "저장 중…" : "저장 → 전체 탭 반영"}</button>
      </div>
      {toast && (
        <div style={{ position: "fixed", bottom: 20, left: "50%", transform: "translateX(-50%)", background: T.text, color: "#fff", padding: "7px 18px", borderRadius: 999, fontSize: 12, fontWeight: 600, zIndex: 400, pointerEvents: "none" }}>{toast}</div>
      )}
    </div>
  );
}

function ChangeLogTab({ supabase, T, VEHICLES, vc }) {
  const today = new Date().toISOString().split("T")[0];
  const [clDateFrom, setClDateFrom] = React.useState(today);
  const [clDateTo, setClDateTo] = React.useState(today);
  const [clVehicle, setClVehicle] = React.useState("전체");
  const [clData, setClData] = React.useState([]);
  const [clLoading, setClLoading] = React.useState(false);
  const [clVisible, setClVisible] = React.useState(false);
  const [clActiveVehicle, setClActiveVehicle] = React.useState("A");

  const fmt = dt => { const d = new Date(dt); const next = new Date(d); next.setDate(d.getDate()+1); return next.getFullYear()+"-"+String(next.getMonth()+1).padStart(2,"0")+"-"+String(next.getDate()).padStart(2,"0"); };

  const setRange = (days) => {
    const to = new Date(); const from = new Date();
    from.setDate(to.getDate() - days + 1);
    const f = d => d.getFullYear()+"-"+String(d.getMonth()+1).padStart(2,"0")+"-"+String(d.getDate()).padStart(2,"0");
    setClDateFrom(f(from)); setClDateTo(f(to));
  };

  const loadChangelog = async () => {
    setClLoading(true);
    try {
      let params = "order=created_at.desc&limit=500";
      if (clDateFrom) params += "&created_at=gte." + clDateFrom;
      if (clDateTo) params += "&created_at=lt." + fmt(clDateTo);
      if (clVehicle !== "전체") params += "&vehicle=eq." + clVehicle;
      const data = await supabase.fetch("change_log", params);
      const d = data || [];
      setClData(d);
      setClVisible(true);
      // 데이터 있는 첫 호차로 자동 선택
      const firstV = ["A","B","C","D"].find(v => d.some(c => c.vehicle === v));
      if (firstV) setClActiveVehicle(firstV);
    } catch(e) { alert("조회 실패: " + e.message); }
    setClLoading(false);
  };

  return (
    <div style={{ padding: "0 4px" }}>
      <div style={{ fontSize: 15, fontWeight: 700, marginBottom: 12 }}>📋 변경 내역</div>
      <div style={{ display: "flex", gap: 6, marginBottom: 8, flexWrap: "wrap" }}>
        {[["오늘", 1], ["3일", 3], ["1주", 7], ["1달", 30]].map(([label, days]) => (
          <button key={label} onClick={() => setRange(days)} style={{
            padding: "5px 12px", borderRadius: 6, border: "1px solid " + T.border,
            background: T.sf, color: T.text, fontSize: 12, fontWeight: 600, cursor: "pointer"
          }}>{label}</button>
        ))}
      </div>
      <div style={{ display: "flex", gap: 6, marginBottom: 10, flexWrap: "wrap", alignItems: "center" }}>
        <input type="date" value={clDateFrom} onChange={e => setClDateFrom(e.target.value)}
          style={{ padding: "7px 8px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 12, flex: 1 }}/>
        <span style={{ color: T.muted, fontSize: 12 }}>~</span>
        <input type="date" value={clDateTo} onChange={e => setClDateTo(e.target.value)}
          style={{ padding: "7px 8px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 12, flex: 1 }}/>
        <select value={clVehicle} onChange={e => setClVehicle(e.target.value)}
          style={{ padding: "7px 8px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 12 }}>
          <option>전체</option>
          {VEHICLES.map(v => <option key={v}>{v}</option>)}
        </select>
        <button onClick={loadChangelog} style={{ padding: "7px 14px", borderRadius: 8, border: "none", background: T.primary, color: "#fff", fontSize: 13, fontWeight: 700, cursor: "pointer" }}>
          {clLoading ? "조회중..." : "조회"}
        </button>
      </div>
      {clData.length === 0 && !clLoading && !clVisible && (
        <div style={{ textAlign: "center", padding: 30, color: T.dim, fontSize: 13 }}>조회 버튼을 눌러주세요</div>
      )}
      {clVisible && clData.length === 0 && !clLoading && (
        <div style={{ textAlign: "center", padding: 30, color: T.dim, fontSize: 13 }}>해당 기간 변경 내역이 없습니다</div>
      )}
      {clVisible && clData.length > 0 && (
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
          <span style={{ fontSize: 12, color: T.muted }}>총 {clData.length}건</span>
          <div style={{ display: "flex", gap: 6 }}>
            <button onClick={() => printChangelog(clData.filter(c => c.vehicle === clActiveVehicle), clDateFrom, clDateTo, clActiveVehicle)} style={{
              padding: "4px 12px", borderRadius: 6, border: "1px solid " + T.border,
              background: T.sf, color: T.text, fontSize: 12, cursor: "pointer", fontWeight: 600
            }}>🖨️ 인쇄</button>
            <button onClick={() => { setClData([]); setClVisible(false); }} style={{
              padding: "4px 12px", borderRadius: 6, border: "1px solid " + T.border,
              background: T.sf, color: T.muted, fontSize: 12, cursor: "pointer"
            }}>✕ 닫기</button>
          </div>
        </div>
      )}
      {clVisible && clData.length > 0 && (
        <div>
          <div style={{ display: "flex", gap: 4, marginBottom: 10 }}>
            {["A","B","C","D"].map(v => {
              const cnt = clData.filter(c => c.vehicle === v).length;
              return (
                <button key={v} onClick={() => setClActiveVehicle(v)} style={{
                  flex: 1, padding: "8px 0", borderRadius: 8, border: "none", cursor: "pointer",
                  background: clActiveVehicle === v ? vc(v) : T.sf,
                  color: clActiveVehicle === v ? "#fff" : T.muted,
                  fontSize: 12, fontWeight: 700,
                }}>
                  {v}호차
                  <div style={{ fontSize: 10, color: clActiveVehicle === v ? "#ffffffaa" : T.dim }}>{cnt}건</div>
                </button>
              );
            })}
          </div>
          {(() => {
            const vData = clData.filter(c => c.vehicle === clActiveVehicle);
            if (vData.length === 0) return <div style={{ textAlign: "center", padding: 20, color: T.dim, fontSize: 13 }}>변경 내역 없음</div>;
            return (
              <div style={{ borderRadius: 10, overflow: "hidden", border: "1px solid " + T.border }}>
                {vData.map((c, i) => (
                  <div key={i} style={{ padding: "10px 14px", borderTop: i > 0 ? "1px solid " + T.border : "none", background: T.card }}>
                    <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
                      <span style={{ fontSize: 13, fontWeight: 600, color: T.text }}>{c.name}</span>
                      <span style={{ fontSize: 11, color: T.muted }}>{c.created_at ? new Date(c.created_at).toLocaleTimeString("ko-KR", {hour:"2-digit",minute:"2-digit"}) : ""}</span>
                    </div>
                    <div style={{ fontSize: 11, color: T.muted, marginBottom: 4 }}>{c.road_address}</div>
                    <div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginBottom: 3 }}>
                      {(c.change_type||"").split(",").map((t,ti) => {
                        const tt = t.trim();
                        const COLOR_MAP = {
                          "수량변경": ["#DBEAFE","#1D4ED8"],
                          "상태변경": ["#FEE2E2","#B91C1C"],
                          "메모":     ["#DCFCE7","#166534"],
                          "업체명":   ["#EEEDFE","#3C3489"],
                          "업장명":   ["#EEEDFE","#3C3489"],
                          "층수":     ["#FAEEDA","#633806"],
                          "주소":     ["#FAECE7","#712B13"],
                        };
                        const [bg, col] = COLOR_MAP[tt] || ["#F1F5F9","#64748B"];
                        return <span key={ti} style={{ fontSize: 12, padding: "2px 8px", borderRadius: 4, background: bg, color: col, fontWeight: 700 }}>{tt}</span>;
                      })}
                    </div>
                    {c.change_detail && (
                      <div style={{ fontSize: 12, color: T.text, marginTop: 3, lineHeight: 1.6 }}>
                        {c.change_detail.split(" / ").map((d, di) => (
                          <div key={di} style={{ display: "flex", alignItems: "center", gap: 4 }}>
                            <span style={{ color: T.muted, fontSize: 11 }}>•</span>
                            {d.includes("→") ? (
                              <span>
                                <span style={{ color: T.muted }}>{d.split("→")[0].trim()}</span>
                                <span style={{ color: T.muted, margin: "0 4px" }}>→</span>
                                <span style={{ color: T.accent, fontWeight: 700 }}>{d.split("→")[1]?.trim()}</span>
                              </span>
                            ) : <span style={{ color: T.muted }}>{d}</span>}
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                ))}
              </div>
            );
          })()}
        </div>
      )}
    </div>
  );
}

function MapTab({ addresses, assignedItems, vehicleCounts, VEHICLES, MAGAZINES, ROUTE_CODES, GU_LIST, vc, T, I, Icon, supabase, extraMags, mapExtraMagGlobal, setMapExtraMagGlobal, allData, setAllData, allDataRef, loadAllData, allDataReady, resetMapKey, vehicleInfo }) {
  var EXTRA_MAGS_REF = extraMags || [];

  // 마감 시 zoneColorMap 초기화
  useEffect(function() {
    if (!resetMapKey) return;
    setZoneColorMap({});
    zoneColorMapRef.current = {};
    setMapCodes([]);
    setMapVehicle("all");
    // 마커 강제 제거
    if (markersRef.current) {
      markersRef.current.forEach(function(m) { try { m.setMap(null); } catch(e) {} });
      markersRef.current = [];
    }
    if (infoRef.current) { try { infoRef.current.close(); } catch(e) {} infoRef.current = null; }
  }, [resetMapKey]);
  // 전역 함수: allData 기반으로 즉시 업데이트
  React.useEffect(function() {
    window.__updateAllMapItems = function(ids, mag, qty) {
      setAllData(function(prev) {
        return prev.map(function(a) {
          if (ids.indexOf(a.id) >= 0) {
            var newMq = Object.assign({}, a.magazine_qty || {});
            newMq[mag] = qty;
            return Object.assign({}, a, { magazine_qty: newMq });
          }
          return a;
        });
      });
    };
    window.__reloadMapData = function() { if (loadAllData) loadAllData(); };
    window.__getAllMapItems = function() { return allDataRef ? allDataRef.current : []; };
    return function() { delete window.__updateAllMapItems; delete window.__reloadMapData; delete window.__getAllMapItems; };
  }, []);
  var mapRef = useRef(null);
  var mapInstance = useRef(null);
  var markersRef = useRef([]);
  var infoRef = useRef(null);
  var [mapReady, setMapReady] = useState(false);
  var [mapVehicle, setMapVehicle] = useState("all");
  var [mapCodes, setMapCodes] = useState([]);
  var [zoneColorMap, setZoneColorMap] = useState(function() {
    try { return JSON.parse(localStorage.getItem("dm_zoneColorMap") || "{}"); } catch(e) { return {}; }
  });
  var zoneColorMapRef = useRef(zoneColorMap);
  useEffect(function() { zoneColorMapRef.current = zoneColorMap; }, [zoneColorMap]);
  var setZoneColor = function(code, v) {
    var next = Object.assign({}, zoneColorMap);
    if (next[code] === v) delete next[code]; else next[code] = v;
    setZoneColorMap(next);
    try { localStorage.setItem("dm_zoneColorMap", JSON.stringify(next)); } catch(e) {}
  };
  var [mapGus, setMapGus] = useState([]);
  var [showCodeFilter, setShowCodeFilter] = useState(false);
  var [showGuFilter, setShowGuFilter] = useState(false);
  var mapExtraMag = mapExtraMagGlobal;
  var setMapExtraMag = setMapExtraMagGlobal;
  var [geocoding, setGeocoding] = useState(false);
  var [gcProgress, setGcProgress] = useState({ done: 0, total: 0 });
  var [mapLoading, setMapLoading] = useState(false);
  var [coordCache, setCoordCache] = useState(function() {
    try { var v = localStorage.getItem("dm_coordCache"); return v ? JSON.parse(v) : {}; } catch(e) { return {}; }
  });

  // v17: 호차 배정 상태
  var [assignMode, setAssignMode] = useState(null);
  var [dragRect, setDragRect] = useState(null);
  var [selectedPins, setSelectedPins] = useState([]);
  var [assigning, setAssigning] = useState(false);
  var [routeLine, setRouteLine] = useState(null); // 최적 경로 Polyline
  var routeLineDataRef = useRef(null); // 경로 데이터 ref (마커 useEffect에서 참조용)
  var [routeInfo, setRouteInfo] = useState(null); // {vehicle, distKm, bldgCount}
  var routeLineRef = useRef(null);
  var routeMarkersRef = useRef([]);
  // 멀티 경로선: 호차별 저장
  var multiRouteLineRef = useRef({}); // {A: Polyline, B: Polyline, ...}
  var multiRouteMarkersRef = useRef({}); // {A: [Marker,...], B: [...], ...}
  var [selectionBounds, setSelectionBounds] = useState(null); // {latMin,latMax,lngMin,lngMax}
  var dragStartRef = useRef(null);
  var selRectRef = useRef(null); // 네이버 지도 Rectangle
  var didFitRef = useRef(false); // fitBounds 최초 1회만 실행
  var [showUncached, setShowUncached] = useState(false); // 미변환 목록 표시
  var [mapFullscreen, setMapFullscreen] = useState(false); // 지도 전체화면

  // v19: 경로 그리기 모드
  var [drawMode, setDrawMode] = useState(false);
  var [routeCodeModal, setRouteCodeModal] = useState(null); // {addr, currentCode, items}
  var [routeCodeInput, setRouteCodeInput] = useState("");
  var [drawRoute, setDrawRoute] = useState([]); // [{addr, lat, lng, items}, ...]
  var drawLineRef = useRef(null); // 그리기 모드 Polyline
  var drawMarkersRef = useRef([]); // 순번 마커들
  var [drawSaving, setDrawSaving] = useState(false);
  var drawModeRef = useRef(false);
  var drawRouteRef = useRef([]);

  // 지도 새로고침 = loadAllData 사용
  var loadMapData = function() {
    setMapLoading(true);
    if (loadAllData) loadAllData().then(function(){ setMapLoading(false); }).catch(function(){ setMapLoading(false); });
  };

  // 좌표 캐시 저장
  useEffect(function() {
    try { localStorage.setItem("dm_coordCache", JSON.stringify(coordCache)); } catch(e) {}
  }, [coordCache]);

  // v19: drawMode/drawRoute ref 동기화 (클로저 문제 방지)
  useEffect(function() { drawModeRef.current = drawMode; }, [drawMode]);
  useEffect(function() { drawRouteRef.current = drawRoute; }, [drawRoute]);

  // v19: 경로 그리기 Polyline (본부→1→2→... 연결선만, 번호는 본 핀에 통합)
  useEffect(function() {
    if (drawLineRef.current) { drawLineRef.current.setMap(null); drawLineRef.current = null; }
    if (!drawMode || drawRoute.length === 0 || !mapInstance.current || !window.naver || !window.naver.maps) return;
    var fullPath = [];
    drawRoute.forEach(function(p) { fullPath.push(new window.naver.maps.LatLng(p.lat, p.lng)); });
    drawLineRef.current = new window.naver.maps.Polyline({
      map: mapInstance.current,
      path: fullPath,
      strokeColor: (function(v){return {A:'#3B82F6',B:'#F59E0B',C:'#10B981',D:'#E91E63'}[v]||'#F97316';})(mapVehicle), strokeWeight: 4, strokeOpacity: 0.9, strokeStyle: 'solid',
    });
  }, [drawMode, drawRoute]);

  // v19: 경로 그리기 모드 진입/해제 시 인포윈도우 닫기
  useEffect(function() {
    if (drawMode && infoRef.current) { infoRef.current.close(); infoRef.current = null; }
  }, [drawMode]);

  // v17: 선택 영역 Rectangle 표시/제거
  useEffect(function() {
    if (selRectRef.current) { selRectRef.current.setMap(null); selRectRef.current = null; }
    if (!selectionBounds || !mapInstance.current || !window.naver || !window.naver.maps) return;
    var b = selectionBounds;
    selRectRef.current = new window.naver.maps.Rectangle({
      map: mapInstance.current,
      bounds: new window.naver.maps.LatLngBounds(
        new window.naver.maps.LatLng(b.latMin, b.lngMin),
        new window.naver.maps.LatLng(b.latMax, b.lngMax)
      ),
      strokeColor: '#F59E0B', strokeWeight: 2, strokeOpacity: 0.8, strokeStyle: 'dash',
      fillColor: '#F59E0B', fillOpacity: 0.1,
    });
  }, [selectionBounds]);

  // v18: 최적 경로 라인 + 번호 마커 그리기
  useEffect(function() {
    // 기존 경로선 제거
    if (routeLineRef.current) { routeLineRef.current.setMap(null); routeLineRef.current = null; }
    // 기존 번호 마커 제거
    if (routeMarkersRef.current.length > 0) {
      routeMarkersRef.current.forEach(function(m) { m.setMap(null); });
      routeMarkersRef.current = [];
    }
    if (!routeLine || !mapInstance.current || !window.naver || !window.naver.maps) return;
    
    // 경로 연결선 (굵은 보라색 실선)
    routeLineRef.current = new window.naver.maps.Polyline({
      map: mapInstance.current,
      path: routeLine.map(function(c) { return new window.naver.maps.LatLng(c.lat, c.lng); }),
      strokeColor: '#7C3AED', strokeWeight: 5, strokeOpacity: 0.9, strokeStyle: 'solid',
    });
    
    // 출발지 마커
    var startMarker = new window.naver.maps.Marker({
      position: new window.naver.maps.LatLng(routeLine[0].lat, routeLine[0].lng),
      map: mapInstance.current, zIndex: 500,
      icon: { content: '<div style="background:#10B981;color:#fff;padding:5px 12px;border-radius:14px;font-size:13px;font-weight:800;border:3px solid #fff;box-shadow:0 3px 12px rgba(0,0,0,0.5);white-space:nowrap;">🚩 본부</div>', anchor: new window.naver.maps.Point(38, 45) },
    });
    routeMarkersRef.current.push(startMarker);
    
    // 경유지 순번 라벨 (핀 아래 작은 태그, 클릭 투과)
    routeLine.forEach(function(c, idx) {
      if (idx === 0 || idx === routeLine.length - 1) return;
      var m = new window.naver.maps.Marker({
        position: new window.naver.maps.LatLng(c.lat, c.lng),
        map: mapInstance.current, zIndex: 10, clickable: false,
        icon: {
          content: '<div style="background:#7C3AED;color:#fff;padding:1px 4px;border-radius:5px;font-size:9px;font-weight:800;border:1px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,0.3);white-space:nowrap;pointer-events:none;">' + idx + '번</div>',
          anchor: new window.naver.maps.Point(16, -4),
        },
      });
      routeMarkersRef.current.push(m);
    });
    
    // 도착지 마커
    var endMarker = new window.naver.maps.Marker({
      position: new window.naver.maps.LatLng(routeLine[routeLine.length - 1].lat, routeLine[routeLine.length - 1].lng),
      map: mapInstance.current, zIndex: 500,
      icon: { content: '<div style="background:#EF4444;color:#fff;padding:5px 12px;border-radius:14px;font-size:13px;font-weight:800;border:3px solid #fff;box-shadow:0 3px 12px rgba(0,0,0,0.5);white-space:nowrap;">🏁 복귀</div>', anchor: new window.naver.maps.Point(38, 45) },
    });
    routeMarkersRef.current.push(endMarker);
    
    // 경로 전체가 보이도록 지도 범위 조정
    var bounds = new window.naver.maps.LatLngBounds();
    routeLine.forEach(function(c) { bounds.extend(new window.naver.maps.LatLng(c.lat, c.lng)); });
    mapInstance.current.fitBounds(bounds, { top: 60, right: 40, bottom: 40, left: 40 });
  }, [routeLine]);

  // v17: 전체화면 변경 시 지도 리사이즈
  useEffect(function() {
    if (!mapInstance.current || !window.naver || !window.naver.maps) return;
    setTimeout(function() { window.naver.maps.Event.trigger(mapInstance.current, "resize"); }, 350);
  }, [mapFullscreen]);

  // v17: 영역선택 모드 시 지도 드래그/터치 비활성화
  useEffect(function() {
    if (!mapInstance.current || !window.naver || !window.naver.maps) return;
    if (assignMode === "drag") {
      mapInstance.current.setOptions({ draggable: false, scrollWheel: false, pinchZoom: false });
    } else {
      mapInstance.current.setOptions({ draggable: true, scrollWheel: true, pinchZoom: true });
    }
  }, [assignMode]);

  // 지도 초기화
  useEffect(function() {
    function initMap() {
      if (mapInstance.current || !mapRef.current || !window.naver || !window.naver.maps || !window.naver.maps.Map) return;
      var map = new window.naver.maps.Map(mapRef.current, {
        center: new window.naver.maps.LatLng(37.517, 127.040),
        zoom: 13,
        zoomControl: true,
        zoomControlOptions: { position: window.naver.maps.Position.RIGHT_CENTER, style: window.naver.maps.ZoomControlStyle.SMALL },
      });
      mapInstance.current = map;
      window.__mapInstance = map;
      // 지도 빈 곳 클릭 시 인포윈도우 닫기
      window.naver.maps.Event.addListener(map, "click", function() {
        if (infoRef.current) { infoRef.current.close(); infoRef.current = null; }
      });
      setMapReady(true);
    }
    if (window.naver && window.naver.maps && window.naver.maps.Map) { initMap(); return; }
    var iv = setInterval(function() {
      if (window.naver && window.naver.maps && window.naver.maps.Map) { clearInterval(iv); initMap(); }
    }, 500);
    var to = setTimeout(function() { clearInterval(iv); }, 15000);
    return function() { clearInterval(iv); clearTimeout(to); };
  }, []);

  // 주소→좌표 변환
  var geocodeAddr = useCallback(function(address) {
    return new Promise(function(resolve) {
      if (!window.naver || !window.naver.maps || !window.naver.maps.Service) { resolve(null); return; }
      window.naver.maps.Service.geocode({ query: address }, function(status, response) {
        if (status !== window.naver.maps.Service.Status.OK || !response.v2.addresses.length) { resolve(null); return; }
        var r = response.v2.addresses[0];
        resolve({ lat: parseFloat(r.y), lng: parseFloat(r.x) });
      });
    });
  }, []);

  // 모든 아이템 (전체 DB에서 로드한 데이터)
  var allItems = useMemo(function() {
    return allData && allData.length > 0 ? allData : addresses;
  }, [allData, addresses, mapExtraMag, mapCodes, mapGus, mapVehicle]);

  // 필터 변경 시 fitBounds 허용
  useEffect(function() { didFitRef.current = false; }, [mapCodes, mapGus]); // mapVehicle 제거: 호차탭 전환시 fitBounds 안함

  // 건물 그룹
  var buildings = useMemo(function() {
    var g = {};
    allItems.forEach(function(a) {
      if (mapVehicle !== "all" && a.vehicle !== mapVehicle) return;
      if (mapCodes.length > 0 && mapCodes.indexOf(a.route_code) === -1) return;
      if (mapExtraMag) {
        var mq2 = a.magazine_qty || {};
        if (typeof mq2 === "string") { try { mq2 = JSON.parse(mq2); } catch(e) { mq2 = {}; } }
        if (!mq2[mapExtraMag] || mq2[mapExtraMag] <= 0) return;
      }
      if (mapGus.length > 0 && mapGus.indexOf(a.gu) === -1) return;
      var key = a.road_address || a.name;
      if (!g[key]) g[key] = { addr: key, items: [], gu: a.gu, dong: a.dong, route_code: a.route_code };
      g[key].items.push(a);
    });
    return g;
  }, [allItems, mapVehicle, mapCodes, mapGus, mapExtraMag, allDataReady]);

  // 마커 그리기
  var markerDrawTimerRef = useRef(null);
  useEffect(function() {
    if (!mapReady || !mapInstance.current || !window.naver || !window.naver.maps || !window.naver.maps.LatLngBounds) return;
    // allData 로딩 완전 완료 후에만 실행 (중복 방지)
    if (!allDataReady) return;

    // 기존 마커 즉시 제거 (debounce로 중복 방지)
    var oldMarkers = markersRef.current.slice();
    markersRef.current = [];
    oldMarkers.forEach(function(m) { try { m.setMap(null); } catch(e) {} });
    if (infoRef.current) { try { infoRef.current.close(); } catch(e) {} infoRef.current = null; }

    if (markerDrawTimerRef.current) clearTimeout(markerDrawTimerRef.current);
    markerDrawTimerRef.current = setTimeout(function() {
    if (!mapReady || !mapInstance.current || !window.naver || !window.naver.maps) return;

    // v19: 본부(출발지) 마커 항상 표시
    (function() {
      var hqM = new window.naver.maps.Marker({
        position: new window.naver.maps.LatLng(DEFAULT_START.lat, DEFAULT_START.lng),
        map: mapInstance.current, zIndex: 900,
        icon: {
          content: '<div style="cursor:pointer;filter:drop-shadow(0 2px 4px rgba(0,0,0,0.4));">' +
            '<div style="background:#111827;color:#fff;padding:5px 10px;border-radius:12px;font-size:12px;font-weight:800;border:2px solid #fff;white-space:nowrap;display:flex;align-items:center;gap:4px;">' +
            '🚩 본부</div>' +
            '<div style="width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:8px solid #111827;margin:0 auto;"></div>' +
          '</div>',
          anchor: new window.naver.maps.Point(40, 42),
        },
      });
      window.naver.maps.Event.addListener(hqM, "click", function() {
        if (infoRef.current) infoRef.current.close();
        var iw = new window.naver.maps.InfoWindow({
          content: '<div style="padding:10px 14px;min-width:180px;font-family:-apple-system,sans-serif;background:#fff;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,0.15);">' +
            '<div style="font-size:14px;font-weight:700;color:#111;">🚩 ' + DEFAULT_START.name + '</div>' +
            '<div style="font-size:11px;color:#888;margin-top:4px;">출발지 · 복귀지</div>' +
            '<div style="margin-top:6px;"><a href="https://map.naver.com/v5/search/' + encodeURIComponent(DEFAULT_START.name) + '" target="_blank" style="display:inline-block;padding:5px 12px;background:#03C75A;color:#fff;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;">🧭 길안내</a></div>' +
          '</div>',
          borderWidth: 0, backgroundColor: "transparent", borderColor: "transparent",
          anchorSize: new window.naver.maps.Size(10, 10),
          pixelOffset: new window.naver.maps.Point(0, -5),
        });
        iw.open(mapInstance.current, hqM);
        infoRef.current = iw;
      });
      markersRef.current.push(hqM);
    })();

    Object.keys(buildings).forEach(function(addr) {
      var bldg = buildings[addr];
      var coord = coordCache[addr];
      if (!coord || coord.failed) return;

      var items = bldg.items;
      var vSet = [];
      items.forEach(function(a) { if (a.vehicle && vSet.indexOf(a.vehicle) === -1) vSet.push(a.vehicle); });
      var mainV = vSet[0] || null;
      var allDone = items.every(function(a) { return a.status === "done"; });
      var totalMq = items.reduce(function(s, a) {
        var mq = a.magazine_qty || {};
        return s + Object.values(mq).reduce(function(ss, v) { return ss + (v || 0); }, 0);
      }, 0);

      var color = mainV ? vc(mainV) : "#64748B";
      // 구역 색상 설정 적용 - 업체의 route_code에 색상이 지정되어 있으면 우선 적용
      var routeCode = items[0] ? items[0].route_code : null;
      if (routeCode && zoneColorMapRef.current[routeCode]) {
        color = vc(zoneColorMapRef.current[routeCode]);
      }
      var markerColor = allDone ? '#10B981' : color;
      // v19: 경로 그리기 모드에서 선택된 핀 강조
      var drawSelected = false;
      var drawIdx = -1;
      if (drawMode && drawRoute.length > 0) {
        drawRoute.forEach(function(p, i) { if (p.addr === addr) { drawSelected = true; drawIdx = i; } });
      }
      var pinH = drawSelected ? 20 : (items.length >= 5 ? 17 : items.length >= 2 ? 14 : 12);
      var pinW = Math.round(pinH * 0.75);
      // sort_order 번호 표시: 배정된 핀은 최소 sort_order, 미배정은 업장 수
      var sortNums = items.map(function(a) { return a.sort_order; }).filter(function(n) { return n != null && n > 0; });
      var sortNum = sortNums.length > 0 ? Math.min.apply(null, sortNums) : null;
      var label = drawSelected ? (drawIdx + 1) : (allDone ? '✓' : items.length);
      // SVG는 viewBox="0 0 30 40" 기준 고정 font-size 사용
      var svgFontSize = drawSelected ? 13 : (String(label).length >= 3 ? 10 : 13);
      if (drawSelected) markerColor = (function(v){return {A:'#3B82F6',B:'#F59E0B',C:'#10B981',D:'#E91E63'}[v]||'#F97316';})(mapVehicle);

      var markerZIndex = drawSelected ? 200 : 50;
      var marker = new window.naver.maps.Marker({
        position: new window.naver.maps.LatLng(coord.lat, coord.lng),
        map: mapInstance.current,
        zIndex: markerZIndex,
        icon: {
          content: '<div style="position:relative;cursor:pointer;user-select:none;-webkit-user-select:none;' + (allDone ? 'opacity:0.55;' : '') + '">' +
            '<svg width="' + pinW + '" height="' + pinH + '" viewBox="0 0 30 40">' +
              '<path d="M15 0C6.7 0 0 6.7 0 15c0 11.25 15 25 15 25s15-13.75 15-25C30 6.7 23.3 0 15 0z" fill="' + markerColor + '"/>' +
              '<path d="M15 0C6.7 0 0 6.7 0 15c0 11.25 15 25 15 25s15-13.75 15-25C30 6.7 23.3 0 15 0z" fill="none" stroke="' + (drawSelected ? '#F97316' : '#fff') + '" stroke-width="' + (drawSelected ? '3' : '2') + '"/>' +
              '<circle cx="15" cy="14" r="9" fill="rgba(255,255,255,0.25)"/>' +
              '<text x="15" y="19" text-anchor="middle" dominant-baseline="middle" fill="#fff" font-size="' + svgFontSize + '" font-weight="800" font-family="Arial,sans-serif">' + label + '</text>' +
            '</svg>' +
          '</div>',
          anchor: new window.naver.maps.Point(pinW / 2, pinH),
        },
      });

      window.naver.maps.Event.addListener(marker, "click", function() {
        // v19: 경로 그리기 모드 — 핀 클릭 시 경로 추가/제거 후 인포윈도우도 표시
        var drawNum = null; // 이 핀의 경로 번호
        if (drawModeRef.current) {
          var curRoute = drawRouteRef.current;
          var alreadyIdx = -1;
          curRoute.forEach(function(p, i) { if (p.addr === addr) alreadyIdx = i; });
          if (alreadyIdx >= 0) {
            // 이미 선택된 핀: 위치 상관없이 제거, 뒤 번호 자동 당김
            setDrawRoute(function(prev) { return prev.filter(function(_, i) { return i !== alreadyIdx; }); });
            drawNum = null;
          } else {
            var c = coordCache[addr];
            if (c) {
              setDrawRoute(function(prev) { return prev.concat([{ addr: addr, lat: c.lat, lng: c.lng, items: items }]); });
              drawNum = curRoute.length + 1;
            }
          }
          // 인포윈도우도 표시 (아래로 계속)
        }
        if (infoRef.current) infoRef.current.close();
        // 글로벌 닫기 함수 등록
        window.__closeMapInfo = function() { if (infoRef.current) { infoRef.current.close(); infoRef.current = null; } window.__mapInfoAddr = null; };
        window.__assignVehicle = function(ba, v) { assignVehicleToBuilding(ba, v); };
        window.__unassignBldg = function(ba) { unassignBuilding(ba); };
        window.__openRouteCodeModal = function(ba, currentCode) {
          setRouteCodeModal({ addr: ba, currentCode: currentCode });
          setRouteCodeInput(currentCode || "");
          if (infoRef.current) { infoRef.current.close(); infoRef.current = null; }
        };
        window.__changeRouteCode = function(ba, newCode) {
          if (!newCode || !newCode.trim()) return;
          var items = allItems.filter(function(a) { return (a.road_address || a.name) === ba; });
          if (items.length === 0) return;
          Promise.all(items.map(function(a) {
            return supabase.update("addresses", a.id, { route_code: newCode.trim() });
          })).then(function() {
            setAllData(function(prev) {
              return prev.map(function(a) {
                if ((a.road_address || a.name) === ba) return Object.assign({}, a, { route_code: newCode.trim() });
                return a;
              });
            });
            window.__closeMapInfo();
            alert("배송코드 변경 완료: " + newCode.trim());
          }).catch(function(e) { alert("변경 실패: " + e.message); });
        };
        // 키보드 단축키: 인포윈도우 열려있을 때 1/2/3/4=호차배정, 0=해제
        if (!window.__keyHandler) {
          window.__keyHandler = function(e) {
            if (!window.__mapInfoAddr) return;
            var keyMap = {"1":"A","2":"B","3":"C","4":"D"};
            if (keyMap[e.key]) {
              window.__assignVehicle(window.__mapInfoAddr, keyMap[e.key]);
              window.__closeMapInfo();
            } else if (e.key === "0") {
              window.__unassignBldg(window.__mapInfoAddr);
              window.__closeMapInfo();
            }
          };
          document.addEventListener("keydown", window.__keyHandler);
        }
        // v17: 주소를 글로벌에 저장 (이스케이프 문제 방지)
        window.__mapInfoAddr = addr;
        // DB 키 정규화 함수 (다양한 키명 → 표준명)
        var normalizeMagKey = function(k) {
          var map = {"MEN":"맨","ART":"아트","아트":"아트","워치&주얼리":"워치","워치":"워치","노블레스":"노블레스","맨":"맨","Y":"Y","웨딩":"웨딩","타임북":"타임북"};
          return map[k] || k;
        };
        var MAG_ORDER = ["노블레스","맨","Y","웨딩","아트","워치","타임북"];
        var MAG_ABBR = {"노블레스":"노블","맨":"맨","아트":"아트","Y":"Y","워치":"워치","웨딩":"웨딩","타임북":"타임"};
        var MAG_COLOR = {"노블레스":"#1D4ED8","맨":"#6D28D9","아트":"#B45309","Y":"#047857","워치":"#B91C1C","웨딩":"#9D174D","타임북":"#4338CA"};
        var html = items.map(function(a) {
          var rawMq = a.magazine_qty || {};
          if (typeof rawMq === "string") { try { rawMq = JSON.parse(rawMq); } catch(e) { rawMq = {}; } }
          // 키 정규화 후 합산
          var mq = {};
          Object.keys(rawMq).forEach(function(k) {
            var nk = normalizeMagKey(k);
            var v = rawMq[k];
            if (typeof v === "object") return; // 잘못된 중첩 객체 무시
            mq[nk] = (mq[nk] || 0) + (Number(v) || 0);
          });
          // 순서 고정
          var orderedKeys = MAG_ORDER.filter(function(k){ return mq[k] > 0; });
          Object.keys(mq).forEach(function(k){ if (MAG_ORDER.indexOf(k) === -1 && mq[k] > 0) orderedKeys.push(k); });
          var mqStr = orderedKeys.map(function(k){
            var abbr = MAG_ABBR[k] || k;
            var color = MAG_COLOR[k] || "#64748b";
            return '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:' + color + '22;color:' + color + ';font-weight:600;margin-left:2px;">' + abbr + mq[k] + '</span>';
          }).join('');
          var icon = a.status === "done" ? "✅" : "⏳";
          var rejStatus = a.rejection_status || "";
          var nameStyle = rejStatus ? "color:#94a3b8;text-decoration:line-through;" : "color:#333;";
          var rejBadge = rejStatus === "거부" ? '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:#EF444422;color:#EF4444;font-weight:700;margin-left:3px;">거부</span>'
                       : rejStatus === "폐업" ? '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:#94a3b822;color:#94a3b8;font-weight:700;margin-left:3px;">폐업</span>'
                       : rejStatus === "중지" ? '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:#F59E0B22;color:#F59E0B;font-weight:700;margin-left:3px;">중지</span>' : "";
          return '<div style="padding:5px 0;font-size:12px;border-bottom:1px solid #eee;display:flex;align-items:center;flex-wrap:wrap;gap:2px;">' + icon + ' ' + (a.detail_addr ? '<b style="color:#111;margin:0 2px;">' + a.detail_addr + '</b>' : '') + '<span style="' + nameStyle + '">' + a.name + '</span>' + rejBadge + (a.vehicle ? '<span style="color:' + vc(a.vehicle) + ';font-weight:700;margin-left:3px;">' + a.vehicle + '</span>' : '') + (mqStr && !rejStatus ? '<span style="margin-left:4px;">' + mqStr + '</span>' : '') + '</div>';
        }).join('');

        var iw = new window.naver.maps.InfoWindow({
          content: '<div style="position:relative;padding:12px 14px;padding-right:30px;min-width:220px;max-width:320px;font-family:-apple-system,sans-serif;color:#333;background:#fff;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,0.15);">' +
            '<div onclick="window.__closeMapInfo()" style="position:absolute;top:8px;right:10px;width:22px;height:22px;border-radius:50%;background:#f1f5f9;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:13px;color:#94a3b8;font-weight:700;">✕</div>' +
            '<div style="font-size:14px;font-weight:700;color:#111;margin-bottom:3px;">' + addr + '</div>' +
            '<div style="font-size:11px;color:#888;margin-bottom:8px;">' + (bldg.route_code || '') + ' · ' + (bldg.gu || '') + ' · ' + items.length + '개 업장 · ' + totalMq + '권</div>' +
            '<div style="max-height:180px;overflow-y:auto;">' + html + '</div>' +
            '<div style="margin-top:8px;display:flex;gap:4px;flex-wrap:wrap;align-items:center;">' +
            '<span style="font-size:10px;color:#888;margin-right:2px;">배정:</span>' +
            ['A','B','C','D'].map(function(vh) {
              var vColors = {A:'#3B82F6',B:'#F59E0B',C:'#10B981',D:'#E91E63'};
              var isActive = items.length > 0 && items[0].vehicle === vh;
              return '<button onclick="window.__assignVehicle(window.__mapInfoAddr,\'' + vh + '\')" style="padding:4px 10px;border-radius:5px;border:' + (isActive ? '2px solid ' + vColors[vh] : '1px solid #ddd') + ';background:' + (isActive ? vColors[vh] + '22' : '#f8fafc') + ';color:' + vColors[vh] + ';font-size:11px;font-weight:700;cursor:pointer;">' + vh + '</button>';
            }).join('') +
            '<button onclick="window.__unassignBldg(window.__mapInfoAddr)" style="padding:4px 8px;border-radius:5px;border:1px solid #ddd;background:#f8fafc;color:#94a3b8;font-size:10px;cursor:pointer;">해제</button>' +
            '<button onclick="window.__openRouteCodeModal(window.__mapInfoAddr,\'' + (bldg.route_code||'') + '\')" style="padding:4px 8px;border-radius:5px;border:1px solid #3B82F6;background:#EFF6FF;color:#3B82F6;font-size:10px;font-weight:700;cursor:pointer;">코드</button>' +
            '</div>' +


            '<div style="margin-top:6px;display:flex;align-items:center;justify-content:space-between;">' +
            '<a href="https://map.naver.com/v5/search/' + encodeURIComponent(addr) + '" target="_blank" style="display:inline-block;padding:5px 12px;background:#03C75A;color:#fff;border-radius:6px;font-size:10px;font-weight:600;text-decoration:none;">🧭 길안내</a>' +
            '<div style="display:flex;gap:4px;align-items:center;">' +
            (items[0] && items[0].sort_order ? '<div style="background:#7C3AED;color:#fff;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:800;">순서 ' + items[0].sort_order + '번</div>' : '') +
            (drawNum ? '<div style="background:' + ({A:'#3B82F6',B:'#F59E0B',C:'#10B981',D:'#E91E63'}[mapVehicle]||'#F97316') + ';color:#fff;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:800;">경로 ' + drawNum + '번</div>' : '') +
            '</div>' +
            '</div></div>',
          borderWidth: 0, backgroundColor: "transparent", borderColor: "transparent",
          anchorSize: new window.naver.maps.Size(10, 10),
          pixelOffset: new window.naver.maps.Point(0, -5),
        });
        iw.open(mapInstance.current, marker);
        infoRef.current = iw;
      });

      markersRef.current.push(marker);
    });

    // 필터 적용 시 최초 1회만 fitBounds (배정 후 지도 이동 방지)
    if (!drawMode && !didFitRef.current && (mapCodes.length > 0 || mapGus.length > 0 || mapVehicle !== "all")) {
      var bounds = new window.naver.maps.LatLngBounds();
      var pinCount = 0;
      Object.keys(buildings).forEach(function(addr) {
        var c = coordCache[addr];
        if (c) { bounds.extend(new window.naver.maps.LatLng(c.lat, c.lng)); pinCount++; }
      });
      if (pinCount > 0) {
        didFitRef.current = true;
        if (pinCount === 1) {
          var onlyAddr = Object.keys(buildings)[0];
          var onlyCoord = coordCache[onlyAddr];
          if (onlyCoord) {
            mapInstance.current.setCenter(new window.naver.maps.LatLng(onlyCoord.lat, onlyCoord.lng));
            mapInstance.current.setZoom(16);
          }
        } else {
          mapInstance.current.fitBounds(bounds, { top: 40, right: 40, bottom: 40, left: 40 });
        }
      }
    }
    // v18: 경로선이 있으면 마커 재그리기 후 경로 복원
    var activeRoute = routeLineDataRef.current;
    if (activeRoute && activeRoute.length > 1) {
      if (routeLineRef.current) { routeLineRef.current.setMap(null); routeLineRef.current = null; }
      if (routeMarkersRef.current.length > 0) { routeMarkersRef.current.forEach(function(m) { m.setMap(null); }); routeMarkersRef.current = []; }
      // 본부/복귀 제외한 배송지만 연결
      var drawRoute2 = activeRoute.slice(1, activeRoute.length - 1);
      var vColor2 = {A:'#3B82F6',B:'#F59E0B',C:'#10B981',D:'#E91E63'}[routeInfo ? routeInfo.vehicle : null] || '#7C3AED';
      routeLineRef.current = new window.naver.maps.Polyline({
        map: mapInstance.current,
        path: drawRoute2.map(function(c) { return new window.naver.maps.LatLng(c.lat, c.lng); }),
        strokeColor: vColor2, strokeWeight: 4, strokeOpacity: 0.85, strokeStyle: 'solid',
      });
      // 경유지 순번 라벨 (핀 아래 작은 태그, 클릭 투과)
      activeRoute.forEach(function(c, ri) {
        if (ri === 0 || ri === activeRoute.length - 1) return;
        var rm = new window.naver.maps.Marker({
          position: new window.naver.maps.LatLng(c.lat, c.lng),
          map: mapInstance.current, zIndex: 10, clickable: false,
          icon: {
            content: '<div style="background:#7C3AED;color:#fff;padding:1px 4px;border-radius:5px;font-size:9px;font-weight:800;border:1px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,0.3);white-space:nowrap;pointer-events:none;">' + ri + '번</div>',
            anchor: new window.naver.maps.Point(16, -4),
          },
        });
        routeMarkersRef.current.push(rm);
      });
      // 도착 마커
      var endM = new window.naver.maps.Marker({
        position: new window.naver.maps.LatLng(activeRoute[activeRoute.length - 1].lat, activeRoute[activeRoute.length - 1].lng),
        map: mapInstance.current, zIndex: 500,
        icon: { content: '<div style="background:#EF4444;color:#fff;padding:5px 12px;border-radius:14px;font-size:13px;font-weight:800;border:3px solid #fff;box-shadow:0 3px 12px rgba(0,0,0,0.5);white-space:nowrap;">🏁 복귀</div>', anchor: new window.naver.maps.Point(38, 45) },
      });
      routeMarkersRef.current.push(endM);
    }
    }, 50); // debounce 50ms
    return function() { if (markerDrawTimerRef.current) clearTimeout(markerDrawTimerRef.current); };
  }, [mapReady, buildings, coordCache, vc, assignVehicleToBuilding, unassignBuilding, allDataReady]);



  // 지오코딩 실행
  var FAIL_TTL = 7 * 24 * 60 * 60 * 1000; // 7일

  var runGeocode = useCallback(function() {
    var uncached = {};
    var now = Date.now();
    allItems.forEach(function(a) {
      var key = a.road_address || a.name;
      var cached = coordCache[key];
      // 정상 캐시 → 스킵
      if (cached && !cached.failed) return;
      // 실패 캐시 → 7일 안 지났으면 스킵, 지났으면 재시도
      if (cached && cached.failed && (now - cached.ts) < FAIL_TTL) return;
      if (uncached[key]) return;

      var alreadyFull = key.indexOf("서울특별시") >= 0 || key.indexOf("경기도") >= 0 || key.indexOf("인천광역시") >= 0 || key.indexOf("서울시") >= 0;
      var geocodeQuery;
      if (alreadyFull) {
        geocodeQuery = key;
      } else {
        var regionPrefix = (function(code, gu) {
          if (!code) return gu ? "서울시 " + gu + " " : "서울시 ";
          if (code.indexOf("분당") >= 0) return "경기도 성남시 분당구 ";
          if (code.indexOf("인천공항") >= 0) return "인천광역시 중구 ";
          if (code.indexOf("여의도") >= 0) return "서울시 영등포구 ";
          return gu ? "서울시 " + gu + " " : "서울시 ";
        })(a.route_code, a.gu);
        geocodeQuery = regionPrefix + key;
      }
      uncached[key] = geocodeQuery;
    });
    var list = Object.entries(uncached);
    if (list.length === 0) { alert("모든 주소의 좌표가 이미 변환되었습니다."); return; }

    setGeocoding(true);
    setGcProgress({ done: 0, total: list.length });

    var newCache = Object.assign({}, coordCache);
    var idx = 0;

    function next() {
      if (idx >= list.length) {
        setCoordCache(newCache);
        setGeocoding(false);
        return;
      }
      var entry = list[idx];
      geocodeAddr(entry[1]).then(function(coord) {
        if (coord) {
          newCache[entry[0]] = coord; // 성공: 정상 좌표 저장
        } else {
          newCache[entry[0]] = { failed: true, ts: Date.now() }; // 실패: 실패 마크 저장
        }
        idx++;
        setGcProgress({ done: idx, total: list.length });
        if (idx % 50 === 0) setCoordCache(Object.assign({}, newCache));
        setTimeout(next, 100);
      });
    }
    next();
  }, [allItems, coordCache, geocodeAddr]);

  // 캐시 초기화
  var clearCache = function() {
    if (!confirm("좌표 캐시를 초기화하시겠습니까?\n다시 변환해야 합니다.")) return;
    setCoordCache({});
    localStorage.removeItem("dm_coordCache");
  };

  // 실패 캐시만 초기화 (주소 수정 후 재시도용)
  var clearFailedCache = function() {
    var newCache = {};
    Object.keys(coordCache).forEach(function(k) {
      if (!coordCache[k].failed) newCache[k] = coordCache[k];
    });
    var removedCnt = Object.keys(coordCache).length - Object.keys(newCache).length;
    if (removedCnt === 0) { alert("실패한 캐시가 없습니다."); return; }
    setCoordCache(newCache);
    try { localStorage.setItem("dm_coordCache", JSON.stringify(newCache)); } catch(e) {}
    alert(removedCnt + "건 실패 캐시 초기화 완료!\n변환 버튼을 눌러 재시도하세요.");
  };

  // v17: 건물 호차 배정
  var assignVehicleToBuilding = useCallback(function(bldgAddr, newVehicle) {
    var items = allItems.filter(function(a) { return (a.road_address || a.name) === bldgAddr; });
    if (items.length === 0) return;
    setAssigning(true);
    // 해당 차량의 현재 최대 sort_order 계산
    var maxOrder = allItems.filter(function(a) { return a.vehicle === newVehicle && a.sort_order; })
      .reduce(function(mx, a) { return Math.max(mx, a.sort_order || 0); }, 0);
    var updates = items.map(function(a, i) { return { id: a.id, sort_order: maxOrder + i + 1 }; });
    Promise.all(updates.map(function(u) {
      return supabase.update("addresses", u.id, { vehicle: newVehicle, sort_order: u.sort_order, status: "pending" });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          var u = updates.find(function(x) { return x.id === a.id; });
          if (u) return Object.assign({}, a, { vehicle: newVehicle, sort_order: u.sort_order, status: "pending" });
          return a;
        });
      });
      setAssigning(false);
      if (infoRef.current) { infoRef.current.close(); infoRef.current = null; }
    }).catch(function(e) { alert("배정 실패: " + e.message); setAssigning(false); });
  }, [allItems, supabase]);

  // v17: 배송코드 일괄 배정
  var assignVehicleToCode = useCallback(function(code, newVehicle, skipConfirm) {
    var allCodeItems = allItems.filter(function(a) { return a.route_code === code; });
    if (allCodeItems.length === 0) return;
    // 다른 호차에 이미 배정된 업체만 제외 (같은 호차 or 미배정은 포함)
    var items = allCodeItems.filter(function(a) { return !a.vehicle || a.vehicle === newVehicle; });
    var skipped = allCodeItems.length - items.length;
    if (items.length === 0) { if (!skipConfirm) alert(code + " 전체 " + skipped + "건이 이미 다른 호차에 배정되어 있습니다."); return; }
    if (!skipConfirm) {
      var msg = code + " " + items.length + "건을 " + newVehicle + "호차로 배정?" + (skipped > 0 ? "\n(다른 호차 배정된 " + skipped + "건 제외)" : "");
      if (!confirm(msg)) return;
    }
    setAssigning(true);
    var maxOrder = allItems.filter(function(a) { return a.vehicle === newVehicle && a.sort_order; })
      .reduce(function(mx, a) { return Math.max(mx, a.sort_order || 0); }, 0);
    var updates = items.map(function(a, i) { return { id: a.id, sort_order: maxOrder + i + 1 }; });
    Promise.all(updates.map(function(u) {
      return supabase.update("addresses", u.id, { vehicle: newVehicle, sort_order: u.sort_order });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          var u = updates.find(function(x) { return x.id === a.id; });
          if (u) return Object.assign({}, a, { vehicle: newVehicle, sort_order: u.sort_order });
          return a;
        });
      });
      setAssigning(false);
      if (!skipConfirm) alert(code + " " + items.length + "건 → " + newVehicle + "호차 완료");
    }).catch(function(e) { alert("배정 실패: " + e.message); setAssigning(false); });
  }, [allItems, supabase]);

  // v17: 배송코드 일괄 해제
  var unassignByCode = useCallback(function() {
    if (mapCodes.length === 0) return;
    var items = allItems.filter(function(a) { return mapCodes.indexOf(a.route_code) >= 0 && a.vehicle; });
    if (items.length === 0) { alert("해제할 배정된 업장이 없습니다."); return; }
    if (!confirm(mapCodes.join(", ") + " 총 " + items.length + "건 호차 배정을 해제하시겠습니까?")) return;
    setAssigning(true);
    Promise.all(items.map(function(a) {
      return supabase.update("addresses", a.id, { vehicle: null, sort_order: null, status: "pending" });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          if (mapCodes.indexOf(a.route_code) >= 0) return Object.assign({}, a, { vehicle: null, sort_order: null, status: "pending" });
          return a;
        });
      });
      setAssigning(false);
      alert(items.length + "건 호차 배정 해제 완료");
    }).catch(function(e) { alert("해제 실패: " + e.message); setAssigning(false); });
  }, [allItems, mapCodes, supabase]);

  // v17: 호차 전체 배정 해제
  var unassignByVehicle = useCallback(function(v) {
    var items = allItems.filter(function(a) { return a.vehicle === v; });
    if (items.length === 0) { alert(v + "호차에 배정된 업장이 없습니다."); return; }
    if (!confirm(v + "호차 전체 " + items.length + "건의 배정을 해제하시겠습니까?\n기사 배송일정에서도 모두 사라집니다.")) return;
    setAssigning(true);
    Promise.all(items.map(function(a) {
      return supabase.update("addresses", a.id, { vehicle: null, sort_order: null, status: "pending" });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          if (a.vehicle === v) return Object.assign({}, a, { vehicle: null, sort_order: null, status: "pending" });
          return a;
        });
      });
      setAssigning(false);
      alert(v + "호차 " + items.length + "건 배정 해제 완료");
    }).catch(function(e) { alert("해제 실패: " + e.message); setAssigning(false); });
  }, [allItems, supabase]);

  // v17: 선택된 핀들 일괄 배정
  var assignVehicleToSelected = useCallback(function(newVehicle) {
    if (selectedPins.length === 0) return;
    var items = allItems.filter(function(a) { return selectedPins.indexOf(a.road_address || a.name) >= 0; });
    if (items.length === 0) return;
    if (!confirm(selectedPins.length + "개 건물 " + items.length + "건을 " + newVehicle + "호차로 배정?")) return;
    setAssigning(true);
    var maxOrder = allItems.filter(function(a) { return a.vehicle === newVehicle && a.sort_order; })
      .reduce(function(mx, a) { return Math.max(mx, a.sort_order || 0); }, 0);
    var updates = items.map(function(a, i) { return { id: a.id, sort_order: maxOrder + i + 1 }; });
    Promise.all(updates.map(function(u) {
      return supabase.update("addresses", u.id, { vehicle: newVehicle, sort_order: u.sort_order });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          var u = updates.find(function(x) { return x.id === a.id; });
          if (u) return Object.assign({}, a, { vehicle: newVehicle, sort_order: u.sort_order });
          return a;
        });
      });
      setAssigning(false); setSelectedPins([]); setAssignMode(null); setDragRect(null); setSelectionBounds(null);
      alert(items.length + "건 → " + newVehicle + "호차 완료");
    }).catch(function(e) { alert("배정 실패: " + e.message); setAssigning(false); });
  }, [allItems, selectedPins, supabase]);

  // v17: 선택된 핀들 일괄 해제
  var unassignSelected = useCallback(function() {
    if (selectedPins.length === 0) return;
    var items = allItems.filter(function(a) { return selectedPins.indexOf(a.road_address || a.name) >= 0 && a.vehicle; });
    if (items.length === 0) { alert("해제할 배정된 업장이 없습니다."); return; }
    if (!confirm(selectedPins.length + "개 건물 " + items.length + "건 호차 배정을 해제하시겠습니까?")) return;
    setAssigning(true);
    Promise.all(items.map(function(a) {
      return supabase.update("addresses", a.id, { vehicle: null });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          if (selectedPins.indexOf(a.road_address || a.name) >= 0) return Object.assign({}, a, { vehicle: null });
          return a;
        });
      });
      setAssigning(false); setSelectedPins([]); setAssignMode(null); setDragRect(null); setSelectionBounds(null);
      alert(items.length + "건 호차 배정 해제 완료");
    }).catch(function(e) { alert("해제 실패: " + e.message); setAssigning(false); });
  }, [allItems, selectedPins, supabase]);

  // v17: 배정 해제
  var unassignBuilding = useCallback(function(bldgAddr) {
    var items = allItems.filter(function(a) { return (a.road_address || a.name) === bldgAddr; });
    if (items.length === 0) return;
    setAssigning(true);
    Promise.all(items.map(function(a) {
      return supabase.update("addresses", a.id, { vehicle: null });
    })).then(function() {
      setAllData(function(prev) {
        return prev.map(function(a) {
          if ((a.road_address || a.name) === bldgAddr) return Object.assign({}, a, { vehicle: null });
          return a;
        });
      });
      setAssigning(false);
      if (infoRef.current) { infoRef.current.close(); infoRef.current = null; }
    }).catch(function(e) { alert("해제 실패: " + e.message); setAssigning(false); });
  }, [allItems, supabase]);

  // 통계
  var totalBldg = Object.keys(buildings).length;
  // 브랜드북/필터 적용된 건물 기준으로 좌표 카운트
  var filteredBldgKeys = {};
  Object.keys(buildings).forEach(function(k) { filteredBldgKeys[k] = true; });
  var allBldgKeys = {};
  allItems.forEach(function(a) { allBldgKeys[a.road_address || a.name] = true; });
  var allBldgCnt = mapExtraMag ? Object.keys(filteredBldgKeys).length : Object.keys(allBldgKeys).length;
  var cachedCnt = mapExtraMag
    ? Object.keys(filteredBldgKeys).filter(function(k) { return coordCache[k] && !coordCache[k].failed; }).length
    : Object.keys(coordCache).filter(function(k) { return !!allBldgKeys[k] && !coordCache[k].failed; }).length;

  return (
    <div style={mapFullscreen ? { position: "fixed", top: 0, left: 0, right: 0, bottom: 0, zIndex: 1000, background: T.bg, overflow: "auto", padding: "8px 12px" } : {}}>
      <div style={{ fontSize: 15, fontWeight: 700, marginBottom: 8, display: "flex", alignItems: "center", gap: 6 }}>
        <Icon d={I.mapView} size={16} color={T.primary}/> 배송지 지도
        {assigning && <span style={{ fontSize: 10, color: T.warning, fontWeight: 500 }}>배정 중...</span>}
        <div style={{ flex: 1 }}/>
        <button onClick={function() { loadMapData(); }} style={{ padding: "4px 8px", borderRadius: 6, border: "1px solid " + T.border, background: T.sf, color: T.muted, fontSize: 10, cursor: "pointer" }}>🔄 새로고침</button>
        <button onClick={function() { setMapFullscreen(!mapFullscreen); }} style={{ padding: "4px 8px", borderRadius: 6, border: "1px solid " + T.border, background: mapFullscreen ? T.primary + "22" : T.sf, color: mapFullscreen ? T.primary : T.muted, fontSize: 10, cursor: "pointer" }}>{mapFullscreen ? "✕ 닫기" : "🔍 확대"}</button>
      </div>

      {/* 배송코드 + 구 필터 (상단, 복수선택) */}
      {/* 추가 잡지 필터 체크박스 */}
      {EXTRA_MAGS_REF && EXTRA_MAGS_REF.length > 0 && (
        <div style={{ display: "flex", gap: 6, marginBottom: 6, flexWrap: "wrap", alignItems: "center" }}>
          {EXTRA_MAGS_REF.map(function(mag) {
            var isActive = mapExtraMag === mag;
            var cnt = (allData && allData.length > 0 ? allData : addresses).filter(function(a) { var mq = a.magazine_qty || {}; if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } } return mq[mag] > 0; }).length;
            return React.createElement("button", {
              key: mag,
              onClick: function() {
                var next = isActive ? null : mag;
                setMapExtraMag(next);
                loadMapData();
                if (next && mapInstance.current && window.naver && window.naver.maps) {
                  // 브랜드북 있는 업체들로 fitBounds
                  setTimeout(function() {
                    var filtered = allItems.filter(function(a) {
                      var mq = a.magazine_qty || {};
                      if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
                      return mq[next] > 0;
                    });
                    if (filtered.length === 0) return;
                    var bounds = new window.naver.maps.LatLngBounds();
                    var hasCoord = false;
                    filtered.forEach(function(a) {
                      var coord = coordCache[a.road_address || a.name];
                      if (coord) { bounds.extend(new window.naver.maps.LatLng(coord.lat, coord.lng)); hasCoord = true; }
                    });
                    if (hasCoord) mapInstance.current.fitBounds(bounds, { top: 60, right: 40, bottom: 40, left: 40 });
                  }, 500);
                }
              },
              style: { padding: "5px 12px", borderRadius: 7, border: "1px solid " + (isActive ? T.primary : T.border), background: isActive ? T.primary + "22" : T.sf, color: isActive ? T.primary : T.muted, fontSize: 11, fontWeight: 700, cursor: "pointer" }
            }, (isActive ? "✓ " : "") + mag + " (" + cnt + ")");
          })}
          {mapLoading && React.createElement("span", { style: { fontSize: 10, color: T.muted } }, "로딩중...")}
        </div>
      )}
      <div style={{ display: "flex", gap: 4, marginBottom: 6 }}>
        {/* 배송코드 멀티셀렉트 */}
        <div style={{ flex: 1, position: "relative" }}>
          <button onClick={function() { setShowCodeFilter(!showCodeFilter); setShowGuFilter(false); }} style={{
            width: "100%", padding: "7px 10px", borderRadius: 8, border: "1px solid " + (mapCodes.length > 0 ? T.accent : T.border),
            background: mapCodes.length > 0 ? T.accent + "15" : T.sf, color: mapCodes.length > 0 ? T.accent : T.muted,
            fontSize: 10, fontWeight: 600, cursor: "pointer", textAlign: "left",
            display: "flex", justifyContent: "space-between", alignItems: "center",
          }}>
            <span>{mapCodes.length === 0 ? "배송코드 전체" : mapCodes.length + "개 선택"}</span>
            <span style={{ fontSize: 8 }}>{showCodeFilter ? "▲" : "▼"}</span>
          </button>
          {showCodeFilter && (
            <div onClick={function(e) { e.stopPropagation(); }} style={{
              position: "absolute", top: "100%", left: 0, right: 0, zIndex: 999,
              background: T.card, border: "1px solid " + T.border, borderRadius: 8,
              maxHeight: 250, overflowY: "auto", marginTop: 2, boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
            }}>
              <div style={{ display: "flex", borderBottom: "1px solid " + T.border }}>
                <button onClick={function() { setMapCodes([]); }} style={{ flex: 1, padding: "8px", border: "none", background: mapCodes.length === 0 ? T.primary + "22" : "transparent", color: mapCodes.length === 0 ? T.primary : T.text, fontSize: 10, fontWeight: 600, cursor: "pointer" }}>전체</button>
                <button onClick={function() { setShowCodeFilter(false); }} style={{ padding: "8px 12px", border: "none", borderLeft: "1px solid " + T.border, background: "transparent", color: T.accent, fontSize: 10, fontWeight: 600, cursor: "pointer" }}>닫기</button>
              </div>
              {ROUTE_CODES.map(function(c) {
                var cnt = allItems.filter(function(a) { return a.route_code === c; }).length;
                if (cnt === 0 && c !== "기타") return null;
                var chk = mapCodes.indexOf(c) >= 0;
                return React.createElement("button", {
                  key: c,
                  onClick: function() {
                    if (chk) {
                      // 체크 해제
                      setMapCodes(mapCodes.filter(function(x) { return x !== c; }));
                      // zoneColorMap 제거
                      var next = Object.assign({}, zoneColorMap);
                      delete next[c];
                      setZoneColorMap(next);
                      try { localStorage.setItem("dm_zoneColorMap", JSON.stringify(next)); } catch(e) {}
                      // 해당 구역 vehicle 해제
                      var codeItems = allItems.filter(function(a) { return a.route_code === c && a.vehicle; });
                      if (codeItems.length === 0) return;
                      // 로컬 상태 즉시 업데이트
                      setAllData(function(prev) {
                        return prev.map(function(a) {
                          if (a.route_code === c && a.vehicle) return Object.assign({}, a, { vehicle: null, sort_order: null });
                          return a;
                        });
                      });
                      // DB 백그라운드 업데이트 후 카운트 재조회
                      Promise.all(codeItems.map(function(a) {
                        return supabase.update("addresses", a.id, { vehicle: null, sort_order: null });
                      })).then(function() {
                        // DB에서 정확한 카운트 재조회
                                              });
                    } else {
                      setMapCodes(mapCodes.concat([c]));
                    }
                  },
                  style: {
                    width: "100%", padding: "7px 10px", border: "none", borderBottom: "1px solid " + T.border + "44",
                    background: chk ? T.accent + "15" : "transparent", color: chk ? T.accent : T.text,
                    fontSize: 11, cursor: "pointer", textAlign: "left", display: "flex", alignItems: "center", gap: 6,
                  }
                },
                  React.createElement("span", { style: { width: 16, height: 16, borderRadius: 4, border: "2px solid " + (chk ? T.accent : T.dim), display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 10, background: chk ? T.accent : "transparent", color: "#fff", flexShrink: 0 } }, chk ? "✓" : ""),
                  React.createElement("span", { style: { flex: 1 } }, c),
                  React.createElement("span", { style: { color: T.dim, fontSize: 10, marginRight: 4 } }, cnt),
                  React.createElement("div", { style: { display: "flex", gap: 2 }, onClick: function(e) { e.stopPropagation(); } },
                    ["A","B","C","D"].map(function(v) {
                      var sel = zoneColorMap[c] === v;
                      var btnDisabled = !chk;
                      return React.createElement("div", {
                        key: v,
                        onClick: btnDisabled ? undefined : (function(capturedV, capturedC) { return function(e) {
                          e.stopPropagation();
                          var currentSel = zoneColorMapRef.current[capturedC] === capturedV;
                          if (currentSel) {
                            // 같은 호차 클릭 → 해제
                            var codeItems = allDataRef.current.filter(function(a) { return a.route_code === capturedC && a.vehicle === capturedV; });
                            var nextZone = Object.assign({}, zoneColorMapRef.current);
                            delete nextZone[capturedC];
                            zoneColorMapRef.current = nextZone;
                            setZoneColorMap(Object.assign({}, nextZone));
                            try { localStorage.setItem("dm_zoneColorMap", JSON.stringify(nextZone)); } catch(e) {}
                            setAllData(function(prev) {
                              return prev.map(function(a) {
                                if (a.route_code === capturedC && a.vehicle === capturedV) return Object.assign({}, a, { vehicle: null, sort_order: null });
                                return a;
                              });
                            });
                            Promise.all(codeItems.map(function(a) {
                              return supabase.update("addresses", a.id, { vehicle: null, sort_order: null });
                            }));
                          } else {
                            // 다른 호차 or 미배정 → 새 호차 배정
                            var prevV = zoneColorMapRef.current[capturedC];
                            var nextZone2 = Object.assign({}, zoneColorMapRef.current);
                            nextZone2[capturedC] = capturedV;
                            zoneColorMapRef.current = nextZone2;
                            setZoneColorMap(Object.assign({}, nextZone2));
                            try { localStorage.setItem("dm_zoneColorMap", JSON.stringify(nextZone2)); } catch(e) {}
                            // 이전 호차 배정 해제 후 새 호차 배정
                            if (prevV) {
                              setAllData(function(prev) {
                                return prev.map(function(a) {
                                  if (a.route_code === capturedC && a.vehicle === prevV) return Object.assign({}, a, { vehicle: null, sort_order: null });
                                  return a;
                                });
                              });
                              var prevItems = allDataRef.current.filter(function(a) { return a.route_code === capturedC && a.vehicle === prevV; });
                              Promise.all(prevItems.map(function(a) { return supabase.update("addresses", a.id, { vehicle: null, sort_order: null }); }))
                                .then(function() { assignVehicleToCode(capturedC, capturedV, true); });
                            } else {
                              assignVehicleToCode(capturedC, capturedV, true);
                            }
                          }
                        };})(v, c),
                        style: { width: 20, height: 20, borderRadius: 4, background: sel ? vc(v) : "transparent", border: "1.5px solid " + (btnDisabled ? T.border : vc(v)), display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 700, color: sel ? "#fff" : (btnDisabled ? T.dim : vc(v)), cursor: btnDisabled ? "default" : "pointer", opacity: btnDisabled ? 0.3 : 1, pointerEvents: btnDisabled ? "none" : "auto" }
                      }, v);
                    })
                  )
                );
              })}
            </div>
          )}
        </div>

        {/* 구 멀티셀렉트 */}
        <div style={{ flex: 1, position: "relative" }}>
          <button onClick={function() { setShowGuFilter(!showGuFilter); setShowCodeFilter(false); }} style={{
            width: "100%", padding: "7px 10px", borderRadius: 8, border: "1px solid " + (mapGus.length > 0 ? T.accent : T.border),
            background: mapGus.length > 0 ? T.accent + "15" : T.sf, color: mapGus.length > 0 ? T.accent : T.muted,
            fontSize: 10, fontWeight: 600, cursor: "pointer", textAlign: "left",
            display: "flex", justifyContent: "space-between", alignItems: "center",
          }}>
            <span>{mapGus.length === 0 ? "구 전체" : mapGus.length + "개 선택"}</span>
            <span style={{ fontSize: 8 }}>{showGuFilter ? "▲" : "▼"}</span>
          </button>
          {showGuFilter && (
            <div onClick={function(e) { e.stopPropagation(); }} style={{
              position: "absolute", top: "100%", left: 0, right: 0, zIndex: 999,
              background: T.card, border: "1px solid " + T.border, borderRadius: 8,
              maxHeight: 250, overflowY: "auto", marginTop: 2, boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
            }}>
              <div style={{ display: "flex", borderBottom: "1px solid " + T.border }}>
                <button onClick={function() { setMapGus([]); }} style={{ flex: 1, padding: "8px", border: "none", background: mapGus.length === 0 ? T.primary + "22" : "transparent", color: mapGus.length === 0 ? T.primary : T.text, fontSize: 10, fontWeight: 600, cursor: "pointer" }}>전체</button>
                <button onClick={function() { setShowGuFilter(false); }} style={{ padding: "8px 12px", border: "none", borderLeft: "1px solid " + T.border, background: "transparent", color: T.accent, fontSize: 10, fontWeight: 600, cursor: "pointer" }}>닫기</button>
              </div>
              {GU_LIST.map(function(g) {
                var cnt = allItems.filter(function(a) { return a.gu === g; }).length;
                if (cnt === 0) return null;
                var chk = mapGus.indexOf(g) >= 0;
                return React.createElement("button", {
                  key: g,
                  onClick: function() { setMapGus(chk ? mapGus.filter(function(x) { return x !== g; }) : mapGus.concat([g])); },
                  style: {
                    width: "100%", padding: "7px 10px", border: "none", borderBottom: "1px solid " + T.border + "44",
                    background: chk ? T.accent + "15" : "transparent", color: chk ? T.accent : T.text,
                    fontSize: 11, cursor: "pointer", textAlign: "left", display: "flex", alignItems: "center", gap: 6,
                  }
                },
                  React.createElement("span", { style: { width: 16, height: 16, borderRadius: 4, border: "2px solid " + (chk ? T.accent : T.dim), display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 10, background: chk ? T.accent : "transparent", color: "#fff", flexShrink: 0 } }, chk ? "✓" : ""),
                  React.createElement("span", { style: { flex: 1 } }, g),
                  React.createElement("span", { style: { color: T.dim, fontSize: 10 } }, cnt)
                );
              })}
            </div>
          )}
        </div>
      </div>

      {/* 배송코드 선택 시 총 업체수 + 잡지수량 + 일괄해제 */}
      {mapCodes.length > 0 && (function() {
        var selItems = allItems.filter(function(a) { return mapCodes.indexOf(a.route_code) >= 0; });
        var mqSum = {};
        selItems.forEach(function(a) {
          var mq = a.magazine_qty || {};
          if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
          Object.keys(mq).forEach(function(k) { mqSum[k] = (mqSum[k] || 0) + (mq[k] || 0); });
        });
        var mqKeys = ["노블레스","맨","아트","Y","워치","웨딩","타임북"];
        var mqFiltered = mqKeys.filter(function(k) { return mqSum[k] > 0; });
        return React.createElement("div", { style: { marginBottom: 6, padding: "8px 10px", borderRadius: 8, background: T.accent + "10", border: "1px solid " + T.accent + "30" } },
          React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: mqFiltered.length > 0 ? 6 : 0, flexWrap: "wrap" } },
            React.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: T.accent } }, mapCodes.join(", ")),
            React.createElement("span", { style: { fontSize: 11, color: T.muted } }, "· " + selItems.length + "개 업체"),
            React.createElement("button", {
              onClick: function() { setMapCodes([]); },
              style: { marginLeft: "auto", padding: "2px 10px", borderRadius: 6, border: "1px solid " + T.border, background: T.sf, color: T.danger, fontSize: 10, fontWeight: 600, cursor: "pointer" }
            }, "전체해제")
          ),
          mqFiltered.length > 0 && React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 } },
            mqFiltered.map(function(k) {
              return React.createElement("span", { key: k, style: { padding: "2px 7px", borderRadius: 4, fontSize: 10, fontWeight: 600, background: T.sf, color: T.text } },
                k + " " + mqSum[k].toLocaleString()
              );
            })
          )
        );
      })()}

      {/* v17: 배송코드 일괄 배정 */}
      {mapCodes.length > 0 && (
        <div style={{ display: "flex", gap: 4, marginBottom: 6, alignItems: "center" }}>
          <div style={{ display: "flex", gap: 3, alignItems: "center", flex: 1, flexWrap: "wrap" }}>
            <span style={{ fontSize: 10, color: T.muted }}>일괄배정:</span>
            {VEHICLES.map(function(v) {
              return React.createElement("button", {
                key: "bulk-" + v, disabled: assigning,
                onClick: function() {
                  var totalItems = allItems.filter(function(a) { return mapCodes.indexOf(a.route_code) >= 0 && (!a.vehicle || a.vehicle === v); });
                  var skipped = allItems.filter(function(a) { return mapCodes.indexOf(a.route_code) >= 0 && a.vehicle && a.vehicle !== v; }).length;
                  if (totalItems.length === 0) { alert("배정할 업체가 없습니다. (전체 다른 호차에 배정된 상태)"); return; }
                  var msg = mapCodes.join(", ") + "\n총 " + totalItems.length + "건을 " + v + "호차로 배정?" + (skipped > 0 ? "\n(다른 호차 배정된 " + skipped + "건 제외)" : "");
                  if (!confirm(msg)) return;
                  mapCodes.forEach(function(code) {
                    assignVehicleToCode(code, v, true);
                    setZoneColor(code, v);
                  });
                },
                style: { padding: "4px 10px", borderRadius: 6, border: "none", cursor: assigning ? "wait" : "pointer", background: vc(v), color: "#fff", fontSize: 10, fontWeight: 700, opacity: assigning ? 0.5 : 1 }
              }, v);
            })}
            <button disabled={assigning} onClick={function() { unassignByCode(); }} style={{
              padding: "4px 10px", borderRadius: 6, border: "1px solid " + T.border,
              background: T.sf, color: T.danger, fontSize: 10, fontWeight: 600,
              cursor: assigning ? "wait" : "pointer", opacity: assigning ? 0.5 : 1,
            }}>해제</button>
          </div>
        </div>
      )}

      {/* v17: 선택된 핀 배정 UI */}
      {selectedPins.length > 0 && (
        <div style={{ marginBottom: 6, padding: "8px 10px", borderRadius: 8, background: T.warning + "15", border: "1px solid " + T.warning + "44" }}>
          <div style={{ display: "flex", gap: 4, alignItems: "center", marginBottom: 6 }}>
            <span style={{ fontSize: 11, fontWeight: 700, color: T.warning }}>{selectedPins.length}개 건물 · {selectedPins.reduce(function(s, addr) { var b = buildings[addr]; return s + (b ? b.items.length : 0); }, 0)}개 업장</span>
            <div style={{ flex: 1 }}/>
            {VEHICLES.map(function(v) {
              return React.createElement("button", {
                key: "sel-" + v, disabled: assigning,
                onClick: function() { assignVehicleToSelected(v); },
                style: { padding: "5px 12px", borderRadius: 6, border: "none", cursor: assigning ? "wait" : "pointer", background: vc(v), color: "#fff", fontSize: 11, fontWeight: 700 }
              }, v);
            })}
            <button disabled={assigning} onClick={function() { unassignSelected(); }} style={{
              padding: "5px 10px", borderRadius: 6, border: "1px solid " + T.border,
              background: T.sf, color: T.danger, fontSize: 10, fontWeight: 600,
              cursor: assigning ? "wait" : "pointer",
            }}>해제</button>
            <button onClick={function() { setSelectedPins([]); setDragRect(null); setSelectionBounds(null); }} style={{
              padding: "5px 8px", borderRadius: 6, border: "1px solid " + T.border, background: T.sf, color: T.dim, fontSize: 10, cursor: "pointer",
            }}>취소</button>
          </div>
          <div style={{ maxHeight: 80, overflowY: "auto", fontSize: 10, color: T.muted, lineHeight: 1.6 }}>
            {selectedPins.map(function(addr, idx) {
              var bldg = buildings[addr];
              var cnt = bldg ? bldg.items.length : 0;
              return React.createElement("span", { key: idx }, (idx > 0 ? " · " : "") + addr + "(" + cnt + ")");
            })}
          </div>
        </div>
      )}

      {/* 호차 필터 (하단) */}
      <div style={{ display: "flex", gap: 4, marginBottom: 8 }}>
        {["all"].concat(VEHICLES).map(function(v) {
          var isAll = v === "all";
          var vIdx = ["A","B","C","D"].indexOf(v);
          var vNum = !isAll && vehicleInfo ? (vehicleInfo[vIdx]?.num || v) : null;
          return (
            <button key={v} onClick={function() {
                setMapVehicle(v);
                if (v !== 'all' && mapInstance.current && window.naver && window.naver.maps) {
                  var vItems = allItems.filter(function(a) { return a.vehicle === v && a.sort_order > 0; });
                  vItems.sort(function(a,b){ return a.sort_order - b.sort_order; });
                  var firstItem = vItems[0];
                  if (!firstItem) firstItem = allItems.filter(function(a) { return a.vehicle === v; })[0];
                  if (firstItem) {
                    var coord = coordCache[firstItem.road_address || firstItem.name];
                    if (coord) {
                      mapInstance.current.setCenter(new window.naver.maps.LatLng(coord.lat, coord.lng));
                      mapInstance.current.setZoom(16);
                    }
                  }
                }
              }} style={{
              flex: 1, padding: "7px 0", borderRadius: 8, border: "none", cursor: "pointer",
              fontSize: 10, fontWeight: 700,
              background: mapVehicle === v ? (isAll ? T.primary : vc(v)) : T.sf,
              color: mapVehicle === v ? "#fff" : "#0F172A",
              boxShadow: mapVehicle === v ? "0 2px 8px " + (isAll ? T.primary : vc(v)) + "44" : "none",
            }}>{isAll ? "전체" : (function() {
                var saved = allItems.filter(function(a) { return a.vehicle === v && a.sort_order > 0; }).length;
                var total = vehicleCounts[v] || 0;
                return vNum + " (" + (saved > 0 ? saved + "/" : "") + total + ")";
              })()}</button>
          );
        })}
      </div>
      {mapVehicle !== "all" && vehicleCounts[mapVehicle] > 0 && (
        <div style={{ display: "flex", gap: 4, marginBottom: 8 }}>
          <button disabled={assigning} onClick={function() { unassignByVehicle(mapVehicle); }} style={{
            flex: 1, padding: "8px", borderRadius: 8, border: "1px solid " + T.danger + "44",
            background: T.danger + "15", color: T.danger, fontSize: 11, fontWeight: 600,
            cursor: assigning ? "wait" : "pointer", opacity: assigning ? 0.5 : 1,
          }}>{mapVehicle}호차 전체 배정 해제 ({vehicleCounts[mapVehicle]}건)</button>
        </div>
      )}


      {/* v19: 경로 그리기 모드 */}
      {mapVehicle !== "all" && (
        <div style={{ marginBottom: 8 }}>
          {!drawMode ? (
            <div style={{ display: "flex", gap: 6 }}>
              <button onClick={function() {
                var vItems = allItems.filter(function(a) { return a.vehicle === mapVehicle; });
                if (vItems.length < 2) { alert(mapVehicle + "호차에 2건 이상 배정해야 합니다."); return; }
                if (routeLineRef.current) { routeLineRef.current.setMap(null); routeLineRef.current = null; }
                if (routeMarkersRef.current.length > 0) { routeMarkersRef.current.forEach(function(m) { m.setMap(null); }); routeMarkersRef.current = []; }
                routeLineDataRef.current = null; setRouteInfo(null); setRouteLine(null);
                setDrawRoute([]); setDrawMode(true);
                if (infoRef.current) { infoRef.current.close(); infoRef.current = null; }
              }} style={{
                flex: 1, padding: "10px", borderRadius: 10, border: "2px dashed #F97316",
                background: "#F9731615", color: "#F97316", fontSize: 13, fontWeight: 700,
                cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
              }}>✏️ {mapVehicle}호차 경로 직접 그리기</button>
              <button onClick={function() {
                var vItems = allItems.filter(function(a) { return a.vehicle === mapVehicle; }).sort(function(a,b){ return (a.sort_order||9999)-(b.sort_order||9999); });
                if (vItems.length === 0) { alert("배정된 업체가 없습니다."); return; }
                var vColor = {A:"#3B82F6",B:"#F59E0B",C:"#10B981",D:"#E91E63"}[mapVehicle] || "#333";
                var MAGS = ["노블레스","맨","아트","Y","워치","웨딩","타임북"];
                var MAG_ABBR = {"노블레스":"노블레스","맨":"맨","아트":"아트","Y":"Y","워치":"워치","웨딩":"웨딩","타임북":"타임북"};
                var totalMq = {};
                vItems.forEach(function(a) {
                  var mq = a.magazine_qty || {};
                  if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
                  MAGS.forEach(function(k) { if (mq[k]) totalMq[k] = (totalMq[k]||0) + mq[k]; });
                });
                var mqStr = MAGS.filter(function(k){ return totalMq[k]; }).map(function(k){ return MAG_ABBR[k]+totalMq[k]; }).join(" ");
                var magHeaders = MAGS.map(function(k){ return "<th style=\"padding:5px 6px;text-align:center;width:32px\">"+MAG_ABBR[k]+"</th>"; }).join("");
                var rows = vItems.map(function(a) {
                  var mq = a.magazine_qty || {};
                  if (typeof mq === "string") { try { mq = JSON.parse(mq); } catch(e) { mq = {}; } }
                  var mqCells = MAGS.map(function(k){ return "<td style=\"padding:5px 6px;text-align:center;\">" + (mq[k] || "") + "</td>"; }).join("");
                  return "<tr style=\"border-top:1px solid #eee;\">" +
                    "<td style=\"padding:5px 8px;text-align:center;color:"+vColor+";font-weight:700;\">" + (a.sort_order || "-") + "</td>" +
                    "<td style=\"padding:5px 8px;font-weight:600;\">" + (a.name||"") + "</td>" +
                    "<td style=\"padding:5px 8px;color:#666;\">" + (a.road_address||"") + (a.floor ? " "+a.floor : "") + "</td>" +
                    "<td style=\"padding:5px 8px;color:#888;\">" + (a.route_code||"") + "</td>" +
                    mqCells +
                    "<td style=\"padding:5px 8px;color:#666;\">" + (a.memo||"") + "</td>" +
                    "</tr>";
                }).join("");
                var html = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>"+mapVehicle+"호차 배송목록</title>" +
                  "<style>body{font-family:sans-serif;font-size:11px;padding:16px;}h2{margin:0 0 4px;}p{margin:0 0 12px;color:#666;}table{width:100%;border-collapse:collapse;}th{background:#f5f5f5;padding:6px 8px;text-align:left;border-bottom:2px solid #ddd;font-size:11px;}td{font-size:11px;}@media print{button{display:none;}}</style></head><body>" +
                  "<h2 style=\"color:"+vColor+"\">"+mapVehicle+"호차 배송목록</h2>" +
                  "<p>총 "+vItems.length+"개 업체 · "+mqStr+"</p>" +
                  "<button onclick=\"window.print()\" style=\"margin-bottom:12px;padding:6px 16px;cursor:pointer;\">🖨️ 인쇄</button>" +
                  "<table><thead><tr><th style=\"width:36px\">순서</th><th>업체명</th><th>주소/층</th><th>배송코드</th>"+magHeaders+"<th>메모</th></tr></thead><tbody>" +
                  rows + "</tbody></table></body></html>";
                var w = window.open("", "_blank", "width=900,height=700");
                w.document.write(html);
                w.document.close();
              }} style={{
                padding: "10px 14px", borderRadius: 10, border: "1px solid " + T.border,
                background: T.sf, color: T.text, fontSize: 16, cursor: "pointer",
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>🖨️</button>
            </div>
          ) : (
            <div style={{ padding: "10px", borderRadius: 10, background: "#F9731615", border: "2px solid #F97316" }}>
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
                <div style={{ fontSize: 13, fontWeight: 700, color: "#F97316" }}>✏️ 경로 그리기 모드</div>
                <button onClick={function() {
                  setDrawMode(false); setDrawRoute([]);
                  if (drawLineRef.current) { drawLineRef.current.setMap(null); drawLineRef.current = null; }
                }} style={{
                  padding: "4px 10px", borderRadius: 6, border: "1px solid " + T.border,
                  background: T.sf, color: T.dim, fontSize: 11, cursor: "pointer",
                }}>취소</button>
              </div>
              <div style={{ fontSize: 11, color: T.muted, marginBottom: 8 }}>
                핀을 순서대로 터치 · 선택: <span style={{ color: "#F97316", fontWeight: 700 }}>{drawRoute.length}</span>개
              </div>
              <div style={{ display: "flex", gap: 6 }}>
                {/* 되돌리기 */}
                <button disabled={drawRoute.length === 0} onClick={function() {
                  setDrawRoute(function(prev) { return prev.slice(0, -1); });
                }} style={{
                  flex: 1, padding: "8px", borderRadius: 8, border: "1px solid " + T.border,
                  background: T.sf, color: drawRoute.length > 0 ? T.text : T.dim,
                  fontSize: 12, fontWeight: 600, cursor: drawRoute.length > 0 ? "pointer" : "default",
                  opacity: drawRoute.length > 0 ? 1 : 0.4,
                }}>↩️ 되돌리기</button>
                {/* 전체 초기화 */}
                <button disabled={drawRoute.length === 0} onClick={function() {
                  if (confirm("그린 경로를 모두 지울까요?")) setDrawRoute([]);
                }} style={{
                  padding: "8px 12px", borderRadius: 8, border: "1px solid " + T.border,
                  background: T.sf, color: drawRoute.length > 0 ? T.warning : T.dim,
                  fontSize: 12, cursor: drawRoute.length > 0 ? "pointer" : "default",
                  opacity: drawRoute.length > 0 ? 1 : 0.4,
                }}>🗑️</button>
                {/* 저장 */}
                <button disabled={drawRoute.length < 2 || drawSaving} onClick={function() {
                  // 경로 그리기에서 선택되지 않은 건물도 맨 뒤에 추가
                  var vItems = allItems.filter(function(a) { return a.vehicle === mapVehicle; });
                  var selectedAddrs = {};
                  drawRoute.forEach(function(p) { selectedAddrs[p.addr] = true; });
                  var unselected = [];
                  vItems.forEach(function(a) {
                    var key = a.road_address || a.name;
                    if (!selectedAddrs[key] && unselected.indexOf(key) === -1) unselected.push(key);
                  });
                  var msg = mapVehicle + "호차 경로를 저장합니다.\n" +
                    "선택: " + drawRoute.length + "개 건물\n" +
                    (unselected.length > 0 ? "미선택 " + unselected.length + "개 건물은 배정 해제됩니다.\n" : "") +
                    "\nDB에 sort_order가 저장됩니다. 계속?";
                  if (!confirm(msg)) return;
                  setDrawSaving(true);
                  // 건물별 아이템 맵
                  var bldgItemMap = {};
                  vItems.forEach(function(a) {
                    var key = a.road_address || a.name;
                    if (!bldgItemMap[key]) bldgItemMap[key] = [];
                    bldgItemMap[key].push(a);
                  });
                  var promises = [];
                  var sortIdx = 1;
                  // 선택된 순서대로
                  drawRoute.forEach(function(p) {
                    var items = bldgItemMap[p.addr] || [];
                    items.forEach(function(a) {
                      promises.push(supabase.update("addresses", a.id, { sort_order: sortIdx }));
                      sortIdx++;
                    });
                  });
                  // 미선택 건물 → 배정 해제
                  unselected.forEach(function(addr) {
                    var items = bldgItemMap[addr] || [];
                    items.forEach(function(a) {
                      promises.push(supabase.update("addresses", a.id, { vehicle: null, sort_order: null }));
                    });
                  });
                  Promise.all(promises).then(function() {
                    // 저장 완료 → 경로 시각화로 전환
                    var pathCoords = [DEFAULT_START];
                    drawRoute.forEach(function(p) {
                      pathCoords.push({ lat: p.lat, lng: p.lng, key: p.addr, count: (bldgItemMap[p.addr] || []).length });
                    });
                    pathCoords.push(DEFAULT_START);
                    // 총 거리 계산
                    var totalDist = 0;
                    for (var i = 1; i < pathCoords.length; i++) {
                      var a = pathCoords[i-1], b = pathCoords[i];
                      var R = 6371000;
                      var dLat = (b.lat - a.lat) * Math.PI / 180;
                      var dLng = (b.lng - a.lng) * Math.PI / 180;
                      var x = Math.sin(dLat/2)*Math.sin(dLat/2) + Math.cos(a.lat*Math.PI/180)*Math.cos(b.lat*Math.PI/180)*Math.sin(dLng/2)*Math.sin(dLng/2);
                      totalDist += R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x));
                    }
                    // 그리기 모드 종료
                    setDrawMode(false); setDrawRoute([]);
                    if (drawLineRef.current) { drawLineRef.current.setMap(null); drawLineRef.current = null; }
                    
                    // AI 경로와 같은 방식으로 표시
                    routeLineDataRef.current = pathCoords;
                    setRouteInfo({ vehicle: mapVehicle, distKm: (totalDist / 1000).toFixed(1), bldgCount: drawRoute.length });
                    // 멀티 경로선: 현재 호차 경로선 업데이트
                    var vColor = {A:'#3B82F6',B:'#F59E0B',C:'#10B981',D:'#E91E63'}[mapVehicle] || '#7C3AED';
                    if (mapInstance.current && window.naver && window.naver.maps) {
                      // 기존 해당 호차 경로선 제거
                      if (multiRouteLineRef.current[mapVehicle]) { multiRouteLineRef.current[mapVehicle].setMap(null); }
                      if (multiRouteMarkersRef.current[mapVehicle]) { multiRouteMarkersRef.current[mapVehicle].forEach(function(m){m.setMap(null);}); }
                      multiRouteMarkersRef.current[mapVehicle] = [];
                      // 본부→1번 선 제외: pathCoords[0](본부) 제외하고 1번부터 시작
                      var drawCoords = pathCoords.slice(1, pathCoords.length - 1); // 본부/복귀 제외 실제 배송지만
                      multiRouteLineRef.current[mapVehicle] = new window.naver.maps.Polyline({
                        map: mapInstance.current,
                        path: drawCoords.map(function(c) { return new window.naver.maps.LatLng(c.lat, c.lng); }),
                        strokeColor: vColor, strokeWeight: 4, strokeOpacity: 0.85, strokeStyle: 'solid',
                      });
                      drawCoords.forEach(function(c, ri) {
                        multiRouteMarkersRef.current[mapVehicle].push(new window.naver.maps.Marker({
                          position: new window.naver.maps.LatLng(c.lat, c.lng),
                          map: mapInstance.current, zIndex: 10, clickable: false,
                          icon: {
                            content: '<div style="background:' + vColor + ';color:#fff;padding:1px 4px;border-radius:5px;font-size:9px;font-weight:800;border:1px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,0.3);white-space:nowrap;pointer-events:none;">' + (ri+1) + '번</div>',
                            anchor: new window.naver.maps.Point(16, -4),
                          },
                        }));
                      });
                      var bounds = new window.naver.maps.LatLngBounds();
                      drawCoords.forEach(function(c) { bounds.extend(new window.naver.maps.LatLng(c.lat, c.lng)); });
                      mapInstance.current.fitBounds(bounds, { top: 60, right: 40, bottom: 40, left: 40 });
                    }
                    setDrawSaving(false);
                    loadMapData();
                    alert("✅ " + mapVehicle + "호차 경로 저장 완료!\n" + drawRoute.length + "개 건물, 총 " + (totalDist / 1000).toFixed(1) + "km");
                  }).catch(function(e) {
                    alert("저장 실패: " + e.message);
                    setDrawSaving(false);
                  });
                }} style={{
                  flex: 2, padding: "8px", borderRadius: 8, border: "none",
                  background: drawRoute.length >= 2 && !drawSaving ? "linear-gradient(135deg,#F97316,#EA580C)" : T.sf,
                  color: drawRoute.length >= 2 && !drawSaving ? "#fff" : T.dim,
                  fontSize: 12, fontWeight: 700,
                  cursor: drawRoute.length >= 2 && !drawSaving ? "pointer" : "default",
                  opacity: drawRoute.length >= 2 && !drawSaving ? 1 : 0.4,
                }}>{drawSaving ? "저장중..." : "💾 경로 저장 (" + drawRoute.length + "건)"}</button>
              </div>
            </div>
          )}
        </div>
      )}

      {/* 배송코드 변경 모달 */}
      {routeCodeModal && React.createElement("div", {
        style: { position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center" },
        onClick: function(e) { if (e.target === e.currentTarget) setRouteCodeModal(null); }
      },
        React.createElement("div", { style: { background: T.card, borderRadius: 12, padding: 20, width: 300, boxShadow: "0 4px 24px rgba(0,0,0,0.3)" } },
          React.createElement("div", { style: { fontSize: 14, fontWeight: 700, marginBottom: 4 } }, "배송코드 변경"),
          React.createElement("div", { style: { fontSize: 11, color: T.muted, marginBottom: 12 } }, routeCodeModal.addr),
          React.createElement("select", {
            value: routeCodeInput,
            onChange: function(e) { setRouteCodeInput(e.target.value); },
            style: { width: "100%", padding: "8px 10px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.text, fontSize: 13, boxSizing: "border-box", marginBottom: 10, outline: "none" }
          },
            ROUTE_CODES.map(function(c) { return React.createElement("option", { key: c, value: c }, c); })
          ),
          React.createElement("div", { style: { display: "flex", gap: 8 } },
            React.createElement("button", {
              onClick: function() { setRouteCodeModal(null); },
              style: { flex: 1, padding: "9px", borderRadius: 8, border: "1px solid " + T.border, background: T.sf, color: T.muted, fontSize: 13, cursor: "pointer" }
            }, "취소"),
            React.createElement("button", {
              onClick: function() {
                if (!routeCodeInput.trim()) return;
                window.__changeRouteCode(routeCodeModal.addr, routeCodeInput.trim());
                setRouteCodeModal(null);
              },
              style: { flex: 1, padding: "9px", borderRadius: 8, border: "none", background: T.primary, color: "#fff", fontSize: 13, fontWeight: 700, cursor: "pointer" }
            }, "변경")
          )
        )
      )}

      {/* 지오코딩 상태 */}
      <div style={{ display: "flex", gap: 6, marginBottom: 8, alignItems: "center" }}>
        <div style={{ flex: 1, padding: "6px 10px", borderRadius: 8, background: T.sf, border: "1px solid " + T.border, fontSize: 10, color: T.muted }}>
          📍 좌표: <span style={{ color: cachedCnt >= allBldgCnt ? T.accent : T.warning, fontWeight: 600 }}>{cachedCnt}</span>/{allBldgCnt} 건물
          {cachedCnt < allBldgCnt && <span style={{ color: T.warning }}> ({allBldgCnt - cachedCnt}건 미변환)</span>}
        </div>
        <button onClick={function() {
          if (assignMode === "drag") { setAssignMode(null); setDragRect(null); setSelectedPins([]); setSelectionBounds(null); }
          else { setAssignMode("drag"); }
        }} style={{
          padding: assignMode === "drag" ? "5px 9px" : "6px 10px", borderRadius: 8, border: "1px solid " + (assignMode === "drag" ? T.warning : T.border),
          background: assignMode === "drag" ? T.warning + "22" : T.sf,
          color: assignMode === "drag" ? T.warning : T.muted, fontSize: 10, fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap",
        }}>
          {assignMode === "drag" ? "✋ 취소" : "⬜ 영역"}
        </button>
        <button onClick={runGeocode} disabled={geocoding} style={{
          padding: "6px 12px", borderRadius: 8, border: "none", cursor: geocoding ? "wait" : "pointer",
          background: geocoding ? T.sf : "linear-gradient(135deg,#03C75A,#02A94F)",
          color: geocoding ? T.dim : "#fff", fontSize: 10, fontWeight: 600, whiteSpace: "nowrap",
        }}>
          {geocoding ? gcProgress.done + "/" + gcProgress.total : "📍 변환"}
        </button>
        <button onClick={clearCache} title="전체 캐시 초기화" style={{
          padding: "6px 8px", borderRadius: 8, border: "none", cursor: "pointer",
          background: T.sf, color: T.dim, fontSize: 10,
        }}>↺</button>
        <button onClick={clearFailedCache} title="실패 캐시만 초기화 (주소 수정 후 재시도)" style={{
          padding: "6px 8px", borderRadius: 8, border: "none", cursor: "pointer",
          background: T.sf, color: T.warning, fontSize: 10, fontWeight: 700,
        }}>⚠↺</button>
        {cachedCnt < allBldgCnt && (
          <button onClick={function() { setShowUncached(!showUncached); }} style={{
            padding: "6px 8px", borderRadius: 8, border: "none", cursor: "pointer",
            background: showUncached ? T.warning + "22" : T.sf, color: showUncached ? T.warning : T.dim, fontSize: 10,
          }}>📋</button>
        )}
      </div>

      {/* 미변환 건물 목록 */}
      {showUncached && (function() {
        var uncachedList = [];
        var uncachedData = [];
        var now = Date.now();
        allItems.forEach(function(a) {
          var key = a.road_address || a.name;
          var cached = coordCache[key];
          var isFailed = cached && cached.failed;
          var isUncached = !cached;
          if ((isUncached || isFailed) && uncachedList.indexOf(key) === -1) {
            uncachedList.push(key);
          }
        });
        uncachedList.forEach(function(addr) {
          var items = allItems.filter(function(a) { return (a.road_address || a.name) === addr; });
          var codes = [];
          items.forEach(function(a) { if (a.route_code && codes.indexOf(a.route_code) === -1) codes.push(a.route_code); });
          var names = items.map(function(a) { return a.name; }).join(", ");
          uncachedData.push({ addr: addr, codes: codes.join(","), count: items.length, names: names, gu: items[0] ? items[0].gu || "" : "" });
        });
        var downloadExcel = function() {
          var rows = [["주소", "구", "배송코드", "업장수", "업장명"]];
          uncachedData.forEach(function(d) { rows.push([d.addr, d.gu, d.codes, d.count, d.names]); });
          var ws = XLSX.utils.aoa_to_sheet(rows);
          ws["!cols"] = [{ wch: 30 }, { wch: 10 }, { wch: 15 }, { wch: 8 }, { wch: 40 }];
          var wb = XLSX.utils.book_new();
          XLSX.utils.book_append_sheet(wb, ws, "미변환건물");
          XLSX.writeFile(wb, "미변환건물_" + uncachedList.length + "건.xlsx");
        };
        return React.createElement("div", { style: { marginBottom: 8, padding: "8px 10px", borderRadius: 8, background: T.card, border: "1px solid " + T.border } },
          React.createElement("div", { style: { display: "flex", alignItems: "center", marginBottom: 6 } },
            React.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: T.warning, flex: 1 } }, "미변환 건물 " + uncachedList.length + "건"),
            React.createElement("button", {
              onClick: async function() {
                if (!window.naver || !window.naver.maps || !window.naver.maps.Service) { alert("지도 로드 후 시도해주세요"); return; }

                // 자동 주소 수정 함수
                var autoFixAddr = function(addr, routeCode) {
                  // 오타 수정
                  var fixed = addr
                    .replace(/강나맫로/g, '강남대로')
                    .replace(/월드컴로/g, '월드컵로')
                    .replace(/국제 금융로/g, '국제금융로')
                    .replace(/\s+길/g, '길')
                    .replace(/\s+로/g, '로');
                  // 이미 시/도 포함된 경우 스킵
                  if (/^서울|^경기|^인천|^부산/.test(fixed)) return fixed;
                  // 배송코드 기반 지역 추가
                  var prefix = '';
                  if (/분당/.test(routeCode)) prefix = '경기도 성남시 분당구 ';
                  else if (/인천공항/.test(routeCode)) prefix = '인천광역시 중구 ';
                  else if (/김포공항/.test(routeCode)) prefix = '서울특별시 강서구 ';
                  else if (/강북호텔|강북/.test(routeCode)) prefix = '서울특별시 ';
                  else if (/여의도|영등포/.test(routeCode)) prefix = '서울특별시 영등포구 ';
                  else if (/마포|용산/.test(routeCode)) prefix = '서울특별시 마포구 ';
                  else if (/남부순환/.test(routeCode)) prefix = '서울특별시 관악구 ';
                  else if (/강남|테헤란|도산|서초|방배|잠실|청담|압구정/.test(routeCode)) prefix = '서울특별시 강남구 ';
                  else prefix = '서울특별시 ';
                  return prefix + fixed;
                };

                var inputs = uncachedData.map(function(d, i) {
                  var inputEl = document.getElementById("uncached-input-"+i);
                  var addr = inputEl ? inputEl.value.trim() : d.addr;
                  // 입력값이 원본과 같으면 자동 수정 적용
                  if (addr === d.addr) addr = autoFixAddr(addr, d.codes);
                  return { d: d, addr: addr };
                });

                var ok = 0, fail = 0;
                var newEntries = {};
                var total = inputs.length;
                // 진행상황 표시 엘리먼트
                var progEl = document.createElement("div");
                progEl.style.cssText = "position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1e293b;border:1px solid #3b82f6;border-radius:10px;padding:14px 24px;font-size:13px;color:#e2e8f0;z-index:9999;min-width:280px;text-align:center;box-shadow:0 4px 24px rgba(0,0,0,0.6);";
                progEl.id = "geocode-progress";
                document.body.appendChild(progEl);
                var updateProg = function(done) {
                  var pct = Math.round(done / total * 100);
                  var bar = "█".repeat(Math.round(pct/5)) + "░".repeat(20 - Math.round(pct/5));
                  progEl.innerHTML = "🔧 좌표 변환 중...<br><span style='font-size:11px;color:#94a3b8'>" + bar + "</span><br><span style='color:#3b82f6;font-weight:700'>" + done + " / " + total + "건 (" + pct + "%)</span><br><span style='font-size:10px;color:#34d399'>성공 " + ok + "</span> <span style='font-size:10px;color:#f87171'>실패 " + fail + "</span>";
                };
                updateProg(0);
                for (var i = 0; i < inputs.length; i++) {
                  await new Promise(function(resolve) {
                    var item = inputs[i];
                    window.naver.maps.Service.geocode({ query: item.addr }, function(status, response) {
                      if (status === window.naver.maps.Service.Status.OK && response.v2.addresses.length > 0) {
                        var lat = parseFloat(response.v2.addresses[0].y);
                        var lng = parseFloat(response.v2.addresses[0].x);
                        newEntries[item.d.addr] = { lat: lat, lng: lng };
                        if (item.addr !== item.d.addr) newEntries[item.addr] = { lat: lat, lng: lng };
                        var patchBody = { lat: lat, lng: lng };
                        if (item.addr !== item.d.addr) patchBody.road_address = item.addr;
                        fetch(SUPABASE_URL + "/rest/v1/addresses?road_address=eq." + encodeURIComponent(item.d.addr), {
                          method: "PATCH",
                          headers: { "apikey": SUPABASE_KEY, "Authorization": "Bearer " + SUPABASE_KEY, "Content-Type": "application/json", "Prefer": "return=minimal" },
                          body: JSON.stringify(patchBody)
                        });
                        ok++;
                      } else { fail++; }
                      resolve();
                    });
                  });
                  updateProg(i + 1);
                  await new Promise(function(r){ setTimeout(r, 300); });
                }
                document.body.removeChild(progEl);
                setCoordCache(function(prev) { return Object.assign({}, prev, newEntries); });
                alert("✅ 완료: " + ok + "건 성공, " + fail + "건 실패\nDB 좌표 업데이트됐습니다." + (fail > 0 ? "\n실패 건은 주소 직접 수정 후 재시도해주세요." : ""));
              },
              style: { padding: "4px 10px", borderRadius: 6, border: "none", background: T.accent, color: "#fff", fontSize: 10, fontWeight: 700, cursor: "pointer" }
            }, "🔧 자동수정+전체변환"),
            React.createElement("span", { style: { fontSize: 10, color: T.dim, marginLeft: 6 } }, "주소 수정 후 변환 클릭")
          ),
          // 자동 수정 미리보기
          React.createElement("div", { style: { marginBottom: 6, padding: "6px 8px", borderRadius: 6, background: T.sf, border: "1px solid " + T.border } },
            React.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: T.primary, marginBottom: 4 } }, "🔧 자동 수정 예정"),
            uncachedData.slice(0, 3).map(function(d, i) {
              var autoFixAddr = function(addr, routeCode) {
                var fixed = addr.replace(/강나맫로/g,'강남대로').replace(/월드컴로/g,'월드컵로').replace(/국제 금융로/g,'국제금융로').replace(/\s+길/g,'길').replace(/\s+로/g,'로');
                if (/^서울|^경기|^인천|^부산/.test(fixed)) return fixed;
                var prefix = '';
                if (/분당/.test(routeCode)) prefix = '경기도 성남시 분당구 ';
                else if (/인천공항/.test(routeCode)) prefix = '인천광역시 중구 ';
                else if (/김포공항/.test(routeCode)) prefix = '서울특별시 강서구 ';
                else if (/강북호텔|강북/.test(routeCode)) prefix = '서울특별시 ';
                else if (/여의도|영등포/.test(routeCode)) prefix = '서울특별시 영등포구 ';
                else if (/마포|용산/.test(routeCode)) prefix = '서울특별시 마포구 ';
                else if (/남부순환/.test(routeCode)) prefix = '서울특별시 관악구 ';
                else if (/강남|테헤란|도산|서초|방배|잠실|청담|압구정/.test(routeCode)) prefix = '서울특별시 강남구 ';
                else prefix = '서울특별시 ';
                return prefix + fixed;
              };
              var fixed = autoFixAddr(d.addr, d.codes);
              if (fixed === d.addr) return null;
              return React.createElement("div", { key: i, style: { fontSize: 9, color: T.muted, marginBottom: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } },
                React.createElement("span", { style: { color: "#EF4444" } }, d.addr),
                React.createElement("span", { style: { color: T.dim, margin: "0 4px" } }, "→"),
                React.createElement("span", { style: { color: "#059669" } }, fixed)
              );
            }),
            uncachedData.length > 3 && React.createElement("div", { style: { fontSize: 9, color: T.dim, marginTop: 2 } }, "+ " + (uncachedData.length - 3) + "건 더...")
          ),
          React.createElement("div", { style: { maxHeight: 260, overflowY: "auto", display: "flex", flexDirection: "column", gap: 4 } },
            uncachedData.map(function(d, idx) {
              var inputId = "uncached-input-" + idx;
              return React.createElement("div", { key: idx, style: { padding: "5px 6px", borderRadius: 6, background: T.sf, border: "1px solid " + T.border + "88" } },
                React.createElement("div", { style: { fontSize: 10, color: T.dim, marginBottom: 3 } },
                  React.createElement("span", { style: { color: T.warning, fontWeight: 700 } }, d.codes),
                  " · " + d.count + "업장 · " + d.names.substring(0, 20) + (d.names.length > 20 ? "..." : "")
                ),
                React.createElement("input", {
                  id: inputId,
                  defaultValue: ({
                    '인천 중구 제2터미널대로 340': '인천광역시 중구 제2터미널대로 340',
                    '안양판교로1201번길 161': '경기도 안양시 동안구 안양판교로1201번길 161',
                    '창업로 18': '경기도 성남시 분당구 창업로 18',
                    '내곡로 155': '서울특별시 서초구 내곡로 155',
                    '내곡로 159': '서울특별시 서초구 내곡로 159',
                    '강나맫로 53길 7': '서울특별시 강남구 강남대로53길 7',
                    '서울 강남구 삼성로 147길 46': '서울특별시 강남구 삼성로147길 46',
                    '퇴계로 135-8': '서울특별시 중구 퇴계로 135-8',
                    '압구정로30길 66': '서울특별시 강남구 압구정로30길 66',
                    '남대문로 131': '서울특별시 중구 남대문로 131',
                    '남대문로 132': '서울특별시 중구 남대문로 132',
                    '남대문로 133': '서울특별시 중구 남대문로 133',
                    '영등포구 국제 금융로 39': '서울특별시 영등포구 국제금융로 39',
                    '여의나루로 97': '서울특별시 영등포구 여의나루로 97',
                    '국회대로 37': '서울특별시 영등포구 국회대로 37',
                    '월드컴로5길 33-23': '서울특별시 마포구 월드컵로5길 33-23',
                    '남부순환로 2763': '서울특별시 관악구 남부순환로 2763',
                    '남부순환로 373길': '서울특별시 관악구 남부순환로373길',
                    '선릉로 146길': '서울특별시 강남구 선릉로146길',
                    '삼성로 742': '서울특별시 강남구 삼성로 742',
                    '도산대로 90 길': '서울특별시 강남구 도산대로90길',
                    '도산대로 445': '서울특별시 강남구 도산대로 445',
                    '선릉로152길 17': '서울특별시 강남구 선릉로152길 17',
                    '도산대로 165': '서울특별시 강남구 도산대로 165',
                    '압구정로 12길 13': '서울특별시 강남구 압구정로12길 13',
                  }[d.addr] || d.addr),
                  style: { width: "100%", fontSize: 11, padding: "4px 7px", borderRadius: 5, border: "1px solid " + T.border, background: T.card, color: T.text, outline: "none", boxSizing: "border-box" }
                })
              );
            })
          )
        );
      })()}

      {/* 프로그레스 바 */}
      {geocoding && (
        <div style={{ marginBottom: 8, height: 4, background: T.sf, borderRadius: 2, overflow: "hidden" }}>
          <div style={{ height: "100%", borderRadius: 2, background: "linear-gradient(90deg,#03C75A,#10B981)", width: gcProgress.total > 0 ? (gcProgress.done / gcProgress.total * 100) + "%" : "0%", transition: "width 0.3s" }}/>
        </div>
      )}

      {/* 지도 */}
      <div style={{ position: "relative" }}>
      <div ref={mapRef} style={{ width: "100%", height: mapFullscreen ? "calc(100vh - 100px)" : 520, borderRadius: mapFullscreen ? 0 : 12, overflow: "hidden", border: "1px solid " + (assignMode === "drag" ? T.warning : T.border), background: T.sf, boxSizing: "border-box" }}>
        {!mapReady && (
          <div style={{ width: "100%", height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", color: T.muted }}>
            <Icon d={I.mapView} size={28} color={T.dim}/>
            <div style={{ fontSize: 13, fontWeight: 600, marginTop: 10 }}>네이버 지도 로딩중...</div>
            <div style={{ fontSize: 10, marginTop: 4, color: T.dim }}>잠시만 기다려주세요</div>
          </div>
        )}
      </div>
      {assignMode === "drag" && React.createElement("div", {
        ref: function(el) {
          if (!el) return;
          // 네이티브 터치 이벤트 등록 (passive: false로 스크롤 차단)
          if (el.__dragBound) return;
          el.__dragBound = true;
          el.addEventListener("touchstart", function(e) {
            e.preventDefault(); e.stopPropagation();
            var t = e.touches[0], rect = el.getBoundingClientRect();
            var ox = t.clientX - rect.left, oy = t.clientY - rect.top;
            dragStartRef.current = { x: ox, y: oy };
            setDragRect({ startX: ox, startY: oy, endX: ox, endY: oy });
          }, { passive: false });
          el.addEventListener("touchmove", function(e) {
            e.preventDefault(); e.stopPropagation();
            if (!dragStartRef.current) return;
            var t = e.touches[0], rect = el.getBoundingClientRect();
            setDragRect(function(prev) { return prev ? Object.assign({}, prev, { endX: t.clientX - rect.left, endY: t.clientY - rect.top }) : prev; });
          }, { passive: false });
          el.addEventListener("touchend", function(e) {
            e.preventDefault();
            if (!dragStartRef.current || !dragRect || !mapInstance.current) { dragStartRef.current = null; return; }
            var dr = dragRect;
            var minPx = Math.min(dr.startX, dr.endX), maxPx = Math.max(dr.startX, dr.endX);
            var minPy = Math.min(dr.startY, dr.endY), maxPy = Math.max(dr.startY, dr.endY);
            if (maxPx - minPx < 10 || maxPy - minPy < 10) { dragStartRef.current = null; setDragRect(null); return; }
            var map = mapInstance.current;
            var mapEl = mapRef.current;
            var rect = mapEl.getBoundingClientRect();
            var bounds = map.getBounds();
            var w = rect.width, h = rect.height;
            var ne = bounds.getNE(), sw = bounds.getSW();
            var lngMin = sw.lng() + (minPx / w) * (ne.lng() - sw.lng());
            var lngMax = sw.lng() + (maxPx / w) * (ne.lng() - sw.lng());
            var latMax = ne.lat() - (minPy / h) * (ne.lat() - sw.lat());
            var latMin = ne.lat() - (maxPy / h) * (ne.lat() - sw.lat());
            var sel = [];
            Object.keys(buildings).forEach(function(a) { var c = coordCache[a]; if (c && c.lat >= latMin && c.lat <= latMax && c.lng >= lngMin && c.lng <= lngMax) sel.push(a); });
            setSelectedPins(sel); setSelectionBounds({latMin:latMin,latMax:latMax,lngMin:lngMin,lngMax:lngMax}); dragStartRef.current = null; setDragRect(null); setAssignMode(null);
          }, { passive: false });
        },
        style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, cursor: "crosshair", zIndex: 10, borderRadius: 12, touchAction: "none", overscrollBehavior: "none" },
        onMouseDown: function(e) {
          var rect = e.currentTarget.getBoundingClientRect();
          var ox = e.clientX - rect.left, oy = e.clientY - rect.top;
          dragStartRef.current = { x: ox, y: oy };
          setDragRect({ startX: ox, startY: oy, endX: ox, endY: oy });
        },
        onMouseMove: function(e) {
          if (!dragStartRef.current) return;
          var rect = e.currentTarget.getBoundingClientRect();
          setDragRect(function(prev) { return prev ? Object.assign({}, prev, { endX: e.clientX - rect.left, endY: e.clientY - rect.top }) : prev; });
        },
        onMouseUp: function(e) {
          if (!dragStartRef.current || !dragRect || !mapInstance.current) { dragStartRef.current = null; return; }
          var rect = e.currentTarget.getBoundingClientRect();
          var eX = e.clientX - rect.left, eY = e.clientY - rect.top;
          var minPx = Math.min(dragStartRef.current.x, eX), maxPx = Math.max(dragStartRef.current.x, eX);
          var minPy = Math.min(dragStartRef.current.y, eY), maxPy = Math.max(dragStartRef.current.y, eY);
          if (maxPx - minPx < 10 || maxPy - minPy < 10) { dragStartRef.current = null; setDragRect(null); return; }
          var map = mapInstance.current;
          var bounds = map.getBounds();
          var w = rect.width, h = rect.height;
          var ne = bounds.getNE(), sw = bounds.getSW();
          var lngMin = sw.lng() + (minPx / w) * (ne.lng() - sw.lng());
          var lngMax = sw.lng() + (maxPx / w) * (ne.lng() - sw.lng());
          var latMax = ne.lat() - (minPy / h) * (ne.lat() - sw.lat());
          var latMin = ne.lat() - (maxPy / h) * (ne.lat() - sw.lat());
          var sel = [];
          Object.keys(buildings).forEach(function(a) { var c = coordCache[a]; if (c && c.lat >= latMin && c.lat <= latMax && c.lng >= lngMin && c.lng <= lngMax) sel.push(a); });
          setSelectedPins(sel); setSelectionBounds({latMin:latMin,latMax:latMax,lngMin:lngMin,lngMax:lngMax}); dragStartRef.current = null; setDragRect(null); setAssignMode(null);
        }
      },
        dragRect ? React.createElement("div", { style: {
          position: "absolute",
          left: Math.min(dragRect.startX, dragRect.endX),
          top: Math.min(dragRect.startY, dragRect.endY),
          width: Math.abs(dragRect.endX - dragRect.startX),
          height: Math.abs(dragRect.endY - dragRect.startY),
          border: "2px dashed " + T.warning, background: T.warning + "22", borderRadius: 4, pointerEvents: "none"
        }}) : null
      )}
      </div>

      {/* 범례 + 호차현황 + 배송코드분포 (전체화면 시 숨김) */}
      {!mapFullscreen && (
      <div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8, padding: "8px 10px", borderRadius: 8, background: T.sf, border: "1px solid " + T.border }}>
        {VEHICLES.map(function(v) {
          return (
            <div key={v} style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 10, color: T.muted }}>
              <div style={{ width: 10, height: 10, borderRadius: "50%", background: vc(v) }}/>{v}호차
            </div>
          );
        })}
        <div style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 10, color: T.muted }}>
          <div style={{ width: 10, height: 10, borderRadius: "50%", background: "#64748B" }}/>미배정
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 10, color: T.muted }}>
          <div style={{ width: 10, height: 10, borderRadius: "50%", background: "#10B981", opacity: 0.55 }}/>완료
        </div>
      </div>

      {/* 호차별 현황 */}
      <div style={{ marginTop: 8, padding: "10px", borderRadius: 10, background: T.card, border: "1px solid " + T.border }}>
        <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8 }}>🗺️ 호차별 배송지 현황</div>
        {VEHICLES.map(function(v) {
          var vItems = allItems.filter(function(a) { return a.vehicle === v; });
          var vDone = vItems.filter(function(a) { return a.status === "done"; }).length;
          var vMq = vItems.reduce(function(s, a) { var mq = a.magazine_qty || {}; return s + Object.values(mq).reduce(function(ss, n) { return ss + (n || 0); }, 0); }, 0);
          var vBldg = {};
          vItems.forEach(function(a) { vBldg[a.road_address || a.name] = true; });
          var vBldgCnt = Object.keys(vBldg).length;
          return (
            <div key={v} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
              <div style={{ width: 22, height: 22, borderRadius: 6, background: vc(v), display: "flex", alignItems: "center", justifyContent: "center", color: "#fff", fontSize: 10, fontWeight: 700, flexShrink: 0 }}>{v}</div>
              <div style={{ flex: 1 }}>
                <div style={{ height: 5, borderRadius: 3, background: T.sf, overflow: "hidden" }}>
                  <div style={{ width: vItems.length > 0 ? (vDone / vItems.length * 100) + "%" : "0%", height: "100%", borderRadius: 3, background: vc(v), transition: "width 0.3s" }}/>
                </div>
              </div>
              <div style={{ fontSize: 10, color: T.muted, minWidth: 110, textAlign: "right" }}>
                {vDone}/{vItems.length}건 · {vBldgCnt}건물 · {vMq}권
              </div>
            </div>
          );
        })}
        {/* 미배정 */}
        {(function() {
          var noV = allItems.filter(function(a) { return !a.vehicle; });
          if (noV.length === 0) return null;
          var nBldg = {};
          noV.forEach(function(a) { nBldg[a.road_address || a.name] = true; });
          return (
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 2 }}>
              <div style={{ width: 22, height: 22, borderRadius: 6, background: T.sf, border: "1px solid " + T.border, display: "flex", alignItems: "center", justifyContent: "center", color: T.dim, fontSize: 9, fontWeight: 700, flexShrink: 0 }}>-</div>
              <div style={{ flex: 1, fontSize: 10, color: T.dim }}>미배정</div>
              <div style={{ fontSize: 10, color: T.dim, minWidth: 110, textAlign: "right" }}>
                {noV.length}건 · {Object.keys(nBldg).length}건물
              </div>
            </div>
          );
        })()}
      </div>

      {/* 배송코드별 분포 */}
      <div style={{ marginTop: 8, padding: "10px", borderRadius: 10, background: T.card, border: "1px solid " + T.border }}>
        <div style={{ fontSize: 12, fontWeight: 700, marginBottom: 8 }}>📊 배송코드별 분포 (상위 10개)</div>
        {(function() {
          var codeCnt = {};
          Object.values(buildings).forEach(function(b) {
            var code = b.route_code || "기타";
            codeCnt[code] = (codeCnt[code] || 0) + b.items.length;
          });
          var sorted = Object.entries(codeCnt).sort(function(a, b) { return b[1] - a[1]; }).slice(0, 10);
          var maxVal = sorted.length > 0 ? sorted[0][1] : 1;
          return sorted.map(function(entry) {
            return (
              <div key={entry[0]} style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
                <div style={{ width: 65, fontSize: 10, fontWeight: 600, color: T.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{entry[0]}</div>
                <div style={{ flex: 1, height: 5, borderRadius: 3, background: T.sf, overflow: "hidden" }}>
                  <div style={{ width: (entry[1] / maxVal * 100) + "%", height: "100%", borderRadius: 3, background: T.primary, transition: "width 0.3s" }}/>
                </div>
                <div style={{ fontSize: 10, fontWeight: 700, color: T.primary, minWidth: 30, textAlign: "right" }}>{entry[1]}</div>
              </div>
            );
          });
        })()}
      </div>
      </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════
// 배본일정 탭 컴포넌트
// ═══════════════════════════════════════
function ScheduleTab({ T, I, Icon, dbAddresses, setDbAddresses, ROUTE_CODES, supabase, vehicleInfo, adminPassword, scheduleUnlocked, setScheduleUnlocked }) {
  const DAYS = ["일","월","화","수","목","금","토"];
  const VH_COLORS = VH_COLORS_GLOBAL;
  const VH_BG    = ["#EFF6FF","#FFFBEB","#F0FDF4","#FDF2F8"];
  const MONTHS   = ["1월호","2월호","3월호","4월호","5월호","6월호","7월호","8월호","9월호","10월호","11월호","12월호"];

  // ── 비밀번호 잠금 (DeliveryApp에서 관리 - 탭 이동시 유지) ──
  const [pwInput, setPwInput] = useState("");
  const unlocked = scheduleUnlocked;
  const setUnlocked = setScheduleUnlocked;

  // ── 로컬 스토리지 helpers ──
  const LS_DATA = "dm_sch_data2";
  const ls_get = (k,d) => { try { const v=localStorage.getItem(k); return v?JSON.parse(v):d; } catch(e){ return d; } };
  const ls_set = (k,v) => { try { localStorage.setItem(k,JSON.stringify(v)); } catch(e){} };

  const [allData, setAllData] = useState(() => ls_get(LS_DATA, {"4월호": BASE_4}));
  const [dbLoaded, setDbLoaded] = useState(false);

  // DB에서 배본일정 로드
  React.useEffect(() => {
    supabase.fetch("settings", "key=eq.schedule_data&limit=1").then(rows => {
      if (rows && rows[0] && rows[0].value) {
        try {
          const parsed = JSON.parse(rows[0].value);
          setAllData(parsed);
          ls_set(LS_DATA, parsed);
        } catch(e) {}
      }
      setDbLoaded(true);
    }).catch(() => setDbLoaded(true));
  }, []);

  const saveAll = (newData) => {
    setAllData(newData);
    ls_set(LS_DATA, newData);
    // DB에도 저장
    const val = JSON.stringify(newData);
    supabase.fetch("settings", "key=eq.schedule_data&limit=1").then(rows => {
      if (rows && rows[0]) {
        supabase.updateByKey("settings", "key", "schedule_data", { value: val });
      } else {
        window.fetch(`${SUPABASE_URL}/rest/v1/settings`, {
          method: "POST",
          headers: { "apikey": SUPABASE_KEY, "Authorization": "Bearer " + SUPABASE_KEY, "Content-Type": "application/json", "Prefer": "return=minimal" },
          body: JSON.stringify({ key: "schedule_data", value: val })
        });
      }
    }).catch(() => {});
  };

  // vehicles는 전역 vehicleInfo 사용
  const vehicles = vehicleInfo || DEFAULT_VEHICLES;

  // 기본 4월호 데이터
  const BASE_4 = [
    {date:"2026-03-23",notice:"★ 노블레스 입고",tasks:[["강북호텔3"],["강남호텔"],["여의도/하얏트"],[]]},
    {date:"2026-03-24",notice:"",tasks:[["도산대로(신사동)"],["강남호텔 1,2"],["청담2"],["강북호텔1"]]},
    {date:"2026-03-25",notice:"★ 웨딩 입고",tasks:[["서초방배1"],["강남금융4"],["서초방배3"],["강북2"]]},
    {date:"2026-03-26",notice:"",tasks:[["분당 호텔/백화점","남서울cc"],["잠실","강남금융4 미비"],["김포공항","목동,영등포"],["강북3"]]},
    {date:"2026-03-27",notice:"",tasks:[["분당(남부CC,구미동)"],["방배빌라"],["여의도"],["강남금융기관2"]]},
    {date:"2026-03-30",notice:"",tasks:[["신사 내부2, 가로수"],["강북호텔 2"],["청담1"],["서초방배2,4"]]},
    {date:"2026-03-31",notice:"★ 맨 입고 예정",tasks:[["도산대로(신사동)"],["인천공항(Y)"],["청담3,4"],["강남대로3"]]},
    {date:"2026-04-01",notice:"",tasks:[["강북호텔3","(아트,맨,바젤Y)"],["청담빌라(Y)"],["서초주거지"],["강남금융기관3"]]},
    {date:"2026-04-02",notice:"",tasks:[["한남"],["강북호텔2(맨)"],["강북주상복합"],["강북1"]]},
    {date:"2026-04-03",notice:"",tasks:[["도산대로(맨)"],["테헤란로1,GFC(Y)"],["남부순환로2"],["강북호텔1(맨)"]]},
    {date:"2026-04-06",notice:"",tasks:[["신사동(맨)","내부1,로데오"],["테헤란로2(Y)","테헤란로1(맨)"],["청담2(맨)"],["강남금융기관1"]]},
    {date:"2026-04-07",notice:"",tasks:[["강남대로1 1차"],["테헤란로3(Y)"],["청담1(맨)"],["남부순환로1/영동대로"]]},
    {date:"2026-04-08",notice:"",tasks:[["강남대로1 2차"],["강남호텔1,2(맨,웨딩)"],["청담3,4(맨)"],["강남대로2/금융1미비"]]},
    {date:"2026-04-09",notice:"",tasks:[["분당(코오롱/야탑/율동)"],["압구정외부"],["건대/성수/용답"],[]]},
    {date:"2026-04-10",notice:"",tasks:[["신사동맨","가로수,내부2"],["압구정외부 미비"],["마포/용산"],[]]},
  ];

  const makeEmpty = (monthLabel) => {
    const m = parseInt(monthLabel);
    const year = 2026;
    const rows = [];
    let d = new Date(year, m-1, 1);
    while(d.getDay()!==1) d.setDate(d.getDate()+1);
    for(let i=0;i<15&&rows.length<12;i++){
      const wd=d.getDay();
      if(wd!==0&&wd!==6){
        const pad=n=>String(n).padStart(2,"0");
        rows.push({date:`${year}-${pad(m)}-${pad(d.getDate())}`,notice:"",tasks:[[],[],[],[]]});
      }
      d.setDate(d.getDate()+1);
    }
    return rows;
  };

  const [selMonth, setSelMonth] = useState(() => ls_get("dm_selMonth", null) || (() => { const m = new Date().getMonth() + 1; return m + "월호"; })());
  const [showMonthPicker, setShowMonthPicker] = useState(false);
  const [firstDateModal, setFirstDateModal] = useState(false); // 첫 배포일 지정
  const [firstDateInput, setFirstDateInput] = useState("");
  const [history, setHistory] = useState([]);
  const [dragSrc, setDragSrc] = useState(null);
  const [dragOverKey, setDragOverKey] = useState(null);
  const [dateModal, setDateModal] = useState(null);
  const [starOn, setStarOn] = useState(false);
  const [toast, setToast] = useState("");

  const rows = allData[selMonth] || [];
  const visibleRows = rows.filter(row => row.date);

  const showToast = (msg) => {
    setToast(msg);
    setTimeout(()=>setToast(""), 2000);
  };

  const fmtDate = (d) => {
    const dt=new Date(d+"T00:00:00");
    return { label:`${dt.getMonth()+1}/${dt.getDate()}`, day:DAYS[dt.getDay()], full:`${dt.getFullYear()}. ${dt.getMonth()+1}. ${dt.getDate()}(${DAYS[dt.getDay()]})` };
  };

  // 월 선택
  const switchMonth = (m) => {
    const newData = {...allData};
    if(!newData[m]) newData[m] = makeEmpty(m.replace("월호",""));
    saveAll(newData);
    setSelMonth(m);
    ls_set("dm_selMonth", m);
    setShowMonthPicker(false);
  };

  // 드래그
  const onDragStart = (ri, wi, ci) => setDragSrc({ri,wi,ci});
  const onDragEnd = () => { setDragSrc(null); setDragOverKey(null); };
  const onDragOver = (e, key) => { e.preventDefault(); setDragOverKey(key); };
  const onDragLeave = (e, key) => { if(!e.currentTarget.contains(e.relatedTarget)) setDragOverKey(null); };

  const dropTo = (tRi, tWi, tCi) => {
    if(!dragSrc) return;
    const {ri:fRi,wi:fWi,ci:fCi} = dragSrc;
    if(fRi===tRi&&fWi===tWi&&fCi===tCi){ setDragSrc(null); return; }

    setHistory(h=>[...h, JSON.parse(JSON.stringify(allData))]);
    const newRows = JSON.parse(JSON.stringify(rows));
    // visibleRows 인덱스 → rows 실제 인덱스 변환
    const fActual = newRows.findIndex(r => r.date === visibleRows[fRi]?.date);
    const tActual = newRows.findIndex(r => r.date === visibleRows[tRi]?.date);
    if(fActual<0||tActual<0){ setDragSrc(null); return; }

    const srcTask = newRows[fActual].tasks[fWi][fCi];
    newRows[fActual].tasks[fWi].splice(fCi,1);
    if(!newRows[tActual].tasks[tWi]) newRows[tActual].tasks[tWi]=[];

    if(fActual===tActual&&fWi===tWi){
      const ins = fCi<tCi ? tCi-1 : tCi;
      newRows[tActual].tasks[tWi].splice(ins,0,srcTask);
      showToast("순서 변경");
    } else {
      newRows[tActual].tasks[tWi].splice(tCi,0,srcTask);
      showToast(`${vehicles[fWi]?.num||fWi}→${vehicles[tWi]?.num||tWi} 이동`);
    }
    saveAll({...allData,[selMonth]:newRows});
    setDragSrc(null); setDragOverKey(null);
  };

  const undoLast = () => {
    if(!history.length){ showToast("되돌릴 내역 없음"); return; }
    const prev = history[history.length-1];
    setHistory(h=>h.slice(0,-1));
    saveAll(prev);
    showToast("되돌리기 완료");
  };

  // ── 배송 적용 ──
  const [applyModal, setApplyModal] = useState(null);
  const [applying, setApplying] = useState(false);
  const [openUnassign, setOpenUnassign] = useState(null); // 열린 호차 해제 버튼 (A/B/C/D)
  // ── 셀 드롭다운 ──
  const [cellDropdown, setCellDropdown] = useState(null);
  const [cellSearch, setCellSearch] = useState("");

  const openCellDropdown = (e, ri, wi) => {
    e.stopPropagation();
    setCellSearch("");
    setCellDropdown({ ri, wi });
  };

  const toggleCellCode = (ri, wi, code) => {
    // 실제 rows 인덱스 찾기
    const targetDate = visibleRows[ri]?.date;
    const actualRi = rows.findIndex(r => r.date === targetDate);
    if (actualRi < 0) return;
    setHistory(h=>[...h, JSON.parse(JSON.stringify(allData))]);
    const newRows = JSON.parse(JSON.stringify(rows));
    const cur = newRows[actualRi].tasks[wi] || [];
    const idx = cur.indexOf(code);
    if (idx >= 0) {
      cur.splice(idx, 1); // 이미 있으면 제거
    } else {
      cur.push(code); // 없으면 추가
    }
    newRows[actualRi].tasks[wi] = cur;
    saveAll({...allData, [selMonth]: newRows});
  };

  const applySchedule = async (row) => {
    if (!dbAddresses || !setDbAddresses) return;
    setApplying(true);
    const VK = ["A","B","C","D"];
    // tasks[i] 배열의 각 텍스트가 ROUTE_CODES에 정확히 있으면 매핑
    // { routeCode → vehicle }
    const codeToVehicle = {};
    VK.forEach((v, i) => {
      (row.tasks[i] || []).forEach(text => {
        const matched = ROUTE_CODES.find(rc => rc === text.trim());
        if (matched) codeToVehicle[matched] = v;
      });
    });

    if (Object.keys(codeToVehicle).length === 0) {
      showToast("매칭된 배송코드 없음");
      setApplying(false);
      setApplyModal(null);
      return;
    }

    // 기존 vehicle 초기화 대상: 이번 적용에 포함된 코드들에 속한 address
    const affectedCodes = Object.keys(codeToVehicle);
    const toUpdate = dbAddresses.filter(a => affectedCodes.includes(a.route_code));

    try {
      // 50건씩 배치 업데이트
      for (let i = 0; i < toUpdate.length; i += 50) {
        await Promise.all(
          toUpdate.slice(i, i + 50).map(a =>
            supabase.update("addresses", a.id, { vehicle: codeToVehicle[a.route_code], sort_order: null })
          )
        );
      }
      // allData 즉시 갱신 → 모든 탭 반영
      setDbAddresses(prev => prev.map(a =>
        codeToVehicle[a.route_code]
          ? { ...a, vehicle: codeToVehicle[a.route_code], sort_order: null }
          : a
      ));
      showToast(`✅ ${toUpdate.length}건 배송 적용 완료`);
    } catch(e) {
      showToast("오류: " + e.message);
    } finally {
      setApplying(false);
      setApplyModal(null);
    }
  };

  // 날짜 모달 - visibleRows 인덱스 사용
  const openDateModal = (ri) => {
    const r = visibleRows[ri];
    setStarOn((r.notice||"").startsWith("★"));
    setDateModal({ri, date:r.date, notice:(r.notice||"").replace(/^★\s*/,"")});
  };
  const saveDateModal = () => {
    if(!dateModal) return;
    setHistory(h=>[...h, JSON.parse(JSON.stringify(allData))]);
    // visibleRows[ri]의 실제 rows 인덱스 찾기
    const targetDate = visibleRows[dateModal.ri]?.date;
    const newRows = JSON.parse(JSON.stringify(rows));
    const actualRi = newRows.findIndex(r => r.date === targetDate);
    if (actualRi < 0) return;
    const txt = dateModal.notice.trim();
    newRows[actualRi].date = dateModal.date;
    newRows[actualRi].notice = txt ? (starOn?"★ "+txt:txt) : "";
    saveAll({...allData,[selMonth]:newRows});
    setDateModal(null); showToast("저장됨");
  };

  // CSV 다운로드 (구글 시트용)
  const downloadCsv = () => {
    const header = ["일차","일자", ...vehicles.map(v=>`${v.num} ${v.name}`), "공지"];
    const dataRows = visibleRows.map((row, ri) => {
      const {full} = fmtDate(row.date);
      return [ri+1, full, ...vehicles.map((_,i) => (row.tasks[i]||[]).join(" / ")), row.notice||""];
    });
    const csv = [header, ...dataRows].map(r =>
      r.map(c => `"${String(c).replace(/"/g,'""')}"`).join(",")
    ).join("\n");
    const blob = new Blob(["\uFEFF"+csv], {type:"text/csv;charset=utf-8"});
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = `배본일정_${selMonth}.csv`;
    a.click();
    showToast("CSV 다운로드 완료");
  };

  // 첫 배포일 지정 → 직전 월 tasks 복사 + 날짜만 교체
  const applyFirstDate = (dateStr) => {
    if (!dateStr) return;
    const pad = n => String(n).padStart(2,"0");

    // 직전 월 tasks 가져오기
    const prevMonthIdx = MONTHS.indexOf(selMonth) - 1;
    const prevRows = prevMonthIdx >= 0 ? (allData[MONTHS[prevMonthIdx]] || []) : [];
    const srcTasks = prevRows.map(r => r.tasks || [[],[],[],[]]);

    const newRows = [];
    let d = new Date(dateStr + "T00:00:00");
    let srcIdx = 0;
    while (newRows.length < 18) {
      const wd = d.getDay();
      if (wd !== 0 && wd !== 6) {
        // 직전 월 tasks 있으면 복사, 없으면 빈 배열
        const tasks = srcIdx < srcTasks.length
          ? JSON.parse(JSON.stringify(srcTasks[srcIdx]))
          : [[],[],[],[]];
        newRows.push({
          date: `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`,
          notice: "",
          tasks,
        });
        srcIdx++;
      }
      d.setDate(d.getDate()+1);
    }
    setHistory(h=>[...h, JSON.parse(JSON.stringify(allData))]);
    saveAll({...allData, [selMonth]: newRows});
    setFirstDateModal(false);
    showToast(prevRows.length > 0 ? `${selMonth} 전월 일정 복사 완료` : `${selMonth} 18일 생성됨`);
  };

  // 날짜 행 추가
  const addRow = () => {
    const lastRow = rows[rows.length-1];
    let nextDate;
    if (lastRow) {
      const d = new Date(lastRow.date + "T00:00:00");
      d.setDate(d.getDate()+1);
      while (d.getDay()===0||d.getDay()===6) d.setDate(d.getDate()+1);
      const pad = n => String(n).padStart(2,"0");
      nextDate = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
    } else {
      nextDate = new Date().toISOString().split("T")[0];
    }
    setHistory(h=>[...h, JSON.parse(JSON.stringify(allData))]);
    saveAll({...allData, [selMonth]: [...rows, {date:nextDate, notice:"", tasks:[[],[],[], []]}]});
    showToast("날짜 추가됨");
  };

  const notices = visibleRows.filter(r=>r.notice);

  // ── CSS 인라인 스타일 helpers ──
  const colStyle = (wi, isDragOver) => ({
    display:"flex", flexDirection:"column", gap:2, minHeight:52,
    borderRadius:7, padding:2,
    background: isDragOver ? VH_BG[wi]+"cc" : "transparent",
    boxShadow: isDragOver ? `0 0 0 2px ${VH_COLORS[wi]}` : "none",
    transition:"all 0.1s",
  });
  const cardStyle = (wi, isDragOver, isDragging) => ({
    borderRadius:5, padding:"4px 6px", cursor:"grab",
    background: VH_BG[wi], color: wi===0?"#1D4ED8":wi===1?"#065F46":wi===2?"#92400E":"#9D174D",
    border:`1px solid ${VH_COLORS[wi]}44`,
    boxShadow: isDragOver ? `0 0 0 2px ${VH_COLORS[wi]}` : "none",
    transform: isDragOver?"scale(1.04)":isDragging?"scale(0.94)":"none",
    opacity: isDragging ? 0.2 : 1,
    fontSize:10, fontWeight:600, lineHeight:1.4,
    transition:"all 0.1s", userSelect:"none",
  });

  return (
    <div style={{background:T.bg, minHeight:"100vh", paddingBottom:60, position:"relative"}} onClick={()=>{if(showMonthPicker)setShowMonthPicker(false);}}>

      {/* ── 비밀번호 잠금 ── */}
      {!unlocked ? (
        <div style={{textAlign:"center", padding:"60px 20px"}}>
          <div style={{fontSize:36, marginBottom:12}}>📅</div>
          <div style={{fontSize:16, fontWeight:700, marginBottom:4}}>배본일정</div>
          <div style={{fontSize:11, color:T.muted, marginBottom:24}}>비밀번호를 입력해주세요</div>
          <input type="password" inputMode="numeric" value={pwInput}
            onChange={e=>setPwInput(e.target.value)}
            onKeyDown={e=>{if(e.key==="Enter"){if(pwInput===adminPassword)setUnlocked(true);else{alert("비밀번호가 틀렸습니다.");setPwInput("");}}}
            }
            placeholder="비밀번호"
            style={{width:160,padding:"12px",borderRadius:10,border:"2px solid #E2E8F0",background:"#F8FAFC",color:"#0F172A",fontSize:20,textAlign:"center",outline:"none",letterSpacing:8,display:"block",margin:"0 auto 12px"}}
          />
          <button onClick={()=>{if(pwInput===adminPassword)setUnlocked(true);else{alert("비밀번호가 틀렸습니다.");setPwInput("");}}} style={{
            padding:"10px 40px",borderRadius:10,border:"none",cursor:"pointer",
            background:"linear-gradient(135deg,#3B82F6,#2563EB)",color:"#fff",fontSize:14,fontWeight:700,
          }}>확인</button>
        </div>
      ) : (
        <React.Fragment>

          {/* ── 상단 바 ── */}
          <div style={{background:T.sf, borderBottom:`1px solid ${T.border}`, padding:"7px 10px", display:"flex", alignItems:"center", gap:6, flexWrap:"wrap"}}>
            <div style={{position:"relative"}} onClick={e=>e.stopPropagation()}>
              <button onClick={()=>setShowMonthPicker(p=>!p)} style={{
                padding:"5px 14px", borderRadius:8, border:`1px solid ${T.border}`,
                background:T.card, color:T.text, fontSize:13, fontWeight:700, cursor:"pointer",
                display:"flex", alignItems:"center", gap:6,
              }}>
                📅 {selMonth}
                <span style={{fontSize:10, color:T.muted}}>▼</span>
              </button>
              {showMonthPicker && (
                <div style={{
                  position:"absolute", top:"calc(100% + 4px)", left:0, zIndex:200,
                  background:T.card, border:`1px solid ${T.border}`, borderRadius:10,
                  padding:6, display:"grid", gridTemplateColumns:"repeat(3,1fr)", gap:4, width:180,
                  boxShadow:"0 4px 16px rgba(0,0,0,0.15)",
                }}>
                  {MONTHS.map(m=>(
                    <button key={m} onClick={()=>switchMonth(m)} style={{
                      padding:"6px 4px", borderRadius:7, border:"none", cursor:"pointer", fontSize:12,
                      background:selMonth===m?T.primary:T.sf,
                      color:selMonth===m?"#fff":T.muted, fontWeight:selMonth===m?700:400,
                    }}>{m}</button>
                  ))}
                </div>
              )}
            </div>
            <div style={{marginLeft:"auto", display:"flex", gap:6}}>
              <button onClick={()=>setFirstDateModal(true)} style={{padding:"5px 10px", borderRadius:7, fontSize:11, border:`1px solid ${T.border}`, background:T.warning+"22", color:T.warning, cursor:"pointer", fontWeight:600}}>📅 첫 배포일</button>
              <button onClick={undoLast} style={{padding:"5px 10px", borderRadius:7, fontSize:11, border:`1px solid ${T.border}`, background:"transparent", color:T.muted, cursor:"pointer"}}>↩ 되돌리기</button>
              <button onClick={downloadCsv} style={{padding:"5px 12px", borderRadius:7, fontSize:11, border:"none", background:T.accent, color:"#fff", cursor:"pointer", fontWeight:700}}>⬇ CSV</button>
            </div>
          </div>

          {/* ── 공지 배너 ── */}
          {notices.length>0 && (
            <div style={{padding:"5px 10px", background:"#FFFBEB", borderBottom:`1px solid ${T.warning}44`, display:"flex", gap:12, flexWrap:"wrap"}}>
              {notices.map((r,i)=>{
                const {label,day}=fmtDate(r.date);
                return (
                  <div key={i} style={{display:"flex",alignItems:"center",gap:5,fontSize:11,color:T.warning}}>
                    <div style={{width:5,height:5,borderRadius:"50%",background:T.warning}}/>
                    <span><b>{label}({day})</b> {r.notice}</span>
                  </div>
                );
              })}
            </div>
          )}

          {/* ══ 그리드 ══ */}
          <div style={{padding:"6px 8px", overflowX:"auto"}}>
            <div style={{display:"grid", gridTemplateColumns:`52px repeat(4,1fr)`, gap:3, minWidth:320}}>
              {/* 헤더 */}
              <div/>
              {vehicles.map((v,i)=>{
                const vk = ["A","B","C","D"][i];
                const assignedCount = dbAddresses ? dbAddresses.filter(a=>a.vehicle===vk).length : 0;
                const isOpen = openUnassign === vk;
                return (
                  <div key={i} onClick={()=>setOpenUnassign(isOpen ? null : vk)} style={{
                    borderRadius:6, padding:"4px 3px", textAlign:"center",
                    background:VH_BG[i], border: isOpen ? `2px solid ${VH_COLORS[i]}` : `1px solid ${VH_COLORS[i]}44`,
                    display:"flex", flexDirection:"column", alignItems:"center", gap:1, cursor:"pointer",
                  }}>
                    <span style={{fontSize:12,fontWeight:800,color:VH_COLORS[i]}}>{v.num}</span>
                    <span style={{fontSize:10,color:"#0F172A",fontWeight:600}}>{v.name}</span>
                    {isOpen && assignedCount > 0 && (
                      <div onClick={e=>{
                        e.stopPropagation();
                        if (!confirm(`${v.num} 호차 ${assignedCount}건 배정을 해제하시겠습니까?\n완료상태도 초기화됩니다.`)) return;
                        const items = dbAddresses.filter(a=>a.vehicle===vk);
                        Promise.all(items.map(a=>supabase.update("addresses",a.id,{vehicle:null,sort_order:null,status:"pending"})))
                          .then(()=>{
                            setDbAddresses(prev=>prev.map(a=>a.vehicle===vk?{...a,vehicle:null,sort_order:null,status:"pending"}:a));
                            setOpenUnassign(null);
                            showToast(`✅ ${v.num} ${items.length}건 해제 완료`);
                          }).catch(e=>showToast("해제 실패: "+e.message));
                      }} style={{
                        marginTop:2, padding:"2px 6px", borderRadius:4,
                        background:"#EF444422", border:"1px solid #EF444444",
                        fontSize:8, fontWeight:700, color:"#EF4444", cursor:"pointer",
                      }}>배정 해제 {assignedCount}건</div>
                    )}
                    {isOpen && assignedCount === 0 && (
                      <span style={{fontSize:8,color:"#94a3b8",marginTop:2}}>배정없음</span>
                    )}
                  </div>
                );
              })}

              {/* 데이터 행 */}
              {visibleRows.map((row,ri)=>{
                const {label,day}=fmtDate(row.date);
                return (
                  <React.Fragment key={ri}>
                    <div style={{
                      borderRadius:7, minHeight:52, overflow:"hidden",
                      background:row.notice?`${T.warning}18`:T.card,
                      border:row.notice?`1.5px solid ${T.warning}`:`1px solid ${T.border}`,
                      display:"flex", flexDirection:"column",
                    }}>
                      <div onClick={()=>openDateModal(ri)} style={{padding:"5px 4px", cursor:"pointer", flex:1, display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center", gap:2}}>
                        <span style={{fontSize:12,fontWeight:700,color:T.text}}>{label}</span>
                        <span style={{fontSize:9,color:T.dim}}>({day})</span>
                        {row.notice
                          ? <span style={{fontSize:8,padding:"2px 4px",background:T.warning+"33",color:T.warning,borderRadius:4,textAlign:"center",width:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",lineHeight:1.4}}>{row.notice}</span>
                          : <span style={{fontSize:8,color:T.border,marginTop:2}}>공지</span>
                        }
                      </div>
                    </div>

                    {vehicles.map((_,wi)=>{
                      const zones = row.tasks[wi]||[];
                      const colKey = `col-${ri}-${wi}`;
                      const isColOver = dragOverKey===colKey;
                      return (
                        <div key={wi}
                          style={colStyle(wi, isColOver)}
                          onDragOver={e=>onDragOver(e,colKey)}
                          onDragLeave={e=>onDragLeave(e,colKey)}
                          onDrop={e=>{e.preventDefault();dropTo(ri,wi,zones.length);}}
                          onDoubleClick={e=>openCellDropdown(e,ri,wi)}
                        >
                          {zones.length===0 ? (
                            <div style={{flex:1,borderRadius:6,border:`1px dashed ${T.border}`,display:"flex",alignItems:"center",justifyContent:"center",fontSize:11,color:T.dim,minHeight:48,background:isColOver?VH_BG[wi]:"transparent"}}>—</div>
                          ) : zones.map((task,ci)=>{
                            const cardKey=`${ri}-${wi}-${ci}`;
                            const isDragOver=dragOverKey===cardKey;
                            const isDragging=dragSrc&&dragSrc.ri===ri&&dragSrc.wi===wi&&dragSrc.ci===ci;
                            const targetDate = visibleRows[ri]?.date;
                            const actualRi = rows.findIndex(r=>r.date===targetDate);
                            const memoVal = actualRi>=0 ? (rows[actualRi].memos?.[wi]?.[ci] || "") : "";
                            return (
                              <div key={ci} draggable
                                style={cardStyle(wi,isDragOver,isDragging)}
                                onDragStart={e=>{onDragStart(ri,wi,ci);e.stopPropagation();}}
                                onDragEnd={onDragEnd}
                                onDragOver={e=>{e.preventDefault();e.stopPropagation();if(!isDragging)setDragOverKey(cardKey);}}
                                onDragLeave={e=>{if(!e.currentTarget.contains(e.relatedTarget)&&dragOverKey===cardKey)setDragOverKey(null);}}
                                onDrop={e=>{e.preventDefault();e.stopPropagation();dropTo(ri,wi,ci);}}
                              >
                                <div style={{fontSize:11,fontWeight:600,color:VH_COLORS[wi]}}>{task}</div>
                                {memoVal && <div style={{fontSize:9,color:T.dim,marginTop:2,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>{memoVal}</div>}
                              </div>
                            );
                          })}
                        </div>
                      );
                    })}
                  </React.Fragment>
                );
              })}
            </div>

            {/* 날짜 추가 버튼 */}
            <div style={{marginTop:8, display:"flex", justifyContent:"center"}}>
              <button onClick={addRow} style={{
                padding:"8px 24px", borderRadius:10, border:`1px dashed ${T.border}`,
                background:"transparent", color:T.muted, fontSize:12, fontWeight:600, cursor:"pointer",
              }}>＋ 날짜 추가</button>
            </div>
          </div>

          {/* ── 날짜/공지 모달 ── */}
          {dateModal && (
            <div onClick={()=>setDateModal(null)} style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.4)",zIndex:300,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}>
              <div onClick={e=>e.stopPropagation()} style={{background:T.card,borderRadius:14,border:`1px solid ${T.border}`,padding:20,width:320,maxWidth:"95vw"}}>
                <div style={{fontSize:14,fontWeight:700,marginBottom:14,color:T.text}}>날짜 / 공지 수정</div>
                <div style={{marginBottom:12}}>
                  <div style={{fontSize:11,color:T.muted,marginBottom:4}}>날짜</div>
                  <input type="date" value={dateModal.date} onChange={e=>setDateModal(m=>({...m,date:e.target.value}))}
                    style={{width:"100%",padding:"8px 10px",borderRadius:8,border:`1px solid ${T.border}`,fontSize:13,background:T.sf,color:T.text}} />
                </div>
                <div style={{marginBottom:12}}>
                  <div style={{fontSize:11,color:T.muted,marginBottom:4}}>공지사항</div>
                  <div style={{display:"flex",gap:6,alignItems:"center"}}>
                    <button onClick={()=>setStarOn(p=>!p)} style={{width:36,height:36,borderRadius:8,fontSize:16,border:`1px solid ${T.border}`,cursor:"pointer",background:starOn?T.warning+"22":T.sf,color:starOn?T.warning:T.dim}}>★</button>
                    <input type="text" value={dateModal.notice} onChange={e=>setDateModal(m=>({...m,notice:e.target.value}))}
                      placeholder="공지 입력"
                      style={{flex:1,padding:"8px 10px",borderRadius:8,border:`1px solid ${T.border}`,fontSize:13,background:T.sf,color:T.text}} />
                  </div>
                </div>
                <div style={{display:"flex",gap:8,justifyContent:"space-between",alignItems:"center"}}>
                  <div style={{display:"flex",gap:6}}>
                    <button onClick={()=>{
                      const targetRow = rows.find(r=>r.date===dateModal.date) || visibleRows[dateModal.ri];
                      if(targetRow){ setApplyModal(targetRow); setDateModal(null); }
                    }} style={{padding:"7px 10px",borderRadius:8,fontSize:12,border:`1px solid ${T.primary}44`,background:T.primary+"15",color:T.primary,cursor:"pointer",fontWeight:700}}>📋 배송적용</button>
                    <button onClick={()=>{
                      const targetDate = dateModal.date;
                      const idx = rows.findIndex(r=>r.date===targetDate);
                      if(idx<0) return;
                      const delCount = rows.length - idx;
                      const {label,day} = fmtDate(targetDate);
                      if(!confirm(`${label}(${day}) 포함 이후 ${delCount}개 행을 모두 삭제할까요?`)) return;
                      setHistory(h=>[...h,JSON.parse(JSON.stringify(allData))]);
                      const newRows = JSON.parse(JSON.stringify(rows));
                      newRows.splice(idx);
                      saveAll({...allData,[selMonth]:newRows});
                      setDateModal(null);
                    }} style={{padding:"7px 10px",borderRadius:8,fontSize:12,border:`1px solid ${T.danger}44`,background:T.danger+"15",color:T.danger,cursor:"pointer",fontWeight:700}}>🗑 이후삭제</button>
                  </div>
                  <div style={{display:"flex",gap:8}}>
                    <button onClick={()=>setDateModal(null)} style={{padding:"7px 14px",borderRadius:8,fontSize:13,border:`1px solid ${T.border}`,background:"transparent",color:T.muted,cursor:"pointer"}}>취소</button>
                    <button onClick={saveDateModal} style={{padding:"7px 18px",borderRadius:8,fontSize:13,border:"none",background:T.primary,color:"#fff",cursor:"pointer",fontWeight:700}}>저장</button>
                  </div>
                </div>
              </div>
            </div>
          )}

          {/* ── 첫 배포일 모달 ── */}
          {firstDateModal && (
            <div onClick={()=>setFirstDateModal(false)} style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.4)",zIndex:300,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}>
              <div onClick={e=>e.stopPropagation()} style={{background:T.card,borderRadius:14,border:`1px solid ${T.border}`,padding:20,width:300,maxWidth:"95vw"}}>
                <div style={{fontSize:14,fontWeight:700,marginBottom:6,color:T.text}}>📅 첫 배포일 지정</div>
                <div style={{fontSize:11,color:T.muted,marginBottom:14}}>주말 제외 18일치 자동 생성됩니다.</div>
                <input type="date" value={firstDateInput} onChange={e=>setFirstDateInput(e.target.value)}
                  style={{width:"100%",padding:"10px",borderRadius:8,border:`1px solid ${T.border}`,fontSize:14,background:T.sf,color:T.text,marginBottom:14}} />
                <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
                  <button onClick={()=>setFirstDateModal(false)} style={{padding:"7px 14px",borderRadius:8,fontSize:13,border:`1px solid ${T.border}`,background:"transparent",color:T.muted,cursor:"pointer"}}>취소</button>
                  <button onClick={()=>applyFirstDate(firstDateInput)} style={{padding:"7px 18px",borderRadius:8,fontSize:13,border:"none",background:T.primary,color:"#fff",cursor:"pointer",fontWeight:700}}>생성</button>
                </div>
              </div>
            </div>
          )}

          {/* ── 배송 적용 모달 ── */}
          {applyModal && (
            <div onClick={()=>setApplyModal(null)} style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.45)",zIndex:300,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}>
              <div onClick={e=>e.stopPropagation()} style={{background:T.card,borderRadius:14,border:`1px solid ${T.border}`,padding:20,width:320,maxWidth:"95vw"}}>
                <div style={{fontSize:14,fontWeight:700,marginBottom:4,color:T.text}}>📋 배송 적용</div>
                <div style={{fontSize:12,color:T.muted,marginBottom:12}}>{fmtDate(applyModal.date).full}</div>
                <div style={{display:"grid",gridTemplateColumns:"repeat(4,1fr)",gap:6,marginBottom:14}}>
                  {vehicles.map((v,wi)=>(
                    <div key={wi} style={{borderRadius:8,padding:"7px 6px",background:VH_BG[wi],border:`1px solid ${VH_COLORS[wi]}44`}}>
                      <div style={{fontSize:10,fontWeight:800,color:VH_COLORS[wi],marginBottom:4}}>{v.num}</div>
                      {(applyModal.tasks[wi]||[]).length>0
                        ? (applyModal.tasks[wi]||[]).map((t,ci)=>{
                            const matched=ROUTE_CODES&&ROUTE_CODES.includes(t.trim());
                            return <div key={ci} style={{fontSize:9,fontWeight:600,marginBottom:2,color:matched?(wi===0?"#1D4ED8":wi===1?"#92400E":wi===2?"#065F46":"#9D174D"):T.dim,opacity:matched?1:0.5}}>{matched?"✓":"✗"} {t}</div>;
                          })
                        : <div style={{fontSize:9,color:T.dim}}>—</div>
                      }
                    </div>
                  ))}
                </div>
                <div style={{fontSize:11,color:T.warning,marginBottom:14,padding:"6px 10px",background:T.warning+"11",borderRadius:8}}>
                  ✓ 매칭 코드 배정 · ✗ 미매칭 건너뜁니다
                </div>
                <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
                  <button onClick={()=>setApplyModal(null)} style={{padding:"8px 16px",borderRadius:8,fontSize:13,border:`1px solid ${T.border}`,background:"transparent",color:T.muted,cursor:"pointer"}}>취소</button>
                  <button onClick={()=>applySchedule(applyModal)} disabled={applying} style={{padding:"8px 20px",borderRadius:8,fontSize:13,border:"none",background:T.primary,color:"#fff",cursor:"pointer",fontWeight:700}}>
                    {applying?"적용 중…":"적용"}
                  </button>
                </div>
              </div>
            </div>
          )}

          {/* ── 셀 드롭다운 ── */}
          {cellDropdown && (()=>{
            const {ri, wi} = cellDropdown;
            const targetDate = visibleRows[ri]?.date;
            const actualRi = rows.findIndex(r=>r.date===targetDate);
            const curTasks = actualRi>=0 ? (rows[actualRi].tasks[wi]||[]) : [];
            const color = VH_COLORS[wi];
            const filtered = ROUTE_CODES.filter(c=>c.includes(cellSearch) && !curTasks.includes(c));
            return (
              <div onClick={()=>setCellDropdown(null)} style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.45)",zIndex:300,display:"flex",alignItems:"flex-end",justifyContent:"center"}}>
                <div onClick={e=>e.stopPropagation()} style={{background:T.card,borderRadius:"14px 14px 0 0",width:"100%",maxWidth:480,maxHeight:"70vh",display:"flex",flexDirection:"column",overflow:"hidden"}}>

                  {/* 현재 목록 + 메모 */}
                  <div style={{padding:"10px 14px",borderBottom:`0.5px solid ${T.border}`,flexShrink:0}}>
                    <div style={{fontSize:10,color:T.dim,marginBottom:6}}>현재 목록</div>
                    {curTasks.length===0
                      ? <div style={{fontSize:12,color:T.dim}}>없음</div>
                      : <div style={{display:"flex",flexDirection:"column",gap:5}}>
                          {curTasks.map((c,ci)=>(
                            <div key={ci} style={{display:"flex",alignItems:"center",gap:6,padding:"5px 8px",background:T.sf,borderRadius:6,border:`0.5px solid ${T.border}`}}>
                              <span style={{fontSize:12,fontWeight:600,color:color,flexShrink:0}}>{c}</span>
                              <input
                                value={actualRi>=0?(rows[actualRi].memos?.[wi]?.[ci]||""):""}
                                placeholder="메모..."
                                onChange={e=>{
                                  const val=e.target.value;
                                  const newRows=JSON.parse(JSON.stringify(rows));
                                  if(!newRows[actualRi].memos)newRows[actualRi].memos=[[],[],[],[]];
                                  if(!newRows[actualRi].memos[wi])newRows[actualRi].memos[wi]=[];
                                  newRows[actualRi].memos[wi][ci]=val;
                                  saveAll({...allData,[selMonth]:newRows});
                                }}
                                style={{flex:1,border:"none",borderBottom:`1px solid ${color}44`,background:"transparent",fontSize:11,color:T.text,outline:"none",padding:"1px 3px",minWidth:0}}
                              />
                              <button onClick={()=>toggleCellCode(ri,wi,c)} style={{border:"none",background:"transparent",cursor:"pointer",fontSize:13,color:T.dim,padding:"0 2px",flexShrink:0}}>✕</button>
                            </div>
                          ))}
                        </div>
                    }
                  </div>

                  {/* 검색 */}
                  <div style={{padding:"8px 12px",borderBottom:`0.5px solid ${T.border}`,flexShrink:0}}>
                    <input autoFocus type="text" placeholder="검색 후 탭하여 추가..."
                      value={cellSearch} onChange={e=>setCellSearch(e.target.value)}
                      style={{width:"100%",padding:"7px 10px",border:`0.5px solid ${T.border}`,borderRadius:8,background:T.sf,color:T.text,fontSize:12,outline:"none",boxSizing:"border-box"}}
                    />
                  </div>

                  {/* 추가 목록 - 다른 호차 선택 여부 표시 */}
                  <div style={{overflowY:"auto",flex:1}}>
                    {(() => {
                      const allCodes = cellSearch ? ROUTE_CODES.filter(c=>c.includes(cellSearch)) : ROUTE_CODES;
                      // 전체 날짜에서 각 코드가 어느 호차에 배정됐는지 찾기
                      const otherSelected = {};
                      rows.forEach(row => {
                        [0,1,2,3].forEach(owi => {
                          if (owi === wi) return;
                          (row.tasks[owi]||[]).forEach(c => {
                            if (!otherSelected[c]) {
                              otherSelected[c] = { color: VH_COLORS[owi], name: vehicles[owi]?.name || "" };
                            }
                          });
                        });
                        // 현재 호차에서도 이 날짜 외 다른 날짜에 있는지
                        (row.tasks[wi]||[]).forEach(c => {
                          if (!otherSelected[c]) {
                            otherSelected[c] = null; // 현재 호차에 있음 표시용
                          }
                        });
                      });
                      if (allCodes.length === 0) return <div style={{padding:"12px 16px",fontSize:12,color:T.dim}}>없음</div>;
                      return allCodes.map(code => {
                        const isCur = curTasks.includes(code);
                        const other = !isCur && otherSelected[code];
                        const bg = isCur ? color+"15" : other ? other.color+"15" : T.card;
                        const label = isCur ? (vehicles[wi]?.name||"") : other ? other.name : null;
                        const labelColor = isCur ? color : other ? other.color : null;
                        return (
                          <div key={code} onClick={()=>{ if(!isCur) toggleCellCode(ri,wi,code); }} style={{
                            display:"flex", alignItems:"center", justifyContent:"space-between",
                            padding:"11px 16px", cursor:isCur?"default":"pointer",
                            borderBottom:`0.5px solid ${T.border}`, background:bg,
                          }}>
                            <span style={{fontSize:13, color:isCur?color:other?other.color:T.text, fontWeight:isCur||other?700:400}}>{code}</span>
                            {label
                              ? <span style={{background:labelColor, color:"#fff", fontSize:10, fontWeight:800, padding:"2px 8px", borderRadius:10}}>{label}</span>
                              : <span style={{fontSize:13, color, fontWeight:700}}>＋</span>
                            }
                          </div>
                        );
                      });
                    })()}
                  </div>

                </div>
              </div>
            );
          })()}

          {/* ── 토스트 ── */}
          {toast && (
            <div style={{position:"fixed",bottom:20,left:"50%",transform:"translateX(-50%)",background:T.text,color:"#fff",padding:"7px 18px",borderRadius:999,fontSize:12,fontWeight:600,zIndex:400,pointerEvents:"none"}}>{toast}</div>
          )}

        </React.Fragment>
      )}
    </div>
  );
}


// ── Entry Point ──
const rootEl = document.getElementById("root");
const root = ReactDOM.createRoot(rootEl);
root.render(<DeliveryApp />);
