"use client";

import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import {
  Alert,
  Box,
  Badge,
  Button,
  ButtonGroup,
  Chip,
  Card,
  CardContent,
  Checkbox,
  CircularProgress,
  Collapse,
  Divider,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  Drawer,
  IconButton,
  FormControl,
  InputAdornment,
  InputLabel,
  List,
  ListItemButton,
  ListItemText,
  ListSubheader,
  MenuItem,
  Paper,
  Popover,
  Select,
  Snackbar,
  Stack,
  TextField,
  Tooltip,
  Typography,
  Autocomplete,
} from "@mui/material";
import { alpha } from "@mui/material/styles";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import FilterListIcon from "@mui/icons-material/FilterList";
import {
  AlertTriangle,
  ArrowRight,
  Bell,
  Building2,
  Clock,
  CreditCard,
  ChevronRight,
  Download,
  FileText,
  History,
  Info,
  LayoutDashboard,
  Phone,
  Plus,
  RotateCcw,
  Save,
  Search,
  Trash2,
  Truck as TruckIcon,
  Upload,
  Users,
  X,
  MapPin,
  Pencil,
  GripVertical,
  CarFront,
} from "lucide-react";
import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import {
  fetchAllShopsClient,
  diffTruckLaneVersionsClient,
  listTruckLaneVersionsClient,
  restoreTruckLaneVersionClient,
  findAllByTruckLanceCodeAndRemarkAndDeletedFalseClient,
  findAllForRouteBoardClient,
  exportRouteLanesExcelClient,
  exportRouteReportExcelClient,
  exportTruckLaneVersionReportExcelClient,
  importRouteLanesExcelClient,
  parseRouteLanesExcelClient,
  createTruckLaneSnapshotClient,
  createTruckLaneScheduleClient,
  updateTruckLaneVersionNoteClient,
  updateTruckLaneClient,
  updateLaneLogisticClient,
  createTruckClient,
  createTruckWithoutShopClient,
  deleteTruckLaneClient,
} from "@/app/api/shop/client";
import {
  deleteLogisticClient,
  findAllLogisticsClient,
  saveLogisticClient,
  saveLogisticsBatchCreateClient,
  type LogisticRow,
  type SaveLogisticRequest,
} from "@/app/api/logistic/client";
import type {
  CreateTruckWithoutShopRequest,
  LogisticMasterDiffLine,
  MessageResponse,
  SaveTruckLane,
  Truck,
  TruckLaneVersionDiffLine,
} from "@/app/api/shop/actions";
import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil";
import {
  buildStagedBoardLogEntries,
  diffLinesToShopRows,
  formatLaneLabel,
  resolveHeadVersionId,
  resolveVersionLogLaneLabel,
  resolveVersionLogShopHeadline,
  splitVersionCreated,
  resolveVersionActor,
  summarizeVersionRows,
  VERSION_LOG_LOADING_SEQUENCE_LABEL,
  type StagedDeleteMeta,
  type ShopRowBaseline,
} from "@/components/Shop/routeBoardVersionLog";
import {
  computeTruckLaneWarnings,
  appendSyntheticPendingShopRow,
  type TruckLaneWarning,
  type TruckLaneWarningInputRow,
  type TruckLaneWarningLaneRef,
} from "@/components/Shop/computeTruckLaneWarnings";
import { mergeImportPreviewIntoLanes } from "@/components/Shop/routeBoardImportPreview";
import ScheduleChangeModal, {
  type ScheduleChangePayload,
  type ScheduleShopRow,
} from "@/components/Shop/ScheduleChangeModal";
import ScheduleTaskHistoryModal from "@/components/Shop/ScheduleTaskHistoryModal";
import { extractApiErrorMessage } from "@/components/Shop/scheduleClientHelpers";
import { formatScheduleValidationErrors } from "@/components/Shop/scheduleUiHelpers";
import { useRouteBoardScheduleIndicators } from "@/components/Shop/useRouteBoardScheduleIndicators";
import {
  buildRequestFromPayload,
  validatePayloadSubmit,
} from "@/components/Shop/truckLaneMovePlanner";
import {
  computeMovedLoadingSequence,
  flattenDisplayOrder,
  groupByDistrict,
  formatShopCardSubtitle,
  toDistrictDisplayName,
  toDistrictRawValue,
} from "@/components/Shop/routeBoardDisplayOrder";

const JAVA_INT_MAX = 2_147_483_647;

/** 後端 `driverNumber` 為 Int；只送可安全表示的十進位數字，不靜默改值。 */
function phoneDigitsToDriverNumber(phone: string): number | null {
  const d = String(phone).replace(/\D/g, "");
  if (!d) return null;
  const fitsIntString = (s: string): boolean =>
    /^\d{1,10}$/.test(s) && BigInt(s) <= BigInt(JAVA_INT_MAX);
  const parseBlock = (s: string): number | null => {
    if (!fitsIntString(s)) return null;
    const n = parseInt(s, 10);
    return Number.isFinite(n) && n >= 0 && n <= JAVA_INT_MAX ? n : null;
  };
  let n = parseBlock(d);
  if (n != null) return n;
  // HK +852 + 8 位本地號（11 位數字）
  if (d.startsWith("852") && d.length === 11) {
    n = parseBlock(d.slice(3));
    if (n != null) return n;
  }
  return null;
}

type ShopCard = {
  /** truck table row id */
  id: number;
  /** master shop.id（後端 Truck.shop）；新增/去重用 */
  shopEntityId?: number | null;
  /** 分店名（truck table 的 ShopName） */
  branchName: string;
  shopCode: string;
  /** districtReference raw value from backend (never use display label for save) */
  districtReferenceRaw: string | null;
  /** brand/label is not in current API; keep optional for future */
  brand?: string;
  /** periods is not in current API; keep optional for future */
  periods?: string;
  /** ordering inside a lane */
  loadingSequence: number;
  /** remark shown in existing UIs */
  remark?: string | null;
  /** store ID 2F/4F */
  storeId: string;
  /** departure time HH:mm or HH:mm:ss */
  departureTime: string;
};

type Lane = {
  /** 穩定鍵：encodeURIComponent(code)|encodeURIComponent(remark) */
  id: string;
  /** 寫入 truck API 的車線代碼 */
  truckLanceCode: string;
  plate?: string;
  logisticsCompany?: string;
  /** `logistic.id`，來自 truck.logistic；無綁定為 null */
  logisticId?: number | null;
  driver?: string;
  phone?: string;
  startTime: string;
  storeId: string;
  remark?: string | null;
  shops: ShopCard[];
};

type PendingNewLane = {
  laneKey: string;
  payload: CreateTruckWithoutShopRequest;
};

const parseTimeForBackend = (time: string): string => {
  const s = String(time ?? "").trim();
  if (!s) return "";
  // allow HH:mm or HH:mm:ss
  if (/^\d{1,2}:\d{2}(:\d{2})?$/.test(s)) {
    const [hh, mm, ss] = s.split(":");
    return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}${
      ss != null ? `:${String(ss).padStart(2, "0")}` : ""
    }`;
  }
  return s;
};

const toTimeInputValue = (t: string | undefined): string => {
  const s = String(t ?? "").trim();
  if (!s) return "00:00";
  const m = s.match(/^(\d{1,2}):(\d{2})(?::\d{2})?/);
  if (m) return `${m[1].padStart(2, "0")}:${m[2]}`;
  return "00:00";
};

/** 物流商總覽欄標題用：條數、店數、樓層 */
function summarizeLogisticsColumnStats(lanes: Lane[]): {
  laneCount: number;
  shopCount: number;
  count2F: number;
  count4F: number;
} {
  let shopCount = 0;
  let count2F = 0;
  let count4F = 0;
  for (const l of lanes) {
    shopCount += l.shops.length;
    if (normalizeStoreId(l.storeId) === "4F") count4F++;
    else count2F++;
  }
  return {
    laneCount: lanes.length,
    shopCount,
    count2F,
    count4F,
  };
}

function resolveLogisticMasterRow(
  company: string,
  lanes: Lane[],
  masters: LogisticRow[],
): LogisticRow | null {
  if (company === "未分配物流商") return null;
  const byLaneId = lanes.find(
    (l) => l.logisticId != null && Number.isFinite(l.logisticId),
  )?.logisticId;
  if (byLaneId != null) {
    const hit = masters.find((m) => m.id === byLaneId);
    if (hit) return hit;
  }
  const name = String(company).trim();
  return (
    masters.find((m) => String(m.logisticName ?? "").trim() === name) ?? null
  );
}

/**
 * 地區區塊標題編輯語意（RouteBoard）：
 * 後端每筆 truck row 存 `districtReference`，無 lane-level overlay 表。
 * 編輯某「區塊」顯示名稱＝把該 lane 內目前落在該顯示 bucket 的所有 shop 之
 * `districtReferenceRaw` 批量改成 `toDistrictRawValue(新顯示名)`，並對 `id > 0`
 * 的列 `dirtyMoves.set` 以延遲寫 DB。
 * 僅前端存在、尚無店鋪的「空區」用 `pendingEmptyDistrictsByLane` 記錄顯示名，
 * 與 `groupByDistrict` 合併渲染；儲存成功或僅空區時會清掉暫存列。
 */
function buildLaneDistrictSections(
  shops: ShopCard[],
  pendingExtraDistrictDisplays: string[] | undefined,
): Array<{ district: string; shops: ShopCard[]; isPendingEmpty: boolean }> {
  const grouped = groupByDistrict(shops);
  const keysFromShops = new Set(grouped.map((g) => g.district));
  const extras = (pendingExtraDistrictDisplays ?? []).filter(
    (d) => !keysFromShops.has(d),
  );
  const merged = [
    ...grouped.map((g) => ({
      district: g.district,
      shops: g.shops,
      isPendingEmpty: false,
    })),
    ...extras.map((district) => ({
      district,
      shops: [] as ShopCard[],
      isPendingEmpty: true,
    })),
  ];
  merged.sort((a, b) => {
    if (a.district === "未分類") return -1;
    if (b.district === "未分類") return 1;
    return a.district.localeCompare(b.district, "zh-Hant");
  });
  return merged;
}

function districtDisplayExistsInLane(
  lane: Lane,
  pendingExtra: string[] | undefined,
  display: string,
): boolean {
  const set = new Set(groupByDistrict(lane.shops).map((g) => g.district));
  for (const d of pendingExtra ?? []) set.add(d);
  return set.has(display);
}

/** pending 顯示名陣列去重（保序），避免 rename 撞名造成重複 key / 重複區塊 */
function dedupeDistrictPendingOrder(items: string[]): string[] {
  const seen = new Set<string>();
  const out: string[] = [];
  for (const x of items) {
    if (seen.has(x)) continue;
    seen.add(x);
    out.push(x);
  }
  return out;
}

const LANE_ID_SEP = "|";

/** 與後端 lane 唯一鍵一致：`TruckLanceCode` + 正規化後 remark（NULL/空白 視為同組） */
function encodeLaneId(
  truckLanceCode: string,
  laneRemark: string | null | undefined,
): string {
  const code = String(truckLanceCode || "").trim();
  const rem =
    laneRemark != null && String(laneRemark).trim() !== ""
      ? String(laneRemark).trim()
      : "";
  return `${encodeURIComponent(code)}${LANE_ID_SEP}${encodeURIComponent(rem)}`;
}

function decodeLaneId(laneId: string): {
  truckLanceCode: string;
  remark: string | null;
} | null {
  const i = laneId.indexOf(LANE_ID_SEP);
  if (i < 0) return null;
  try {
    const code = decodeURIComponent(laneId.slice(0, i));
    const remEnc = laneId.slice(i + LANE_ID_SEP.length);
    const rem = decodeURIComponent(remEnc);
    return {
      truckLanceCode: code,
      remark: rem !== "" ? rem : null,
    };
  } catch {
    return null;
  }
}

function lanesToWarningInputRows(lanes: Lane[]): TruckLaneWarningInputRow[] {
  const out: TruckLaneWarningInputRow[] = [];
  for (const lane of lanes) {
    const code = lane.truckLanceCode;
    const laneRemark = lane.remark ?? null;
    for (const s of lane.shops) {
      out.push({
        truckRowId: s.id,
        truckLanceCode: code,
        laneRemark,
        storeId: s.storeId,
        departureTime: s.departureTime,
        shopEntityId: s.shopEntityId ?? null,
        shopCode: s.shopCode,
        shopDisplayName: s.branchName,
      });
    }
  }
  return out;
}

/** 尚未儲存的新增店鋪（負數 truck row id） */
function stripDraftShopRows(lanes: Lane[]): Lane[] {
  return lanes.map((l) => ({
    ...l,
    shops: l.shops.filter((s) => s.id > 0),
  }));
}

function warningTouchesPickedShop(
  w: TruckLaneWarning,
  pick: { id: number; code: string },
): boolean {
  if (
    w.shopEntityId != null &&
    Number.isFinite(w.shopEntityId) &&
    w.shopEntityId === pick.id
  ) {
    return true;
  }
  const wc = String(w.shopCode || "").trim().toLowerCase();
  const pc = String(pick.code || "").trim().toLowerCase();
  return wc !== "" && pc !== "" && wc === pc;
}

function formatLaneWarningDetail(
  L: TruckLaneWarningLaneRef,
  tr: TFunction<"shop">,
): string {
  const dash = tr("emDash");
  const parts = [`${tr("warnClipboardStore")} ${L.storeId}`];
  if (L.weekday) {
    parts.push(`${tr("warnClipboardWeekday")} ${L.weekday}`);
  }
  parts.push(`${tr("warnClipboardDep")} ${L.departureTimeDisplay ?? dash}`);
  return parts.join(" · ");
}

function formatWarningSummary(
  w: TruckLaneWarning,
  tr: TFunction<"shop">,
): string {
  if (w.rule === "RULE_1_WEEKDAY") {
    return tr("mtmsRouteWarn_conflict4f", { weekday: w.triggerValue });
  }
  return tr("mtmsRouteWarn_conflictDep", { time: w.triggerValue });
}

function formatLaneWarningsClipboard(
  warnings: TruckLaneWarning[],
  tr: TFunction<"shop">,
): string {
  return warnings
    .map((w, idx) => {
      const shopLine = [w.shopCode, w.shopDisplayName]
        .map((s) => String(s ?? "").trim())
        .filter(Boolean)
        .join(" ");
      const lanes = w.lanes
        .map((L) => {
          const laneTitle = `${L.truckLanceCode}${
            L.laneRemark ? ` · ${L.laneRemark}` : ""
          }`;
          return `  - ${laneTitle} | ${formatLaneWarningDetail(L, tr)}`;
        })
        .join("\n");
      return `[${idx + 1}] ${tr("mtmsRouteWarn_shop")}: ${shopLine}\n${formatWarningSummary(w, tr)}\n${lanes}`;
    })
    .join("\n\n");
}

function formatDiffFieldLabel(label: string, tr: TFunction<"shop">): string {
  if (label === "versionLogField_logisticId") return tr("diffField_logisticsCompany");
  // Translate i18n keys; fallback to raw label if not found
  const translated = tr(label as any);
  return translated !== label ? translated : label;
}

function downloadBase64Xlsx(base64: string, filename: string) {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  const blob = new Blob([bytes], {
    type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

function sortLanesByCode(lanes: Lane[]): Lane[] {
  return lanes.slice().sort((a, b) => {
    const c = a.truckLanceCode.localeCompare(b.truckLanceCode, "zh-Hant");
    if (c !== 0) return c;
    return String(a.remark ?? "").localeCompare(
      String(b.remark ?? ""),
      "zh-Hant",
    );
  });
}

/** 由尚未儲存的新增車線 payload 建立看板 Lane（shops 為空） */
function buildLaneFromPendingNewLane(p: PendingNewLane): Lane {
  const payload = p.payload;
  const code = String(payload.truckLanceCode || "").trim();
  const storeNorm = normalizeStoreId(payload.store_id);
  const remarkRaw =
    storeNorm === "4F" &&
    payload.remark != null &&
    String(payload.remark).trim() !== ""
      ? String(payload.remark).trim()
      : null;
  const dep = parseTimeForBackend(payload.departureTime || "") || "00:00:00";
  return {
    id: p.laneKey,
    truckLanceCode: code,
    logisticsCompany: "",
    logisticId:
      payload.logisticId != null && Number.isFinite(Number(payload.logisticId))
        ? Number(payload.logisticId)
        : null,
    plate: "",
    driver: "",
    phone: "",
    startTime: dep,
    storeId: storeNorm,
    remark: remarkRaw,
    shops: [],
  };
}

/** 合併尚未寫入後端的新增車線，避免 loadLanes 覆蓋後從看板消失 */
function mergePendingNewLanesIntoLanes(
  lanes: Lane[],
  pending: PendingNewLane[],
): Lane[] {
  if (pending.length === 0) return lanes;
  const ids = new Set(lanes.map((l) => l.id));
  const additions = pending
    .filter((p) => !ids.has(p.laneKey))
    .map((p) => buildLaneFromPendingNewLane(p));
  if (additions.length === 0) return lanes;
  return sortLanesByCode([...lanes, ...additions]);
}

/** 合併增量刷新後的車線（維持排序；追加後端新出現的 lane） */
function mergeRefreshedLanes(prev: Lane[], refreshed: Lane[]): Lane[] {
  const map = new Map(refreshed.map((l) => [l.id, l]));
  const prevIds = new Set(prev.map((l) => l.id));
  const replaced: Lane[] = [];
  for (const l of prev) {
    const n = map.get(l.id);
    if (n !== undefined) {
      replaced.push(n);
    } else {
      replaced.push(l);
    }
  }
  const additions = refreshed.filter((l) => !prevIds.has(l.id));
  return sortLanesByCode([...replaced, ...additions]);
}

function buildLaneFromTruckRows(
  truckLanceCode: string,
  remark: string | null,
  rows: Truck[] | null | undefined,
  meta?: Partial<Truck> | Record<string, unknown> | null,
): Lane {
  const code = String(truckLanceCode || "").trim();
  const r = rows || [];
  const first = r[0];

  const startSource =
    meta != null
      ? (meta as any).departureTime ?? first?.departureTime ?? "00:00:00"
      : first != null
        ? first.departureTime
        : "00:00:00";

  const startTime =
    parseTimeForBackend(formatDepartureTime(startSource as any)) || "00:00:00";

  const storeId = normalizeStoreId(
    meta != null
      ? (meta as any)?.storeId ?? (meta as any)?.store_id
      : (first as any)?.storeId ?? (first as any)?.store_id,
  );

  const id = encodeLaneId(code, remark);

  const shops: ShopCard[] = r
    .filter((row) => row.id != null)
    .filter((row) => {
      const shopRef = (row as any).shop;
      const hasShopRef =
        shopRef != null && typeof shopRef === "object" && shopRef.id != null;
      const nm = row.shopName != null ? String(row.shopName).trim() : "";
      const cd = row.shopCode != null ? String(row.shopCode).trim() : "";
      if (hasShopRef) return true;
      if (nm === "" && cd === "") return false;
      const u = (s: string) => s.trim().toLowerCase();
      if (u(nm) === "unassign" || u(cd) === "unassign") return false;
      if (u(nm) === "unassigned" || u(cd) === "unassigned") return false;
      return true;
    })
    .map((row) => {
      const districtRaw =
        row.districtReference != null &&
        String(row.districtReference).trim() !== ""
          ? String(row.districtReference).trim()
          : null;
      const shopRef = (row as any).shop;
      const shopEntityId =
        shopRef && typeof shopRef === "object" && shopRef.id != null
          ? Number(shopRef.id)
          : null;
      return {
        id: row.id as number,
        shopEntityId: Number.isFinite(shopEntityId as number)
          ? (shopEntityId as number)
          : null,
        branchName: row.shopName != null ? String(row.shopName) : "",
        shopCode: row.shopCode != null ? String(row.shopCode) : "",
        districtReferenceRaw: districtRaw,
        loadingSequence: Number(row.loadingSequence ?? 0) || 0,
        remark: row.remark != null ? String(row.remark) : null,
        storeId: normalizeStoreId(
          (row as any).storeId ?? (row as any).store_id,
        ),
        departureTime: parseTimeForBackend(
          formatDepartureTime(row.departureTime as any),
        ),
      };
    });

  let laneLogisticId: number | null = null;
  let laneLogisticsName = "";
  let lanePlate = "";
  let laneDriver = "";
  let lanePhone = "";
  for (const row of r) {
    const log = (row as { logistic?: Record<string, unknown> | null }).logistic;
    if (!log || typeof log !== "object") continue;
    if (
      laneLogisticId == null &&
      log.id != null &&
      Number.isFinite(Number(log.id))
    ) {
      laneLogisticId = Number(log.id);
    }
    const nm = String(log.logisticName ?? "").trim();
    if (nm !== "" && laneLogisticsName === "") laneLogisticsName = nm;
    const p = String(log.carPlate ?? "").trim();
    const d = String(log.driverName ?? "").trim();
    const ph =
      log.driverNumber != null && Number.isFinite(Number(log.driverNumber))
        ? String(log.driverNumber)
        : "";
    if (p !== "" && lanePlate === "") lanePlate = p;
    if (d !== "" && laneDriver === "") laneDriver = d;
    if (ph !== "" && lanePhone === "") lanePhone = ph;
  }
  if (laneLogisticId == null && r.length > 0) {
    for (const row of r) {
      const lid = (row as { logisticId?: unknown }).logisticId;
      if (lid != null && Number.isFinite(Number(lid))) {
        laneLogisticId = Number(lid);
        break;
      }
    }
  }

  return {
    id,
    truckLanceCode: code,
    logisticsCompany: laneLogisticsName,
    logisticId: laneLogisticId,
    driver: laneDriver,
    phone: lanePhone,
    plate: lanePlate,
    startTime: startTime || "00:00:00",
    storeId: storeId || "2F",
    remark,
    shops,
  };
}

async function fetchLaneByKey(
  truckLanceCode: string,
  remark: string | null,
  meta?: Partial<Truck> | null,
): Promise<Lane | null> {
  const c = String(truckLanceCode || "").trim();
  if (!c) return null;
  // NOTE:
  // - Next dev StrictMode 會讓初始化 useEffect 跑兩次，若不去重會把同一批 lane 打兩輪
  // - 這裡做「同 key in-flight」去重，避免重複打 API
  const inflight = (fetchLaneByKey as any)._inflight as
    | Map<string, Promise<Lane | null>>
    | undefined;
  const map: Map<string, Promise<Lane | null>> = inflight ?? new Map();
  (fetchLaneByKey as any)._inflight = map;

  const key = encodeLaneId(c, remark);
  const existing = map.get(key);
  if (existing) return existing;

  const p = (async () => {
    const rows = (await findAllByTruckLanceCodeAndRemarkAndDeletedFalseClient(
      c,
      remark,
    )) as Truck[];
    return buildLaneFromTruckRows(c, remark, rows, meta);
  })();
  map.set(key, p);
  try {
    return await p;
  } finally {
    map.delete(key);
  }
}

function laneTargetConflicts(
  shop: ShopCard,
  lane: Lane,
  excludeTruckRowId?: number,
): boolean {
  for (const s of lane.shops) {
    if (excludeTruckRowId != null && s.id === excludeTruckRowId) continue;
    if (
      shop.shopEntityId != null &&
      s.shopEntityId != null &&
      shop.shopEntityId === s.shopEntityId
    ) {
      return true;
    }
    const a = String(shop.shopCode || "")
      .trim()
      .toLowerCase();
    const b = String(s.shopCode || "")
      .trim()
      .toLowerCase();
    if (a !== "" && b !== "" && a === b) return true;
  }
  return false;
}

/** `/shop/combo/allShop` 常有 join 重複列：依 shop.id、再依 code 去重（保留 id 較小） */
function dedupeShopMasterRows(
  rows: Array<{ id?: unknown; code?: unknown; name?: unknown }>,
): Array<{ id: number; name: string; code: string }> {
  const byId = new Map<number, { id: number; name: string; code: string }>();
  for (const s of rows || []) {
    const id = Number(s?.id);
    if (!Number.isFinite(id) || id <= 0 || byId.has(id)) continue;
    const rawCode = String(s?.code ?? "").trim();
    const name = String(s?.name ?? "").trim();
    byId.set(id, {
      id,
      name: name || rawCode || String(id),
      code: rawCode || String(id),
    });
  }
  const byCode = new Map<string, { id: number; name: string; code: string }>();
  for (const row of Array.from(byId.values())) {
    const ck = String(row.code).trim().toLowerCase();
    const key = ck || `__id_${row.id}`;
    const prev = byCode.get(key);
    if (!prev || row.id < prev.id) byCode.set(key, row);
  }
  return Array.from(byCode.values()).sort((a, b) =>
    a.code.localeCompare(b.code, undefined, { numeric: true }),
  );
}

/**
 * 車線店鋪管理看板（對齊 MTMS_ISSUE_LOG.pdf / 圖1 / 圖2）
 * - 左：checkbox 多選車線 + search
 * - 右：所選車線按「地區(districtReference)」分組顯示店鋪，可拖拽跨車線
 * - 支援儲存：把被拖動的店鋪批量呼叫 `updateTruckLaneClient`
 *
 * Logistic：後端 `truck.logistic` join；車線 Excel 見 MTMS_ROUTE_V1（PDF 圖1）。
 */
const RouteBoard: React.FC = () => {
  const { t } = useTranslation("shop");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [laneWarnDrawerOpen, setLaneWarnDrawerOpen] = useState(false);
  const [laneWarnExpandedIdx, setLaneWarnExpandedIdx] = useState<number | null>(
    null,
  );
  const [laneWarnSnackbar, setLaneWarnSnackbar] = useState<string | null>(null);
  const [searchTerm, setSearchTerm] = useState("");
  const didInitialLoadRef = useRef(false);
  const loadLanesInFlightRef = useRef(false);
  const importRouteFileInputRef = useRef<HTMLInputElement>(null);
  const pendingImportFileRef = useRef<File | null>(null);
  const [pendingImportMeta, setPendingImportMeta] = useState<{
    fileName: string;
    sheetCount: number;
    rowCount: number;
  } | null>(null);
  const [routeExcelBusy, setRouteExcelBusy] = useState(false);
  const routeExcelExportLockRef = useRef(false);

  // shopCode(lowercase) -> shop table real name
  const [shopNameByCodeMap, setShopNameByCodeMap] = useState<
    Map<string, string>
  >(new Map());
  const [lanes, setLanes] = useState<Lane[]>([]);
  const laneWarningsMemo = useMemo(
    () => computeTruckLaneWarnings(lanesToWarningInputRows(lanes)),
    [lanes],
  );
  const laneWarnCount = laneWarningsMemo.warnings.length;
  const selectLanesFromWarning = useCallback((w: TruckLaneWarning) => {
    const ids = Array.from(
      new Set(
        w.lanes
          .map((L) => String(L.laneKey ?? "").trim())
          .filter((k) => k !== ""),
      ),
    );
    if (ids.length === 0) return;
    setSelectedLaneIds(ids);
    setLaneWarnDrawerOpen(false);
  }, []);

  useEffect(() => {
    if (!laneWarnDrawerOpen) setLaneWarnExpandedIdx(null);
  }, [laneWarnDrawerOpen]);
  // Keep latest lanes snapshot for drag/drop computations.
  // This avoids relying on React state updater execution timing and prevents
  // side-effects inside state updaters (which can break under StrictMode/Concurrent).
  const lanesRef = useRef<Lane[]>([]);
  useEffect(() => {
    lanesRef.current = lanes;
  }, [lanes]);
  const versionDiffReqSeq = useRef(0);
  const [selectedLaneIds, setSelectedLaneIds] = useState<string[]>([]);
  const [routeBoardTab, setRouteBoardTab] = useState<"board" | "logistics">(
    "board",
  );
  const [laneFilter, setLaneFilter] = useState<{
    floor: "all" | "2F" | "4F";
    query: string;
  }>({
    floor: "all",
    query: "",
  });
  const [laneFilterAnchor, setLaneFilterAnchor] = useState<HTMLElement | null>(
    null,
  );

  // drag state (HTML5 drag & drop)
  const draggedRef = useRef<{ shopId: number; fromLaneId: string } | null>(
    null,
  );
  /** 物流商管理頁：拖曳整條車線指派 logistic */
  const logisticsLaneDragIdRef = useRef<string | null>(null);
  /** baseline: 後端目前 lane logisticId（用於判斷「只改物流商」也要能 Save） */
  const laneLogisticBaselineRef = useRef<Map<string, number | null>>(new Map());
  /** 店鋪列地區 baseline（載入／refresh 後同步），供未儲存清單標註地區差 */
  const shopDistrictBaselineRef = useRef<Map<number, string>>(new Map());
  const shopRowBaselineRef = useRef<Map<number, ShopRowBaseline>>(new Map());
  const [districtBaselineEpoch, setDistrictBaselineEpoch] = useState(0);
  const syncShopDistrictBaselineFromLanes = useCallback((laneList: Lane[]) => {
    const districtMap = new Map<number, string>();
    const rowMap = new Map<number, ShopRowBaseline>();
    for (const lane of laneList) {
      const fromLaneLabel = formatLaneLabel(lane.truckLanceCode, lane.remark);
      for (const s of lane.shops) {
        if (s.id <= 0) continue;
        districtMap.set(s.id, toDistrictDisplayName(s.districtReferenceRaw));
        rowMap.set(s.id, {
          laneId: lane.id,
          fromLaneLabel,
          departureTime: parseTimeForBackend(
            formatDepartureTime(s.departureTime as any),
          ),
          loadingSequence: Number(s.loadingSequence ?? 0) || 0,
          districtDisplay: toDistrictDisplayName(s.districtReferenceRaw),
        });
      }
    }
    shopDistrictBaselineRef.current = districtMap;
    shopRowBaselineRef.current = rowMap;
    setDistrictBaselineEpoch((e) => e + 1);
  }, []);
  const [dropIndicator, setDropIndicator] = useState<{
    laneId: string;
    beforeShopId: number | null;
  } | null>(null);
  /** 跨線拖曳等：來源 lane 可能沒有任何 dirty 列，儲存/還原時仍須一併 refetch */
  const lanesNeedingRefreshOnSaveRef = useRef<Set<string>>(new Set());

  // dirty tracking (shop row id -> new laneId)
  const [dirtyMoves, setDirtyMoves] = useState<Map<number, string>>(new Map());
  // staged deletes (truck row ids)
  const [dirtyDeletes, setDirtyDeletes] = useState<Set<number>>(new Set());
  const dirtyDeletesRef = useRef<Set<number>>(new Set());
  /** 暫刪列在 UI 已移除時仍要在版本 LOG「未儲存」小節顯示店鋪／來源車線 */
  const stagedDeleteMetaRef = useRef<Map<number, StagedDeleteMeta>>(new Map());
  useEffect(() => {
    dirtyDeletesRef.current = dirtyDeletes;
  }, [dirtyDeletes]);

  /** 立刻同步 ref，避免同一 tick 內 await 後 setLanes 仍讀到舊暫刪 */
  const clearDirtyDeletesState = useCallback(() => {
    dirtyDeletesRef.current = new Set();
    stagedDeleteMetaRef.current.clear();
    setDirtyDeletes(new Set());
  }, []);

  /** refresh/load 會用後端資料覆蓋 UI，須再過濾未 Save 的暫刪列 */
  const filterStagedDeletedShops = useCallback(
    (laneList: Lane[], del: Set<number>): Lane[] => {
      if (del.size === 0) return laneList;
      return laneList.map((lane) => ({
        ...lane,
        shops: lane.shops.filter((s) => !del.has(s.id)),
      }));
    },
    [],
  );

  const [departureEditLaneId, setDepartureEditLaneId] = useState<string | null>(
    null,
  );
  const [departureEditDraft, setDepartureEditDraft] = useState("");
  const [seqEditTarget, setSeqEditTarget] = useState<{
    laneId: string;
    shopId: number;
  } | null>(null);
  const [seqEditDraft, setSeqEditDraft] = useState("");
  const [saving, setSaving] = useState(false);
  const saveInFlightRef = useRef(false);
  const [saveResult, setSaveResult] = useState<{
    ok: boolean;
    message: string;
  } | null>(null);

  // version log (snapshot) UI
  const [logDialogOpen, setLogDialogOpen] = useState(false);
  const [loadingVersions, setLoadingVersions] = useState(false);
  const [logVersions, setLogVersions] = useState<any[]>([]);
  const [selectedLogVersionId, setSelectedLogVersionId] = useState<
    number | null
  >(null);
  const [diffLoading, setDiffLoading] = useState(false);
  const [diffError, setDiffError] = useState<string | null>(null);
  const [versionFilterAnchor, setVersionFilterAnchor] =
    useState<HTMLElement | null>(null);
  const [versionFilterQuery, setVersionFilterQuery] = useState("");
  const [versionFilterDate, setVersionFilterDate] = useState("");
  const [changedShopIds, setChangedShopIds] = useState<Set<number>>(new Set());
  const [logisticMasterDiffLines, setLogisticMasterDiffLines] = useState<
    LogisticMasterDiffLine[]
  >([]);
  const [diffLines, setDiffLines] = useState<
    Array<{
      truckRowId: number;
      shopCode: string | null;
      changes: Array<{ field: string; from: string | null; to: string | null }>;
    }>
  >([]);
  /** 版本 LOG：已排程、待「儲存更改」時才呼叫 restore API */
  const [pendingRestoreVersionId, setPendingRestoreVersionId] = useState<
    number | null
  >(null);
  const [versionNoteDrafts, setVersionNoteDrafts] = useState<
    Record<number, string>
  >({});
  const [savingVersionNoteId, setSavingVersionNoteId] = useState<number | null>(
    null,
  );
  const [versionNoteSaveError, setVersionNoteSaveError] = useState<{
    id: number;
    message: string;
  } | null>(null);

  const headVersionId = useMemo(
    () => resolveHeadVersionId(logVersions),
    [logVersions],
  );

  const displayedVersionLabel = useMemo(() => {
    if (
      pendingRestoreVersionId != null &&
      Number.isFinite(pendingRestoreVersionId) &&
      pendingRestoreVersionId > 0
    ) {
      return t("version_ui_pendingRestore", { id: pendingRestoreVersionId });
    }
    if (headVersionId != null) {
      return t("version_ui_id", { id: headVersionId });
    }
    return t("version_ui_none");
  }, [pendingRestoreVersionId, headVersionId, t]);

  const versionFilterActive =
    String(versionFilterQuery || "").trim() !== "" ||
    String(versionFilterDate || "").trim() !== "";

  const filteredLogVersions = useMemo(() => {
    const q = String(versionFilterQuery || "")
      .trim()
      .toLowerCase();
    const exactDate = String(versionFilterDate || "").trim();
    const hasDate = exactDate !== "";
    const hasQ = q !== "";
    if (!hasDate && !hasQ) return logVersions;

    return (logVersions || []).filter((v) => {
      const id = Number(v?.id);
      const created = String(v?.created || "");
      const { date } = splitVersionCreated(created);
      if (hasDate) {
        if (date !== exactDate) return false;
      }
      if (!hasQ) return true;
      const note = v?.note != null ? String(v.note) : "";
      const editor = resolveVersionActor(v ?? {});
      const hay = `${id} ${note} ${editor} ${created}`.toLowerCase();
      return hay.includes(q);
    });
  }, [logVersions, versionFilterDate, versionFilterQuery]);

  const versionShopRows = useMemo(
    () => diffLinesToShopRows(diffLines as TruckLaneVersionDiffLine[]),
    [diffLines],
  );

  const versionRowSummary = useMemo(
    () => summarizeVersionRows(versionShopRows),
    [versionShopRows],
  );

  /** 新增店鋪：從 shop master 挑選 */
  const [allShopsMaster, setAllShopsMaster] = useState<
    Array<{ id: number; name: string; code: string }>
  >([]);
  const [scheduleModalOpen, setScheduleModalOpen] = useState(false);
  const [scheduleHistoryOpen, setScheduleHistoryOpen] = useState(false);
  const scheduleModalOpenRef = useRef(scheduleModalOpen);
  scheduleModalOpenRef.current = scheduleModalOpen;
  const {
    pendingScheduleShopIds,
    lockedScheduleShopIds,
    failedScheduleShopIds,
    failedScheduleCount,
    refreshScheduleIndicators,
  } = useRouteBoardScheduleIndicators({ paused: scheduleModalOpen });
  /** 硬鎖：APPLYING 或進入鎖定時間窗的排程；遠期排程僅標記不鎖。 */
  const scheduledShopIdSet = lockedScheduleShopIds;

  const [addShopDialogOpen, setAddShopDialogOpen] = useState(false);
  const [addShopLaneId, setAddShopLaneId] = useState<string | null>(null);
  const [addShopPick, setAddShopPick] = useState<{
    id: number;
    name: string;
    code: string;
  } | null>(null);
  type PendingShopAdd = {
    tempTruckRowId: number;
    laneId: string;
    shopId: number;
    shopName: string;
    shopCode: string;
    loadingSequence: number;
  };
  const [pendingShopAdds, setPendingShopAdds] = useState<PendingShopAdd[]>(
    [],
  );
  const nextDraftTruckRowIdRef = useRef(-1_000_000_000);
  const addShopConfirmLockRef = useRef(false);
  const addRouteInFlightRef = useRef(false);

  /** 車牌/司機等：truck 表尚無欄位，先暫存於此並在 loadLanes 後疊加到 Lane（刷新頁面會丟失） */
  type LaneDisplayOverlay = Partial<
    Pick<Lane, "plate" | "driver" | "phone" | "logisticsCompany">
  >;
  const laneDisplayOverlayRef = useRef<Map<string, LaneDisplayOverlay>>(
    new Map(),
  );

  type NewRouteFormState = {
    truckLanceCode: string;
    /** 物流主檔 id；null = 未指定（至物流商管理指派） */
    logisticId: number | null;
    startTime: string;
    storeId: "2F" | "4F";
    remark: string;
  };

  const emptyNewRouteForm = (): NewRouteFormState => ({
    truckLanceCode: "",
    logisticId: null,
    startTime: "07:30",
    storeId: "2F",
    remark: "",
  });

  const [addRouteDialogOpen, setAddRouteDialogOpen] = useState(false);
  const [newRouteForm, setNewRouteForm] =
    useState<NewRouteFormState>(emptyNewRouteForm);
  const [addRouteSubmitting, setAddRouteSubmitting] = useState(false);
  const [addRouteError, setAddRouteError] = useState<string | null>(null);
  /** 尚未呼叫後端的新增車線（按「儲存更改」才 createTruckWithoutShop） */
  const [pendingNewLanes, setPendingNewLanes] = useState<PendingNewLane[]>([]);
  const pendingNewLanesRef = useRef<PendingNewLane[]>([]);
  useEffect(() => {
    pendingNewLanesRef.current = pendingNewLanes;
  }, [pendingNewLanes]);
  /** 看板末端「+」：從篩選後車線清單選一條，加入左欄勾選並捲動到該欄 */
  const [boardQuickPickAnchorEl, setBoardQuickPickAnchorEl] =
    useState<HTMLElement | null>(null);
  /** 「+」快速選車線 Popover 內關鍵字（不影響左欄篩選） */
  const [boardQuickPickSearch, setBoardQuickPickSearch] = useState("");
  /** 車線內尚無任何店鋪列的暫存「地區」顯示名（僅前端；見 `buildLaneDistrictSections` 註解） */
  const [pendingEmptyDistrictsByLane, setPendingEmptyDistrictsByLane] =
    useState<Record<string, string[]>>({});
  type DistrictEditCtx =
    | { laneId: string; mode: "add" }
    | { laneId: string; mode: "rename"; oldDisplay: string };
  const [districtEditOpen, setDistrictEditOpen] = useState(false);
  const [districtEditCtx, setDistrictEditCtx] = useState<DistrictEditCtx | null>(
    null,
  );
  const [districtEditDraft, setDistrictEditDraft] = useState("");
  const [districtEditError, setDistrictEditError] = useState<string | null>(null);
  const districtEditSubmitLockRef = useRef(false);
  /** `logistic` 表 logisticName（GET /logistic/all） */
  const [logisticNamesFromDb, setLogisticNamesFromDb] = useState<string[]>([]);
  const [logisticRowsFromDb, setLogisticRowsFromDb] = useState<LogisticRow[]>(
    [],
  );
  const [pendingLogisticMasterAdds, setPendingLogisticMasterAdds] = useState<
    Array<{ tempId: number } & SaveLogisticRequest>
  >([]);
  const [pendingLogisticMasterEdits, setPendingLogisticMasterEdits] = useState<
    Map<number, SaveLogisticRequest>
  >(new Map());
  const [pendingLogisticMasterDeletes, setPendingLogisticMasterDeletes] =
    useState<Set<number>>(new Set());
  const nextPendingLogisticTempIdRef = useRef(-1);
  const addLogisticInFlightRef = useRef(false);
  const logisticRowsEffective = useMemo(() => {
    const pendingRows: LogisticRow[] = pendingLogisticMasterAdds.map((p) => ({
      id: p.tempId,
      logisticName: p.logisticName,
      carPlate: p.carPlate,
      driverName: p.driverName,
      driverNumber: p.driverNumber,
    }));
    const dbWithEdits = logisticRowsFromDb
      .filter((r) => !pendingLogisticMasterDeletes.has(Number(r.id)))
      .map((r) => {
        const id = Number(r.id);
        const edit = pendingLogisticMasterEdits.get(id);
        if (!edit) return r;
        return {
          ...r,
          logisticName: edit.logisticName,
          carPlate: edit.carPlate,
          driverName: edit.driverName,
          driverNumber: edit.driverNumber,
        };
      });
    return [...pendingRows, ...dbWithEdits];
  }, [
    pendingLogisticMasterAdds,
    pendingLogisticMasterEdits,
    pendingLogisticMasterDeletes,
    logisticRowsFromDb,
  ]);
  const logisticNameById = useMemo(() => {
    const m = new Map<number, string>();
    for (const r of logisticRowsEffective) {
      const id = Number((r as any)?.id);
      const name = String((r as any)?.logisticName ?? "").trim();
      if (!Number.isFinite(id) || id === 0 || name === "") continue;
      m.set(id, name);
    }
    return m;
  }, [logisticRowsEffective]);

  const buildPendingLaneFromForm = useCallback(
    (form: NewRouteFormState): Lane => {
      const code = String(form.truckLanceCode || "").trim();
      const storeNorm = normalizeStoreId(form.storeId);
      const remarkRaw =
        storeNorm === "4F" && String(form.remark || "").trim() !== ""
          ? String(form.remark).trim()
          : null;
      const dep = parseTimeForBackend(form.startTime || "") || "00:00:00";
      const laneKey = encodeLaneId(code, remarkRaw);
      const lid =
        form.logisticId != null && Number.isFinite(Number(form.logisticId))
          ? Number(form.logisticId)
          : null;
      const master =
        lid != null
          ? logisticRowsEffective.find((r) => Number((r as any).id) === lid) ??
            null
          : null;
      return {
        id: laneKey,
        truckLanceCode: code,
        logisticsCompany: master
          ? String(master.logisticName ?? "").trim()
          : "",
        logisticId: lid,
        plate: master ? String(master.carPlate ?? "").trim() : "",
        driver: master ? String(master.driverName ?? "").trim() : "",
        phone:
          master != null &&
          (master as any).driverNumber != null &&
          Number.isFinite(Number((master as any).driverNumber))
            ? String((master as any).driverNumber)
            : "",
        startTime: dep,
        storeId: storeNorm,
        remark: remarkRaw,
        shops: [],
      };
    },
    [logisticRowsEffective],
  );

  const buildCreateTruckWithoutShopPayload = useCallback(
    (form: NewRouteFormState): CreateTruckWithoutShopRequest => {
      const code = String(form.truckLanceCode || "").trim();
      const storeNorm = normalizeStoreId(form.storeId);
      const remarkRaw =
        storeNorm === "4F" && String(form.remark || "").trim() !== ""
          ? String(form.remark).trim()
          : null;
      return {
        store_id: storeNorm,
        truckLanceCode: code,
        departureTime: parseTimeForBackend(form.startTime || "") || "00:00:00",
        loadingSequence: 0,
        districtReference: null,
        remark: remarkRaw,
        logisticId: form.logisticId,
      };
    },
    [],
  );

  const [addLogisticOpen, setAddLogisticOpen] = useState(false);
  const [addLogisticSubmitting, setAddLogisticSubmitting] = useState(false);
  const [addLogisticError, setAddLogisticError] = useState<string | null>(null);
  const [addLogisticForm, setAddLogisticForm] = useState({
    logisticName: "",
    carPlate: "",
    driverName: "",
    driverPhone: "",
  });
  const [editLogisticOpen, setEditLogisticOpen] = useState(false);
  const [editLogisticSubmitting, setEditLogisticSubmitting] = useState(false);
  const editLogisticInFlightRef = useRef(false);
  const [editLogisticError, setEditLogisticError] = useState<string | null>(
    null,
  );
  const [editLogisticForm, setEditLogisticForm] = useState({
    id: 0,
    logisticName: "",
    carPlate: "",
    driverName: "",
    driverPhone: "",
  });
  const [logisticsDropHoverCompany, setLogisticsDropHoverCompany] = useState<
    string | null
  >(null);

  const enrichLanesWithLogisticMaster = useCallback(
    (list: Lane[]): Lane[] => {
      if (!Array.isArray(list) || list.length === 0) return list;
      if (!Array.isArray(logisticRowsEffective) || logisticRowsEffective.length === 0)
        return list;

      const byId = new Map<number, LogisticRow>();
      for (const r of logisticRowsEffective) {
        const id = Number((r as any)?.id);
        if (!Number.isFinite(id) || id === 0) continue;
        if (!byId.has(id)) byId.set(id, r);
      }

      return list.map((lane) => {
        const lid = lane.logisticId != null ? Number(lane.logisticId) : NaN;
        if (!Number.isFinite(lid) || lid === 0) return lane;
        const master = byId.get(lid);
        if (!master) return lane;

        const plate = String((master as any).carPlate ?? "").trim();
        const driver = String((master as any).driverName ?? "").trim();
        const phone =
          (master as any).driverNumber != null &&
          Number.isFinite(Number((master as any).driverNumber))
            ? String((master as any).driverNumber)
            : "";

        return {
          ...lane,
          plate:
            lane.plate && String(lane.plate).trim() !== "" ? lane.plate : plate,
          driver:
            lane.driver && String(lane.driver).trim() !== ""
              ? lane.driver
              : driver,
          phone:
            lane.phone && String(lane.phone).trim() !== "" ? lane.phone : phone,
        };
      });
    },
    [logisticRowsEffective],
  );

  const applyLaneDisplayOverlays = (list: Lane[]): Lane[] => {
    const map = laneDisplayOverlayRef.current;
    return list.map((lane) => {
      const o = map.get(lane.id);
      if (!o) return lane;
      return {
        ...lane,
        plate: o.plate !== undefined && o.plate !== "" ? o.plate : lane.plate,
        driver:
          o.driver !== undefined && o.driver !== "" ? o.driver : lane.driver,
        phone: o.phone !== undefined && o.phone !== "" ? o.phone : lane.phone,
        logisticsCompany:
          o.logisticsCompany !== undefined && o.logisticsCompany !== ""
            ? o.logisticsCompany
            : lane.logisticsCompany,
        // DB 的 logisticId 不給 overlay 改
        logisticId: lane.logisticId,
      };
    });
  };

  const refreshLanesByIds = async (
    laneIds: string[],
    options: { preserveStagedLogistics?: boolean } = {},
  ): Promise<Lane[] | null> => {
    const uniq = Array.from(new Set(laneIds)).filter(Boolean);
    if (uniq.length === 0) return null;
    const preserveStagedLogistics = options.preserveStagedLogistics ?? true;
    const stagedLogisticsByLane = new Map<string, Lane>();
    if (preserveStagedLogistics) {
      for (const lane of lanesRef.current) {
        const currentLogisticId =
          lane.logisticId != null ? Number(lane.logisticId) : null;
        const baselineLogisticId = laneLogisticBaselineRef.current.has(lane.id)
          ? laneLogisticBaselineRef.current.get(lane.id) ?? null
          : null;
        if (currentLogisticId !== baselineLogisticId) {
          stagedLogisticsByLane.set(lane.id, lane);
        }
      }
    }
    const refreshed: Lane[] = [];
    for (const laneId of uniq) {
      const d = decodeLaneId(laneId);
      if (!d) continue;
      try {
        const lane = await fetchLaneByKey(d.truckLanceCode, d.remark);
        if (lane) refreshed.push(lane);
      } catch (e) {
        console.warn("refresh lane failed", laneId, e);
      }
    }
    if (refreshed.length === 0) return null;
    const mergedRefreshed = refreshed.map((lane) => {
      const staged = stagedLogisticsByLane.get(lane.id);
      if (!staged) return lane;
      return {
        ...lane,
        logisticId: staged.logisticId ?? null,
        logisticsCompany: staged.logisticsCompany,
        plate: staged.plate,
        driver: staged.driver,
        phone: staged.phone,
      };
    });
    // 同步 baseline（以 server 回來的 lane 為準）
    for (const lane of refreshed) {
      if (stagedLogisticsByLane.has(lane.id)) continue;
      laneLogisticBaselineRef.current.set(
        lane.id,
        lane.logisticId != null ? Number(lane.logisticId) : null,
      );
    }
    let nextApplied: Lane[] | null = null;
    setLanes((prev) => {
      const next = filterStagedDeletedShops(
        applyLaneDisplayOverlays(
          enrichLanesWithLogisticMaster(
            mergePendingNewLanesIntoLanes(
              mergeRefreshedLanes(prev, mergedRefreshed),
              pendingNewLanesRef.current,
            ),
          ),
        ),
        dirtyDeletesRef.current,
      );
      nextApplied = next;
      return next;
    });
    if (nextApplied != null) {
      syncShopDistrictBaselineFromLanes(nextApplied);
    }
    return nextApplied;
  };

  const loadLanes = async () => {
    if (loadLanesInFlightRef.current) return;
    loadLanesInFlightRef.current = true;
    setLoading(true);
    setError(null);
    setPendingEmptyDistrictsByLane({});
    closeDistrictEdit();
    try {
      // build shopCode -> real shop name map (from shop table)
      try {
        const shopRows = (await fetchAllShopsClient()) as Array<{
          id?: any;
          code?: any;
          name?: any;
        }>;
        const master = dedupeShopMasterRows(shopRows || []);
        const map = new Map<string, string>();
        master.forEach((row) => {
          const code = String(row.code ?? "")
            .trim()
            .toLowerCase();
          const name = String(row.name ?? "").trim();
          if (code && name) map.set(code, name);
        });
        setAllShopsMaster(master);
        setShopNameByCodeMap(map);
      } catch (e) {
        // non-blocking: still show branchName if shop table fetch fails
        console.warn("Failed to load shop table names:", e);
      }

      // O(1) load: 後端一次回傳所有 truck rows；前端自行按 lane 分桶
      const allRows = (await findAllForRouteBoardClient()) as Truck[];
      const bucket = new Map<
        string,
        {
          truckLanceCode: string;
          remark: string | null;
          meta: Truck;
          rows: Truck[];
        }
      >();
      for (const row of allRows || []) {
        const code = String((row as any)?.truckLanceCode ?? "").trim();
        if (!code) continue;
        const remarkRaw = String((row as any)?.remark ?? "").trim();
        const remark = remarkRaw !== "" ? remarkRaw : null;
        const id = encodeLaneId(code, remark);
        const b = bucket.get(id);
        if (!b) {
          bucket.set(id, {
            truckLanceCode: code,
            remark,
            meta: row,
            rows: [row],
          });
        } else {
          b.rows.push(row);
        }
      }

      const loaded: Lane[] = Array.from(bucket.values())
        .map((b) => {
          b.rows.sort((a: any, c: any) => {
            const sa = Number(a?.loadingSequence ?? 0) || 0;
            const sb = Number(c?.loadingSequence ?? 0) || 0;
            if (sa !== sb) return sa - sb;
            const ia = Number(a?.id ?? 0) || 0;
            const ib = Number(c?.id ?? 0) || 0;
            return ia - ib;
          });
          return buildLaneFromTruckRows(
            b.truckLanceCode,
            b.remark,
            b.rows,
            b.meta,
          );
        });

      const loadedWithPending = mergePendingNewLanesIntoLanes(
        sortLanesByCode(loaded),
        pendingNewLanesRef.current,
      );

      const nextBoard = filterStagedDeletedShops(
        applyLaneDisplayOverlays(
          enrichLanesWithLogisticMaster(loadedWithPending),
        ),
        dirtyDeletesRef.current,
      );
      lanesRef.current = nextBoard;
      setLanes(nextBoard);
      // 初始化 baseline（以 server 回來的 lane 為準）
      laneLogisticBaselineRef.current = new Map(
        nextBoard.map((l) => [
          l.id,
          l.logisticId != null ? Number(l.logisticId) : null,
        ]),
      );
      syncShopDistrictBaselineFromLanes(nextBoard);
      setPendingLogisticMasterAdds([]);
      setPendingLogisticMasterEdits(new Map());
      setPendingLogisticMasterDeletes(new Set());
      pendingImportFileRef.current = null;
      setPendingImportMeta(null);
      // default: select none (user will pick lanes)
      setSelectedLaneIds((prev) => prev);
    } catch (e: any) {
      console.error("Failed to load lanes:", e);
      setError(e?.message ?? String(e) ?? t("err_loadLanes"));
    } finally {
      setLoading(false);
      loadLanesInFlightRef.current = false;
    }
  };

  useEffect(() => {
    // Next dev StrictMode 會 double-invoke effect；這裡保證初始化只載一次
    if (didInitialLoadRef.current) return;
    didInitialLoadRef.current = true;
    void loadLanes();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const reloadLogisticNamesFromDb = useCallback(async () => {
    try {
      const rows = await findAllLogisticsClient();
      setLogisticRowsFromDb(rows || []);
      const names = Array.from(
        new Set(
          (rows || [])
            .map((r) => String(r.logisticName ?? "").trim())
            .filter((n) => n !== ""),
        ),
      ).sort((a, b) => a.localeCompare(b, "zh-Hant"));
      setLogisticNamesFromDb(names);
    } catch (e) {
      console.warn("Failed to load logistic master (logisticName):", e);
    }
  }, []);

  const resolveLogisticIdForCompanyLabel = useCallback(
    (company: string): number | null => {
      const c = String(company).trim();
      if (c === "" || c === "未分配物流商") return null;
      const row = logisticRowsEffective.find(
        (r) => String(r.logisticName ?? "").trim() === c,
      );
      return row != null && row.id != null ? Number(row.id) : null;
    },
    [logisticRowsEffective],
  );
  const getColumnTargetLogisticId = useCallback(
    (company: string, lanesInColumn: Lane[]): number | null => {
      if (company === "未分配物流商") return null;
      const withId = lanesInColumn.find(
        (l) =>
          l.logisticId != null &&
          Number.isFinite(Number(l.logisticId)) &&
          Number(l.logisticId) !== 0,
      );
      if (withId?.logisticId != null) return Number(withId.logisticId);
      return resolveLogisticIdForCompanyLabel(company);
    },
    [resolveLogisticIdForCompanyLabel],
  );

  useEffect(() => {
    void reloadLogisticNamesFromDb();
  }, [reloadLogisticNamesFromDb]);

  useEffect(() => {
    if (!Array.isArray(logisticRowsEffective) || logisticRowsEffective.length === 0)
      return;
    setLanes((prev) =>
      filterStagedDeletedShops(
        applyLaneDisplayOverlays(enrichLanesWithLogisticMaster(prev)),
        dirtyDeletesRef.current,
      ),
    );
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [logisticRowsEffective]);

  const submitAddLogistic = async () => {
    if (addLogisticInFlightRef.current) return;
    const logisticName = String(addLogisticForm.logisticName || "").trim();
    const carPlate = String(addLogisticForm.carPlate || "").trim();
    const driverName = String(addLogisticForm.driverName || "").trim();
    const driverNumber = phoneDigitsToDriverNumber(addLogisticForm.driverPhone);
    if (!logisticName || !carPlate || !driverName) {
      setAddLogisticError(t("val_logisticsRequired"));
      return;
    }
    if (driverNumber == null) {
      setAddLogisticError(t("val_phoneInvalid"));
      return;
    }
    const dup = logisticRowsEffective.some(
      (r) =>
        String(r.logisticName ?? "").trim().toLowerCase() ===
        logisticName.toLowerCase(),
    );
    if (dup) {
      setAddLogisticError(t("val_logisticsDuplicateName"));
      return;
    }
    addLogisticInFlightRef.current = true;
    setAddLogisticError(null);
    setAddLogisticSubmitting(true);
    try {
      const tempId = nextPendingLogisticTempIdRef.current;
      nextPendingLogisticTempIdRef.current -= 1;
      setPendingLogisticMasterAdds((prev) => [
        ...prev,
        {
          tempId,
          logisticName,
          carPlate,
          driverName,
          driverNumber,
        },
      ]);
      setAddLogisticForm({
        logisticName: "",
        carPlate: "",
        driverName: "",
        driverPhone: "",
      });
      setAddLogisticOpen(false);
      setSaveResult(null);
    } finally {
      setAddLogisticSubmitting(false);
      addLogisticInFlightRef.current = false;
    }
  };

  const openEditLogistic = (row: LogisticRow) => {
    setEditLogisticError(null);
    const id = Number(row.id);
    const pending = pendingLogisticMasterEdits.get(id);
    setEditLogisticForm({
      id: row.id,
      logisticName: String(
        pending?.logisticName ?? row.logisticName ?? "",
      ).trim(),
      carPlate: String(pending?.carPlate ?? row.carPlate ?? "").trim(),
      driverName: String(pending?.driverName ?? row.driverName ?? "").trim(),
      driverPhone:
        pending?.driverNumber != null && Number.isFinite(pending.driverNumber)
          ? String(pending.driverNumber)
          : row.driverNumber != null && Number.isFinite(row.driverNumber)
            ? String(row.driverNumber)
            : "",
    });
    setEditLogisticOpen(true);
  };

  const submitEditLogistic = async () => {
    if (editLogisticInFlightRef.current) return;
    const logisticName = String(editLogisticForm.logisticName || "").trim();
    const carPlate = String(editLogisticForm.carPlate || "").trim();
    const driverName = String(editLogisticForm.driverName || "").trim();
    const driverNumber = phoneDigitsToDriverNumber(
      editLogisticForm.driverPhone,
    );
    if (!editLogisticForm.id || editLogisticForm.id <= 0) {
      setEditLogisticError(t("err_invalidMasterId"));
      return;
    }
    if (!logisticName || !carPlate || !driverName) {
      setEditLogisticError(t("val_logisticsRequired"));
      return;
    }
    if (driverNumber == null) {
      setEditLogisticError(t("val_phoneInvalid"));
      return;
    }
    setEditLogisticError(null);
    editLogisticInFlightRef.current = true;
    setEditLogisticSubmitting(true);
    try {
      const lid = editLogisticForm.id;
      setPendingLogisticMasterEdits((prev) => {
        const next = new Map(prev);
        next.set(lid, {
          id: lid,
          logisticName,
          carPlate,
          driverName,
          driverNumber,
        });
        return next;
      });
      const phone =
        driverNumber != null && Number.isFinite(driverNumber)
          ? String(driverNumber)
          : "";
      setLanes((prev) =>
        filterStagedDeletedShops(
          applyLaneDisplayOverlays(
            prev.map((lane) =>
              lane.logisticId === lid
                ? {
                    ...lane,
                    logisticsCompany: logisticName,
                    plate: carPlate,
                    driver: driverName,
                    phone,
                  }
                : lane,
            ),
          ),
          dirtyDeletesRef.current,
        ),
      );
      setSaveResult(null);
      setEditLogisticOpen(false);
    } catch (e: any) {
      setEditLogisticError(e?.message ?? String(e) ?? t("err_save"));
    } finally {
      setEditLogisticSubmitting(false);
      editLogisticInFlightRef.current = false;
    }
  };

  const stageDeleteLogistic = useCallback(
    (row: LogisticRow, companyLabel: string) => {
      const id = Number(row.id);
      if (!Number.isFinite(id)) return;
      const name =
        String(companyLabel || row.logisticName || "").trim() || "—";
      if (!window.confirm(t("confirm_deleteLogistic", { name }))) return;
      if (id < 0) {
        setPendingLogisticMasterAdds((prev) =>
          prev.filter((p) => p.tempId !== id),
        );
      } else {
        setPendingLogisticMasterDeletes((prev) => {
          const next = new Set(prev);
          next.add(id);
          return next;
        });
      }
      setPendingLogisticMasterEdits((prev) => {
        const next = new Map(prev);
        next.delete(id);
        return next;
      });
      setSaveResult(null);
    },
    [t],
  );

  const handleExportSelectedLanesExcel = useCallback(async () => {
    if (selectedLaneIds.length === 0) {
      setError(t("err_exportNeedSelection"));
      return;
    }
    setRouteExcelBusy(true);
    setError(null);
    try {
      const { base64, filename } =
        await exportRouteLanesExcelClient(selectedLaneIds);
      downloadBase64Xlsx(base64, filename);
    } catch (e: any) {
      setError(e?.message ?? String(e) ?? t("err_export"));
    } finally {
      setRouteExcelBusy(false);
    }
  }, [selectedLaneIds, t]);

  const handleExportRouteReportExcel = useCallback(async () => {
    if (lanes.length === 0) {
      setError(t("err_noLanes"));
      return;
    }
    setRouteExcelBusy(true);
    setError(null);
    try {
      // 空 laneIds = 匯出 RouteBoard 全部（避免傳輸一大串 ids）
      const { base64, filename } = await exportRouteReportExcelClient([]);
      downloadBase64Xlsx(base64, filename);
    } catch (e: any) {
      setError(e?.message ?? String(e) ?? t("err_export"));
    } finally {
      setRouteExcelBusy(false);
    }
  }, [lanes, t]);

  const handleImportRouteExcelChange = async (
    e: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const file = e.target.files?.[0];
    e.target.value = "";
    if (!file) return;

    const hadOther =
      dirtyMoves.size > 0 ||
      dirtyDeletes.size > 0 ||
      pendingShopAdds.length > 0 ||
      pendingNewLanes.length > 0 ||
      pendingLogisticMasterAdds.length > 0 ||
      pendingLogisticMasterEdits.size > 0 ||
      pendingLogisticMasterDeletes.size > 0 ||
      pendingRestoreVersionId != null ||
      pendingImportMeta != null ||
      Object.values(pendingEmptyDistrictsByLane).some(
        (a) => (a?.length ?? 0) > 0,
      );
    if (hadOther && !window.confirm(t("confirm_importDiscardEdits"))) return;

    setRouteExcelBusy(true);
    setError(null);
    try {
      const fd = new FormData();
      fd.append("multipartFileList", file);
      const parsed = await parseRouteLanesExcelClient(fd);
      if (!parsed.rowCount || parsed.rowCount <= 0) {
        setError(t("err_importEmpty"));
        return;
      }
      setDirtyMoves(new Map());
      clearDirtyDeletesState();
      setPendingShopAdds([]);
      setPendingNewLanes([]);
      setPendingLogisticMasterAdds([]);
      setPendingLogisticMasterEdits(new Map());
      setPendingLogisticMasterDeletes(new Set());
      setPendingRestoreVersionId(null);
      setPendingEmptyDistrictsByLane({});
      setDistrictEditOpen(false);
      setDistrictEditCtx(null);
      setDepartureEditLaneId(null);
      setSeqEditTarget(null);
      lanesNeedingRefreshOnSaveRef.current.clear();
      laneDisplayOverlayRef.current.clear();
      const merged = mergeImportPreviewIntoLanes(lanesRef.current, parsed.rows);
      const nextBoard = applyLaneDisplayOverlays(
        enrichLanesWithLogisticMaster(merged),
      );
      lanesRef.current = nextBoard;
      setLanes(nextBoard);
      syncShopDistrictBaselineFromLanes(nextBoard);
      pendingImportFileRef.current = file;
      setPendingImportMeta({
        fileName: file.name,
        sheetCount: parsed.sheetCount,
        rowCount: parsed.rowCount,
      });
      setSaveResult({
        ok: true,
        message: t("import_staged_preview", {
          file: file.name,
          sheets: parsed.sheetCount,
          rows: parsed.rowCount,
        }),
      });
    } catch (err: any) {
      setError(err?.message ?? String(err) ?? t("err_import"));
    } finally {
      setRouteExcelBusy(false);
    }
  };

  const filteredLanes = useMemo(() => {
    const picked = lanes.filter((l) => selectedLaneIds.includes(l.id));
    const term = String(searchTerm || "")
      .trim()
      .toLowerCase();
    if (!term) return picked;

    return picked.map((lane) => ({
      ...lane,
      shops: lane.shops.filter((s) => {
        const codeLower = String(s.shopCode || "")
          .trim()
          .toLowerCase();
        const realName = shopNameByCodeMap.get(codeLower) ?? "";
        const name = String(realName || "").toLowerCase();
        const branch = String(s.branchName || "").toLowerCase();
        const code = String(s.shopCode || "").toLowerCase();
        const district = String(s.districtReferenceRaw || "").toLowerCase();
        return (
          name.includes(term) ||
          branch.includes(term) ||
          code.includes(term) ||
          district.includes(term)
        );
      }),
    }));
  }, [lanes, selectedLaneIds, searchTerm, shopNameByCodeMap]);

  const visibleLaneOptions = useMemo(() => {
    const q = String(laneFilter.query || "")
      .trim()
      .toLowerCase();
    return (lanes || []).filter((lane) => {
      const code = String(lane.truckLanceCode || "").toLowerCase();
      const rem = String(lane.remark ?? "").toLowerCase();
      const floorOk =
        laneFilter.floor === "all"
          ? true
          : normalizeStoreId(lane.storeId) === laneFilter.floor;
      const qOk = !q ? true : code.includes(q) || rem.includes(q);
      return floorOk && qOk;
    });
  }, [lanes, laneFilter.floor, laneFilter.query]);

  /** 「+」與看板欄：只吃樓層，不吃左欄關鍵字（與 `visibleLaneOptions` 分離） */
  const lanesMatchingFloorOnly = useMemo(() => {
    return (lanes || []).filter((lane) => {
      const floorOk =
        laneFilter.floor === "all"
          ? true
          : normalizeStoreId(lane.storeId) === laneFilter.floor;
      return floorOk;
    });
  }, [lanes, laneFilter.floor]);

  const boardQuickPickFilteredLanes = useMemo(() => {
    const q = String(boardQuickPickSearch).trim().toLowerCase();
    if (!q) return lanesMatchingFloorOnly;
    return lanesMatchingFloorOnly.filter((lane) => {
      const code = String(lane.truckLanceCode || "").toLowerCase();
      const rem = String(lane.remark ?? "").toLowerCase();
      const driver = String(lane.driver ?? "").toLowerCase();
      const plate = String(lane.plate ?? "").toLowerCase();
      return (
        code.includes(q) ||
        rem.includes(q) ||
        driver.includes(q) ||
        plate.includes(q)
      );
    });
  }, [lanesMatchingFloorOnly, boardQuickPickSearch]);

  const scrollBoardLaneCardIntoView = useCallback((laneId: string) => {
    const safe =
      typeof CSS !== "undefined" && typeof CSS.escape === "function"
        ? CSS.escape(laneId)
        : laneId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
    document
      .querySelector(`[data-lane-id="${safe}"]`)
      ?.scrollIntoView({
        behavior: "smooth",
        inline: "center",
        block: "nearest",
      });
  }, []);

  const applyBoardQuickPickLane = useCallback(
    (laneId: string) => {
      setBoardQuickPickAnchorEl(null);
      setBoardQuickPickSearch("");
      setSelectedLaneIds((prev) =>
        prev.includes(laneId) ? prev : [...prev, laneId],
      );
      requestAnimationFrame(() => {
        requestAnimationFrame(() => scrollBoardLaneCardIntoView(laneId));
      });
    },
    [scrollBoardLaneCardIntoView],
  );

  /** 新增車線：物流公司下拉選單（依名稱排序） */
  const logisticRowsSortedForSelect = useMemo(
    () =>
      [...logisticRowsEffective].sort((a, b) =>
        String(a.logisticName ?? "").localeCompare(
          String(b.logisticName ?? ""),
          "zh-Hant",
        ),
      ),
    [logisticRowsEffective],
  );

  const logisticNamesEffective = useMemo(() => {
    const set = new Set<string>();
    for (const p of pendingLogisticMasterAdds) {
      const n = String(p.logisticName ?? "").trim();
      if (n) set.add(n);
    }
    for (const r of logisticRowsFromDb) {
      const id = Number(r.id);
      if (pendingLogisticMasterDeletes.has(id)) continue;
      const s = String(r.logisticName ?? "").trim();
      if (s) set.add(s);
    }
    return Array.from(set).sort((a, b) => a.localeCompare(b, "zh-Hant"));
  }, [
    pendingLogisticMasterAdds,
    pendingLogisticMasterDeletes,
    logisticRowsFromDb,
  ]);

  /** 依樓層（不含車線下拉搜尋關鍵字）分組；併入 GET /logistic/all 有、但尚未掛車線的公司 */
  const lanesByLogisticsCompany = useMemo(() => {
    const map = new Map<string, Lane[]>();
    for (const lane of lanesMatchingFloorOnly) {
      if (
        selectedLaneIds.length > 0 &&
        !selectedLaneIds.includes(lane.id)
      ) {
        continue;
      }
      const company =
        String(lane.logisticsCompany ?? "").trim() || "未分配物流商";
      const arr = map.get(company) ?? [];
      arr.push(lane);
      map.set(company, arr);
    }
    for (const name of logisticNamesEffective) {
      const n = String(name).trim();
      if (n && !map.has(n)) map.set(n, []);
    }
    const entries = Array.from(map.entries());
    entries.sort((a, b) => {
      if (a[0] === "未分配物流商" && b[0] !== "未分配物流商") return 1;
      if (b[0] === "未分配物流商" && a[0] !== "未分配物流商") return -1;
      return a[0].localeCompare(b[0], "zh-Hant");
    });
    return entries;
  }, [lanesMatchingFloorOnly, logisticNamesEffective, selectedLaneIds]);

  const addShopCandidates = useMemo(() => {
    if (!addShopLaneId) return [];
    const lane = lanes.find((l) => l.id === addShopLaneId);
    if (!lane) return [];
    const codesInLane = new Set(
      lane.shops
        .map((s) =>
          String(s.shopCode || "")
            .trim()
            .toLowerCase(),
        )
        .filter((c) => c !== ""),
    );
    const idsInLane = new Set(
      lane.shops
        .map((s) => s.shopEntityId)
        .filter((x): x is number => typeof x === "number" && x > 0),
    );
    return allShopsMaster.filter((m) => {
      const c = String(m.code || "")
        .trim()
        .toLowerCase();
      if (c && codesInLane.has(c)) return false;
      if (idsInLane.has(m.id)) return false;
      return true;
    });
  }, [addShopLaneId, lanes, allShopsMaster]);

  const assertMsgOk = (res: MessageResponse, fallback: string) => {
    const msg = res?.message != null ? String(res.message) : "";
    if (msg.startsWith("Error:") || msg.startsWith("Error :")) {
      throw new Error(msg || fallback);
    }
  };

  const handleLogisticsDropOnCompany = (
    targetCompany: string,
    companyLanes: Lane[],
  ) => {
    const dragId = logisticsLaneDragIdRef.current;
    logisticsLaneDragIdRef.current = null;
    setLogisticsDropHoverCompany(null);
    if (!dragId) return;
    const lane = lanesRef.current.find((l) => l.id === dragId);
    if (!lane) return;

    const targetId = getColumnTargetLogisticId(targetCompany, companyLanes);
    if (targetCompany !== "未分配物流商" && targetId == null) {
      setError(
        t("logistic_needMasterTpl", { name: targetCompany }),
      );
      return;
    }
    const fromId = lane.logisticId ?? null;
    if (targetCompany === "未分配物流商") {
      if (fromId == null) return;
    } else if (targetId != null && fromId === targetId) {
      return;
    }

    const targetMaster =
      targetId != null
        ? logisticRowsEffective.find((r) => Number(r.id) === targetId)
        : null;
    const nextLane: Lane = {
      ...lane,
      logisticId: targetId,
      logisticsCompany:
        targetCompany === "未分配物流商"
          ? ""
          : targetMaster?.logisticName?.trim() || targetCompany,
      plate: targetMaster?.carPlate?.trim() || "",
      driver: targetMaster?.driverName?.trim() || "",
      phone:
        targetMaster?.driverNumber != null &&
        Number.isFinite(Number(targetMaster.driverNumber))
          ? String(targetMaster.driverNumber)
          : "",
    };
    const next = lanesRef.current.map((l) => (l.id === lane.id ? nextLane : l));
    lanesNeedingRefreshOnSaveRef.current.add(lane.id);
    laneDisplayOverlayRef.current.delete(lane.id);
    lanesRef.current = next;
    setLanes(next);
    setSaveResult(null);
    setError(null);
  };

  const openAddShopDialog = (laneId: string) => {
    setAddShopLaneId(laneId);
    setAddShopPick(null);
    setAddShopDialogOpen(true);
  };

  const closeAddShopDialog = () => {
    setAddShopDialogOpen(false);
    setAddShopLaneId(null);
    setAddShopPick(null);
  };

  const closeDistrictEdit = () => {
    setDistrictEditOpen(false);
    setDistrictEditCtx(null);
    setDistrictEditDraft("");
    setDistrictEditError(null);
  };

  const openDistrictAdd = (laneId: string) => {
    setDistrictEditCtx({ laneId, mode: "add" });
    setDistrictEditDraft("");
    setDistrictEditError(null);
    setDistrictEditOpen(true);
  };

  const openDistrictRename = (laneId: string, oldDisplay: string) => {
    setDistrictEditCtx({ laneId, mode: "rename", oldDisplay });
    setDistrictEditDraft(oldDisplay === "未分類" ? "" : oldDisplay);
    setDistrictEditError(null);
    setDistrictEditOpen(true);
  };

  const removePendingEmptyDistrict = (laneId: string, display: string) => {
    setPendingEmptyDistrictsByLane((prev) => {
      const arr = prev[laneId];
      if (!arr?.length) return prev;
      const nextArr = arr.filter((d) => d !== display);
      if (nextArr.length === arr.length) return prev;
      const next = { ...prev };
      if (nextArr.length === 0) delete next[laneId];
      else next[laneId] = nextArr;
      return next;
    });
    setSaveResult(null);
  };

  const applyDistrictEdit = () => {
    if (districtEditSubmitLockRef.current || !districtEditCtx) return;
    districtEditSubmitLockRef.current = true;
    setDistrictEditError(null);
    try {
      const lane = lanesRef.current.find((l) => l.id === districtEditCtx.laneId);
      if (!lane) {
        closeDistrictEdit();
        return;
      }
      const pendingExtra =
        pendingEmptyDistrictsByLane[districtEditCtx.laneId] ?? [];
      const trimmed = districtEditDraft.trim();

      if (districtEditCtx.mode === "add") {
        if (!trimmed) {
          setDistrictEditError(t("district_err_name"));
          return;
        }
        if (trimmed === "未分類") {
          setDistrictEditError(t("district_err_reserved"));
          return;
        }
        if (districtDisplayExistsInLane(lane, pendingExtra, trimmed)) {
          setDistrictEditError(t("district_err_exists"));
          return;
        }
        setPendingEmptyDistrictsByLane((prev) => {
          const lid = districtEditCtx.laneId;
          const merged = dedupeDistrictPendingOrder([
            ...(prev[lid] ?? []),
            trimmed,
          ]);
          return { ...prev, [lid]: merged };
        });
        setSaveResult(null);
        closeDistrictEdit();
        return;
      }

      const oldDisp = districtEditCtx.oldDisplay;
      const newDisp = trimmed === "" ? "未分類" : trimmed;
      if (newDisp === oldDisp) {
        closeDistrictEdit();
        return;
      }

      const newRaw = toDistrictRawValue(newDisp);
      const touchedIds: number[] = [];
      const next = lanesRef.current.map((l) => {
        if (l.id !== districtEditCtx.laneId) return l;
        return {
          ...l,
          shops: l.shops.map((s) => {
            if (toDistrictDisplayName(s.districtReferenceRaw) !== oldDisp)
              return s;
            touchedIds.push(s.id);
            return { ...s, districtReferenceRaw: newRaw };
          }),
        };
      });
      lanesRef.current = next;
      setLanes(next);
      setDirtyMoves((prev) => {
        const n = new Map(prev);
        for (const id of touchedIds) {
          if (id > 0) n.set(id, districtEditCtx.laneId);
        }
        return n;
      });
      lanesNeedingRefreshOnSaveRef.current.add(districtEditCtx.laneId);
      const lid = districtEditCtx.laneId;
      setPendingEmptyDistrictsByLane((prev) => {
        const arr = prev[lid];
        let pendingList = arr ? [...arr] : [];
        if (pendingList.includes(oldDisp)) {
          pendingList = pendingList.map((d) => (d === oldDisp ? newDisp : d));
        }
        const laneNext = next.find((l) => l.id === lid);
        const withShops = new Set(
          groupByDistrict(laneNext?.shops ?? []).map((g) => g.district),
        );
        const pruned = dedupeDistrictPendingOrder(
          pendingList.filter((d) => !withShops.has(d)),
        );
        const out = { ...prev };
        if (pruned.length === 0) delete out[lid];
        else out[lid] = pruned;
        return out;
      });
      setSaveResult(null);
      closeDistrictEdit();
    } finally {
      districtEditSubmitLockRef.current = false;
    }
  };

  const openAddRouteDialog = () => {
    setNewRouteForm(emptyNewRouteForm());
    setAddRouteError(null);
    setAddRouteDialogOpen(true);
  };

  const closeAddRouteDialog = () => {
    setAddRouteDialogOpen(false);
    setNewRouteForm(emptyNewRouteForm());
    setAddRouteError(null);
  };

  const submitAddRoute = () => {
    const code = String(newRouteForm.truckLanceCode || "").trim();
    const storeNorm = normalizeStoreId(newRouteForm.storeId);
    const remarkRaw =
      storeNorm === "4F" && String(newRouteForm.remark || "").trim() !== ""
        ? String(newRouteForm.remark).trim()
        : null;
    const laneKey = encodeLaneId(code, remarkRaw);

    if (!code) {
      setAddRouteError(t("route_err_code"));
      return;
    }
    const dep = parseTimeForBackend(newRouteForm.startTime || "");
    if (!dep) {
      setAddRouteError(t("route_err_departure"));
      return;
    }

    if (
      lanesRef.current.some((l) => l.id === laneKey) ||
      pendingNewLanesRef.current.some((p) => p.laneKey === laneKey)
    ) {
      setAddRouteError(t("route_err_duplicate"));
      return;
    }

    if (addRouteInFlightRef.current) return;
    addRouteInFlightRef.current = true;

    setAddRouteSubmitting(true);
    setAddRouteError(null);
    try {
      const payload = buildCreateTruckWithoutShopPayload(newRouteForm);
      const draftLane = buildPendingLaneFromForm(newRouteForm);
      setPendingNewLanes((prev) => [...prev, { laneKey, payload }]);
      laneLogisticBaselineRef.current.set(
        laneKey,
        newRouteForm.logisticId != null &&
          Number.isFinite(Number(newRouteForm.logisticId))
          ? Number(newRouteForm.logisticId)
          : null,
      );
      setLanes((prev) => {
        const next = sortLanesByCode([...prev, draftLane]);
        return applyLaneDisplayOverlays(enrichLanesWithLogisticMaster(next));
      });
      closeAddRouteDialog();
      setSelectedLaneIds((prev) =>
        prev.includes(laneKey) ? prev : [...prev, laneKey],
      );
      requestAnimationFrame(() => {
        requestAnimationFrame(() => scrollBoardLaneCardIntoView(laneKey));
      });
      setSaveResult(null);
    } catch (e: any) {
      setAddRouteError(e?.message ?? String(e) ?? t("route_err_create"));
    } finally {
      setAddRouteSubmitting(false);
      addRouteInFlightRef.current = false;
    }
  };

  const submitAddShop = () => {
    if (!addShopLaneId || !addShopPick) return;
    if (addShopConfirmLockRef.current) return;
    const lane = lanes.find((l) => l.id === addShopLaneId);
    if (!lane) return;

    addShopConfirmLockRef.current = true;
    setError(null);
    try {
      const flat = flattenDisplayOrder(lane.shops);
      const last = flat[flat.length - 1];
      const newSeq = last != null ? last.loadingSequence : 0;
      const seq = Number(newSeq) || 0;
      const storeId = normalizeStoreId(lane.storeId);
      const remark =
        storeId === "4F" &&
        lane.remark != null &&
        String(lane.remark).trim() !== ""
          ? String(lane.remark).trim()
          : null;

      const tempId = nextDraftTruckRowIdRef.current;
      nextDraftTruckRowIdRef.current -= 1;

      const baseRows = lanesToWarningInputRows(lanes);
      const hypo = appendSyntheticPendingShopRow(
        baseRows,
        {
          truckLanceCode: lane.truckLanceCode,
          laneRemark: lane.remark ?? null,
          storeId: lane.storeId,
          startTime: lane.startTime,
        },
        addShopPick,
        tempId,
      );
      const wr = computeTruckLaneWarnings(hypo);
      const touching = wr.warnings.filter((w) =>
        warningTouchesPickedShop(w, addShopPick),
      );
      if (touching.length > 0) {
        const ok = window.confirm(
          t("confirm_addShopConflict", { count: touching.length }),
        );
        if (!ok) return;
      }

      const newCard: ShopCard = {
        id: tempId,
        shopEntityId: addShopPick.id,
        branchName: addShopPick.name,
        shopCode: addShopPick.code,
        districtReferenceRaw: null,
        loadingSequence: seq,
        remark,
        storeId,
        departureTime: parseTimeForBackend(lane.startTime),
      };

      setLanes((prev) =>
        prev.map((l) =>
          l.id !== lane.id ? l : { ...l, shops: [...l.shops, newCard] },
        ),
      );
      setPendingShopAdds((prev) => [
        ...prev,
        {
          tempTruckRowId: tempId,
          laneId: lane.id,
          shopId: addShopPick.id,
          shopName: addShopPick.name,
          shopCode: addShopPick.code,
          loadingSequence: seq,
        },
      ]);
      lanesNeedingRefreshOnSaveRef.current.add(lane.id);
      closeAddShopDialog();
      if (wr.warnings.some((w) => warningTouchesPickedShop(w, addShopPick))) {
        setLaneWarnSnackbar(t("mtmsRouteWarn_postAddConflict"));
      }
    } finally {
      addShopConfirmLockRef.current = false;
    }
  };

  const handleDeleteTruckRow = async (truckRowId: number) => {
    if (truckRowId > 0 && scheduledShopIdSet.has(truckRowId)) {
      return;
    }
    if (truckRowId < 0) {
      if (!window.confirm(t("confirm_discardDraftShop"))) return;
      setError(null);
      setPendingShopAdds((prev) =>
        prev.filter((p) => p.tempTruckRowId !== truckRowId),
      );
      setLanes((prev) =>
        prev.map((lane) => ({
          ...lane,
          shops: lane.shops.filter((s) => s.id !== truckRowId),
        })),
      );
      setSaveResult(null);
      return;
    }
    if (!window.confirm(t("confirm_removeShop")))
      return;
    setError(null);
    const affectedLaneId = lanesRef.current.find((l) =>
      l.shops.some((s) => s.id === truckRowId),
    )?.id;
    if (affectedLaneId) {
      lanesNeedingRefreshOnSaveRef.current.add(affectedLaneId);
    }

    const srcLane = lanesRef.current.find((l) =>
      l.shops.some((s) => s.id === truckRowId),
    );
    const shop = srcLane?.shops.find((s) => s.id === truckRowId);
    if (srcLane && shop && truckRowId > 0) {
      stagedDeleteMetaRef.current.set(truckRowId, {
        shopCode: String(shop.shopCode ?? "").trim(),
        branchName: String(shop.branchName ?? "").trim(),
        fromLane: formatLaneLabel(srcLane.truckLanceCode, srcLane.remark),
      });
    }

    // delete beats move
    setDirtyMoves((prev) => {
      const next = new Map(prev);
      next.delete(truckRowId);
      return next;
    });
    setDirtyDeletes((prev) => {
      const next = new Set(prev);
      next.add(truckRowId);
      return next;
    });

    // optimistic UI: remove now; cancel will refetch
    setLanes((prev) =>
      prev.map((lane) =>
        lane.shops.some((s) => s.id === truckRowId)
          ? { ...lane, shops: lane.shops.filter((s) => s.id !== truckRowId) }
          : lane,
      ),
    );
    setSaveResult(null);
  };

  /** 清空整桶店鋪：與單筆刪除相同，僅標記 dirtyDeletes，按「儲存更改」才 deleteTruckLaneClient */
  const handleClearLaneShops = (lane: Lane) => {
    if (lane.shops.length === 0) return;
    if (
      lane.shops.some((s) => s.id > 0 && scheduledShopIdSet.has(s.id))
    ) {
      return;
    }
    if (
      !window.confirm(
        t("confirm_clearLane", {
          laneLabel: `${lane.truckLanceCode}${
            lane.remark != null && String(lane.remark).trim() !== ""
              ? ` · ${lane.remark}`
              : ""
          }`,
          count: lane.shops.length,
        }),
      )
    )
      return;
    setError(null);
    const draftIds = new Set(
      lane.shops.filter((s) => s.id < 0).map((s) => s.id),
    );
    const serverShopIds = lane.shops
      .filter((s) => s.id > 0)
      .map((s) => s.id);

    if (draftIds.size > 0) {
      setPendingShopAdds((prev) =>
        prev.filter((p) => !draftIds.has(p.tempTruckRowId)),
      );
    }

    if (serverShopIds.length > 0) {
      lanesNeedingRefreshOnSaveRef.current.add(lane.id);
      for (const sid of serverShopIds) {
        const shop = lane.shops.find((s) => s.id === sid);
        if (shop && sid > 0) {
          stagedDeleteMetaRef.current.set(sid, {
            shopCode: String(shop.shopCode ?? "").trim(),
            branchName: String(shop.branchName ?? "").trim(),
            fromLane: formatLaneLabel(lane.truckLanceCode, lane.remark),
          });
        }
      }
      setDirtyMoves((prev) => {
        const next = new Map(prev);
        for (const id of serverShopIds) next.delete(id);
        return next;
      });
      setDirtyDeletes((prev) => {
        const next = new Set(prev);
        for (const id of serverShopIds) next.add(id);
        return next;
      });
    }

    setLanes((prev) =>
      prev.map((l) => (l.id === lane.id ? { ...l, shops: [] } : l)),
    );
    setSaveResult(null);
  };

  const handleDragStart = (shopId: number, fromLaneId: string) => {
    draggedRef.current = { shopId, fromLaneId };
    setDropIndicator(null);
  };

  const clearDragState = () => {
    draggedRef.current = null;
    setDropIndicator(null);
  };

  const getBeforeShopIdByPointer = (
    laneId: string,
    clientY: number,
  ): number | null => {
    const laneEl = document.querySelector<HTMLElement>(
      `[data-lane-id="${CSS.escape(laneId)}"]`,
    );
    if (!laneEl) return null;
    const cards = Array.from(
      laneEl.querySelectorAll<HTMLElement>("[data-shop-id]"),
    );
    for (const cardEl of cards) {
      const rect = cardEl.getBoundingClientRect();
      const midY = rect.top + rect.height / 2;
      if (clientY < midY) {
        const idStr = cardEl.getAttribute("data-shop-id");
        const id = idStr ? Number(idStr) : NaN;
        return Number.isFinite(id) ? id : null;
      }
    }
    return null; // append
  };

  const handleDropToLane = (toLaneId: string) => {
    const before =
      dropIndicator != null && dropIndicator.laneId === toLaneId
        ? dropIndicator.beforeShopId
        : null;
    handleDropToPosition(toLaneId, before);
  };

  const handleDropToPosition = (
    toLaneId: string,
    beforeShopId: number | null,
    targetDistrict?: string | null,
  ) => {
    const dragged = draggedRef.current;
    if (!dragged) return;
    if (dragged.shopId < 0) {
      setError(
        t("drag_blockDraftShop"),
      );
      clearDragState();
      return;
    }
    if (beforeShopId != null && beforeShopId === dragged.shopId) {
      clearDragState();
      return;
    }

    const dirtyToAdd: Array<[number, string]> = [];
    const base = lanesRef.current;
    const next = base.map((lane) => ({ ...lane, shops: lane.shops.slice() }));
    const from = next.find((l) => l.id === dragged.fromLaneId);
    const to = next.find((l) => l.id === toLaneId);
    if (!from || !to) return;

    const shop = from.shops.find((s) => s.id === dragged.shopId);
    if (!shop) return;

    if (from.id !== to.id && laneTargetConflicts(shop, to)) {
      setError(t("err_dragDuplicateShop"));
      clearDragState();
      return;
    }

    const oldSeqFrom = new Map<number, number>(
      from.shops.map((s) => [s.id, s.loadingSequence]),
    );
    const oldSeqTo = new Map<number, number>(
      to.shops.map((s) => [s.id, s.loadingSequence]),
    );

    // build display-ordered lists (matches what user sees on screen)
    const fromFlat = flattenDisplayOrder(from.shops).filter(
      (s) => s.id !== dragged.shopId,
    );
    const toFlatRaw = flattenDisplayOrder(to.shops);
    const toFlat =
      from.id === to.id
        ? toFlatRaw.filter((s) => s.id !== dragged.shopId)
        : toFlatRaw.slice();

    const beforeShop =
      beforeShopId != null ? toFlat.find((s) => s.id === beforeShopId) : null;
    const targetDistrictRaw =
      targetDistrict !== undefined
        ? toDistrictRawValue(targetDistrict)
        : beforeShop
          ? toDistrictRawValue(beforeShop.districtReferenceRaw)
          : shop.districtReferenceRaw;

    // keep fields but update departure/store/remark/district to match target position
    const moved: ShopCard = {
      ...shop,
      districtReferenceRaw: targetDistrictRaw,
      departureTime: to.startTime,
      storeId: to.storeId,
      remark:
        normalizeStoreId(to.storeId) === "4F"
          ? to.remark != null && String(to.remark).trim() !== ""
            ? String(to.remark).trim()
            : null
          : null,
    };

    // insert into target by DISPLAY ORDER (beforeShopId if provided, else append)
    const insertIdx =
      beforeShopId != null
        ? toFlat.findIndex((s) => s.id === beforeShopId)
        : targetDistrict !== undefined
          ? (() => {
              const targetDisplay = toDistrictDisplayName(targetDistrictRaw);
              for (let i = toFlat.length - 1; i >= 0; i -= 1) {
                if (
                  toDistrictDisplayName(toFlat[i].districtReferenceRaw) ===
                  targetDisplay
                ) {
                  return i + 1;
                }
              }
              return -1;
            })()
          : -1;
    const inserted =
      insertIdx >= 0
        ? [...toFlat.slice(0, insertIdx), moved, ...toFlat.slice(insertIdx)]
        : [...toFlat, moved];

    // write back lists
    from.shops = fromFlat;
    to.shops = inserted;

    // IMPORTANT: Do NOT renumber all loadingSequence.
    // loadingSequence can be duplicated intentionally (e.g. 4F grouping).
    // On drag/drop, only update the moved shop's loadingSequence so it joins the target group.
    const newSeq = computeMovedLoadingSequence(
      inserted,
      moved.id,
      targetDistrict !== undefined && beforeShopId == null,
    );
    to.shops = to.shops.map((s) =>
      s.id === moved.id ? { ...s, loadingSequence: newSeq } : s,
    );

    if (
      from.id !== to.id ||
      toDistrictDisplayName(shop.districtReferenceRaw) !==
        toDistrictDisplayName(targetDistrictRaw)
    ) {
      dirtyToAdd.push([moved.id, to.id]);
    }

    // mark dirty for any sequence changes + moved shop
    from.shops.forEach((s) => {
      const old = oldSeqFrom.get(s.id);
      if (old == null || old !== s.loadingSequence)
        dirtyToAdd.push([s.id, from.id]);
    });
    if (to.id !== from.id) {
      to.shops.forEach((s) => {
        const old = oldSeqTo.get(s.id);
        if (old == null || old !== s.loadingSequence)
          dirtyToAdd.push([s.id, to.id]);
      });
    } else {
      // same lane: compare using oldSeqFrom for all shops
      to.shops.forEach((s) => {
        const old = oldSeqFrom.get(s.id);
        if (old == null || old !== s.loadingSequence)
          dirtyToAdd.push([s.id, to.id]);
      });
    }

    dirtyToAdd.forEach(([, laneId]) =>
      lanesNeedingRefreshOnSaveRef.current.add(laneId),
    );
    if (from.id !== to.id) {
      lanesNeedingRefreshOnSaveRef.current.add(from.id);
    }

    if (dirtyToAdd.length === 0) {
      clearDragState();
      return;
    }

    // Make rapid successive drops in same tick see latest snapshot.
    setError(null);
    lanesRef.current = next;
    setLanes(next);
    if (dirtyToAdd.length > 0) {
      setDirtyMoves((prevDirty) => {
        const nextDirty = new Map(prevDirty);
        dirtyToAdd.forEach(([shopId, laneId]) => nextDirty.set(shopId, laneId));
        return nextDirty;
      });
    }

    clearDragState();
  };

  const scheduleLaneOptions = useMemo(
    () =>
      lanes.map((lane) => ({
        id: lane.id,
        label: formatLaneLabel(lane.truckLanceCode, lane.remark),
        truckLanceCode: lane.truckLanceCode,
        remark: lane.remark,
        storeId: normalizeStoreId(lane.storeId),
        departureTime: parseTimeForBackend(lane.startTime) || "00:00:00",
        shops: lane.shops
          .filter((s) => s.id >= 0)
          .map((s) => ({
            truckRowId: s.id,
            districtReferenceRaw: s.districtReferenceRaw ?? null,
            loadingSequence: s.loadingSequence ?? 0,
          })),
      })),
    [lanes],
  );

  const scheduleShopRows = useMemo((): ScheduleShopRow[] => {
    const rows: ScheduleShopRow[] = [];
    for (const lane of lanes) {
      const currentLaneLabel = formatLaneLabel(
        lane.truckLanceCode,
        lane.remark,
      );
      for (const shop of lane.shops) {
        if (shop.id < 0) continue;
        const codeLower = String(shop.shopCode || "")
          .trim()
          .toLowerCase();
        const realName = shopNameByCodeMap.get(codeLower);
        const displayName =
          realName && String(realName).trim() !== ""
            ? String(realName).trim()
            : String(shop.branchName || "").trim() || "-";
        const district = toDistrictDisplayName(shop.districtReferenceRaw);
        const location = [
          district,
          shop.shopCode || "-",
          shop.storeId ? normalizeStoreId(shop.storeId) : "",
        ]
          .filter(Boolean)
          .join(" · ");
        rows.push({
          truckRowId: shop.id,
          shopCode: String(shop.shopCode || "").trim() || "-",
          displayName,
          branchName: String(shop.branchName || "").trim(),
          storeId: shop.storeId ? normalizeStoreId(shop.storeId) : "",
          location,
          districtDisplay: district,
          districtReferenceRaw: shop.districtReferenceRaw ?? null,
          currentLaneId: lane.id,
          currentLaneLabel,
        });
      }
    }
    return rows.sort((a, b) =>
      a.displayName.localeCompare(b.displayName, "zh-Hant"),
    );
  }, [lanes, shopNameByCodeMap]);

  const handleScheduleConfirmManual = useCallback(
    async (payload: ScheduleChangePayload) => {
      const validation = validatePayloadSubmit({
        payload,
        lanes: scheduleLaneOptions,
        shops: scheduleShopRows,
        pendingTruckRowIds: pendingScheduleShopIds,
      });
      if (!validation.ok) {
        setError(formatScheduleValidationErrors(t, validation.errors));
        return;
      }
      const request = buildRequestFromPayload(payload, scheduleLaneOptions);
      if (!request || (request.lines?.length ?? 0) === 0) {
        setError(t("schedule_err_conflict"));
        return;
      }
      try {
        await createTruckLaneScheduleClient(request);
        setLaneWarnSnackbar(
          t("schedule_registered_snackbar", { count: request.lines?.length ?? 0 }),
        );
      } catch (err: unknown) {
        setError(extractApiErrorMessage(err) ?? t("schedule_err_generic"));
        throw err;
      }
    },
    [t, scheduleLaneOptions, scheduleShopRows, pendingScheduleShopIds],
  );


  const prevPendingScheduleSizeRef = useRef(0);
  useEffect(() => {
    const n = pendingScheduleShopIds.size;
    if (
      prevPendingScheduleSizeRef.current > 0 &&
      n < prevPendingScheduleSizeRef.current
    ) {
      void loadLanes();
    }
    prevPendingScheduleSizeRef.current = n;
  }, [pendingScheduleShopIds]);

  const handleDragOver: React.DragEventHandler = (e) => {
    e.preventDefault();
  };

  const getLaneLogisticChanges = useCallback((): Array<{
    laneId: string;
    logisticId: number | null;
  }> => {
    const changes: Array<{ laneId: string; logisticId: number | null }> = [];
    for (const lane of lanesRef.current) {
      const nextId = lane.logisticId != null ? Number(lane.logisticId) : null;
      const prevId = laneLogisticBaselineRef.current.has(lane.id)
        ? laneLogisticBaselineRef.current.get(lane.id) ?? null
        : null;
      if (prevId !== nextId)
        changes.push({ laneId: lane.id, logisticId: nextId });
    }
    return changes;
  }, []);

  const hasDirtyLaneLogistics = getLaneLogisticChanges().length > 0;
  const dirtyLaneLogisticIds = new Set(
    getLaneLogisticChanges().map((c) => c.laneId),
  );
  const hasUnsavedChanges =
    dirtyMoves.size > 0 ||
    dirtyDeletes.size > 0 ||
    hasDirtyLaneLogistics ||
    pendingShopAdds.length > 0 ||
    pendingNewLanes.length > 0 ||
    pendingLogisticMasterAdds.length > 0 ||
    pendingLogisticMasterEdits.size > 0 ||
    pendingLogisticMasterDeletes.size > 0 ||
    pendingImportMeta != null ||
    pendingRestoreVersionId != null ||
    Object.values(pendingEmptyDistrictsByLane).some(
      (a) => (a?.length ?? 0) > 0,
    );

  const laneLogisticChangesForStaged = useMemo(() => {
    const changes: Array<{ laneId: string; logisticId: number | null }> = [];
    for (const lane of lanes) {
      const nextId = lane.logisticId != null ? Number(lane.logisticId) : null;
      const prevId = laneLogisticBaselineRef.current.has(lane.id)
        ? laneLogisticBaselineRef.current.get(lane.id) ?? null
        : null;
      if (prevId !== nextId)
        changes.push({ laneId: lane.id, logisticId: nextId });
    }
    return changes;
  }, [lanes]);

  const stagedDeleteSig = useMemo(
    () =>
      Array.from(dirtyDeletes.values())
        .sort((a, b) => a - b)
        .join(","),
    [dirtyDeletes],
  );

  const dirtyMoveDistrictHints = useMemo(() => {
    const hints = new Map<number, string>();
    dirtyMoves.forEach((laneId, shopId) => {
      if (!Number.isFinite(shopId) || shopId <= 0) return;
      const baseDisp = shopDistrictBaselineRef.current.get(shopId);
      if (baseDisp === undefined) return;
      const lane = lanes.find((l) => l.id === laneId);
      const shop = lane?.shops.find((s) => s.id === shopId);
      if (!shop) return;
      const curDisp = toDistrictDisplayName(shop.districtReferenceRaw);
      if (curDisp !== baseDisp)
        hints.set(
          shopId,
          t("diff_staged_shopDistrictHint", {
            from: baseDisp,
            to: curDisp,
          }),
        );
    });
    return hints;
  }, [lanes, dirtyMoves, districtBaselineEpoch, stagedDeleteSig, t]);

  const stagedLogEntriesView = useMemo(
    () =>
      buildStagedBoardLogEntries({
        pendingRestoreVersionId,
        dirtyMoves,
        dirtyMoveDistrictHints,
        dirtyDeletes,
        stagedDeleteMeta: stagedDeleteMetaRef.current,
        pendingShopAdds,
        pendingNewLanes,
        pendingLogisticMasterAdds: pendingLogisticMasterAdds.map((p) => ({
          tempId: p.tempId,
          logisticName: p.logisticName,
          carPlate: p.carPlate,
        })),
        pendingLogisticMasterEdits: Array.from(
          pendingLogisticMasterEdits.entries(),
        ).map(([id, edit]) => {
          const prev = logisticRowsFromDb.find((r) => Number(r.id) === id);
          return {
            id,
            logisticName: edit.logisticName,
            carPlate: edit.carPlate,
            fromName: prev?.logisticName ?? "",
            fromPlate: prev?.carPlate ?? "",
          };
        }),
        pendingLogisticMasterDeletes: Array.from(
          pendingLogisticMasterDeletes,
        ).map((id) => {
          const prev = logisticRowsFromDb.find((r) => Number(r.id) === id);
          return {
            id,
            logisticName: String(prev?.logisticName ?? "").trim() || "—",
          };
        }),
        pendingImport: pendingImportMeta,
        laneLogisticChanges: laneLogisticChangesForStaged,
        lanes,
        pendingEmptyDistrictsByLane,
        logisticNameById,
        shopDistrictBaseline: new Map(shopDistrictBaselineRef.current),
        shopRowBaseline: new Map(shopRowBaselineRef.current),
      }),
    [
      pendingRestoreVersionId,
      dirtyMoves,
      dirtyMoveDistrictHints,
      dirtyDeletes,
      pendingShopAdds,
      pendingNewLanes,
      pendingLogisticMasterAdds,
      pendingLogisticMasterEdits,
      pendingLogisticMasterDeletes,
      pendingImportMeta,
      laneLogisticChangesForStaged,
      lanes,
      pendingEmptyDistrictsByLane,
      logisticNameById,
      logisticRowsFromDb,
      stagedDeleteSig,
      districtBaselineEpoch,
    ],
  );

  useEffect(() => {
    if (!hasUnsavedChanges) return;
    const message = t("nav_unsavedLeave");
    const currentUrl = window.location.href;
    const guardKey = `routeBoard:${Date.now()}:${Math.random()}`;
    let bypassNavigationGuard = false;
    let confirmedNavigation = false;
    let guardReleased = false;
    const guardState = {
      ...(window.history.state &&
      typeof window.history.state === "object" &&
      !Array.isArray(window.history.state)
        ? window.history.state
        : {}),
      __routeBoardUnsavedGuard: guardKey,
    };
    const isSameLocation = (url: string | URL | null | undefined) => {
      if (url == null) return true;
      const nextUrl = new URL(String(url), window.location.href);
      const here = new URL(currentUrl);
      return (
        nextUrl.origin === here.origin &&
        nextUrl.pathname === here.pathname &&
        nextUrl.search === here.search
      );
    };
    const confirmLeave = () => window.confirm(message);
    window.history.pushState(guardState, "", currentUrl);
    const handleBeforeUnload = (event: BeforeUnloadEvent) => {
      event.preventDefault();
      event.returnValue = message;
      return message;
    };
    const handleDocumentClick = (event: MouseEvent) => {
      if (
        event.defaultPrevented ||
        event.button !== 0 ||
        event.metaKey ||
        event.ctrlKey ||
        event.shiftKey ||
        event.altKey
      ) {
        return;
      }
      const target = event.target as HTMLElement | null;
      const anchor = target?.closest("a[href]") as HTMLAnchorElement | null;
      if (
        !anchor ||
        anchor.target === "_blank" ||
        anchor.hasAttribute("download")
      )
        return;
      if (isSameLocation(anchor.href)) return;
      if (!confirmLeave()) {
        event.preventDefault();
        event.stopPropagation();
        return;
      }
      releaseGuard();
    };
    const handlePopState = () => {
      if (confirmedNavigation) return;
      if (confirmLeave()) {
        releaseGuard();
        window.history.back();
        return;
      }
      bypassNavigationGuard = true;
      window.history.pushState(guardState, "", currentUrl);
      bypassNavigationGuard = false;
    };
    const originalPushState = window.history.pushState;
    const originalReplaceState = window.history.replaceState;
    function releaseGuard() {
      if (guardReleased) return;
      guardReleased = true;
      confirmedNavigation = true;
      window.removeEventListener("beforeunload", handleBeforeUnload);
      window.removeEventListener("popstate", handlePopState);
      document.removeEventListener("click", handleDocumentClick, true);
      window.history.pushState = originalPushState;
      window.history.replaceState = originalReplaceState;
    }
    window.history.pushState = function patchedPushState(
      data: any,
      unused: string,
      url?: string | URL | null,
    ) {
      if (!bypassNavigationGuard && !isSameLocation(url)) {
        if (!confirmLeave()) return;
        releaseGuard();
      }
      return originalPushState.call(window.history, data, unused, url);
    };
    window.history.replaceState = function patchedReplaceState(
      data: any,
      unused: string,
      url?: string | URL | null,
    ) {
      if (!bypassNavigationGuard && !isSameLocation(url)) {
        if (!confirmLeave()) return;
        releaseGuard();
      }
      return originalReplaceState.call(window.history, data, unused, url);
    };
    window.addEventListener("beforeunload", handleBeforeUnload);
    window.addEventListener("popstate", handlePopState);
    document.addEventListener("click", handleDocumentClick, true);
    return () => {
      if (guardReleased) return;
      window.removeEventListener("beforeunload", handleBeforeUnload);
      window.removeEventListener("popstate", handlePopState);
      document.removeEventListener("click", handleDocumentClick, true);
      window.history.pushState = originalPushState;
      window.history.replaceState = originalReplaceState;
      if (
        !confirmedNavigation &&
        window.location.href === currentUrl &&
        window.history.state?.__routeBoardUnsavedGuard === guardKey
      ) {
        window.history.back();
      }
    };
  }, [hasUnsavedChanges, t]);

  const handleSave = async () => {
    if (saveInFlightRef.current) return;
    saveInFlightRef.current = true;
    try {
      const pendingRestoreId =
        pendingRestoreVersionId != null &&
        Number.isFinite(Number(pendingRestoreVersionId)) &&
        Number(pendingRestoreVersionId) > 0
          ? Number(pendingRestoreVersionId)
          : null;

      const pendingLogisticMasterAddsSnapshot = [...pendingLogisticMasterAdds];
      const pendingLogisticMasterEditsSnapshot = new Map(
        pendingLogisticMasterEdits,
      );
      const pendingLogisticMasterDeletesSnapshot = new Set(
        pendingLogisticMasterDeletes,
      );
      const pendingImportFile = pendingImportFileRef.current;

      let laneLogisticChangesLive = getLaneLogisticChanges();
      const pendingSnapshot = [...pendingShopAdds];
      const pendingNewLanesSnapshot = [...pendingNewLanesRef.current];

      const hasPersistedWork =
        pendingSnapshot.length > 0 ||
        pendingNewLanesSnapshot.length > 0 ||
        dirtyMoves.size > 0 ||
        dirtyDeletes.size > 0 ||
        laneLogisticChangesLive.length > 0 ||
        pendingLogisticMasterAddsSnapshot.length > 0 ||
        pendingLogisticMasterEditsSnapshot.size > 0 ||
        pendingLogisticMasterDeletesSnapshot.size > 0 ||
        pendingImportFile != null ||
        pendingRestoreId != null;
      const hasPendingEmptyDistrictsOnly =
        !hasPersistedWork &&
        Object.values(pendingEmptyDistrictsByLane).some(
          (a) => (a?.length ?? 0) > 0,
        );

      if (!hasPersistedWork && !hasPendingEmptyDistrictsOnly) {
        setSaveResult({ ok: true, message: t("No changes") });
        return;
      }

      if (hasPendingEmptyDistrictsOnly) {
        setPendingEmptyDistrictsByLane({});
        setSaveResult({
          ok: true,
          message: t("save_clearedEmptyDistricts"),
        });
        return;
      }

      setSaving(true);
      setSaveResult(null);

      if (pendingRestoreId != null) {
        const hadRestoreWithOtherWork =
          pendingSnapshot.length > 0 ||
          pendingNewLanesSnapshot.length > 0 ||
          dirtyMoves.size > 0 ||
          dirtyDeletes.size > 0 ||
          laneLogisticChangesLive.length > 0 ||
          pendingLogisticMasterAddsSnapshot.length > 0 ||
          pendingLogisticMasterEditsSnapshot.size > 0 ||
          pendingLogisticMasterDeletesSnapshot.size > 0 ||
          pendingImportFile != null;
        if (hadRestoreWithOtherWork) {
          if (!window.confirm(t("confirm_restoreSaveWillDropStaging")))
            return;
        }
        try {
          await restoreTruckLaneVersionClient(pendingRestoreId);
          setPendingRestoreVersionId(null);
          setDirtyMoves(new Map());
          clearDirtyDeletesState();
          setPendingShopAdds([]);
          setPendingNewLanes([]);
          setPendingLogisticMasterAdds([]);
          setPendingLogisticMasterEdits(new Map());
          setPendingLogisticMasterDeletes(new Set());
          pendingImportFileRef.current = null;
          setPendingImportMeta(null);
          setPendingEmptyDistrictsByLane({});
          closeDistrictEdit();
          closeDepartureEdit();
          setSeqEditTarget(null);
          lanesNeedingRefreshOnSaveRef.current.clear();
          laneDisplayOverlayRef.current.clear();
          await loadLanes();
          setSaveResult({
            ok: true,
            message: hadRestoreWithOtherWork
              ? t("restore_appliedDroppedStaging")
              : t("restore_applied"),
          });
        } catch (e: any) {
          console.error("Restore snapshot failed:", e);
          setSaveResult({
            ok: false,
            message: e?.message ?? String(e) ?? t("diff_restoreFail"),
          });
        }
        return;
      }

      if (pendingLogisticMasterEditsSnapshot.size > 0) {
        for (const [id, req] of Array.from(
          pendingLogisticMasterEditsSnapshot.entries(),
        )) {
          if (pendingLogisticMasterDeletesSnapshot.has(id)) continue;
          await saveLogisticClient({ ...req, id });
        }
        setPendingLogisticMasterEdits(new Map());
        await reloadLogisticNamesFromDb();
      }

      if (pendingLogisticMasterDeletesSnapshot.size > 0) {
        for (const id of Array.from(pendingLogisticMasterDeletesSnapshot)) {
          await deleteLogisticClient(id);
        }
        setPendingLogisticMasterDeletes(new Set());
        await reloadLogisticNamesFromDb();
      }

      if (pendingLogisticMasterAddsSnapshot.length > 0) {
        const savedRows = await saveLogisticsBatchCreateClient(
          pendingLogisticMasterAddsSnapshot.map((p) => ({
            logisticName: p.logisticName,
            carPlate: p.carPlate,
            driverName: p.driverName,
            driverNumber: p.driverNumber,
          })),
        );
        if (
          !Array.isArray(savedRows) ||
          savedRows.length !== pendingLogisticMasterAddsSnapshot.length
        ) {
          throw new Error(
            `logistic save-batch: expected ${pendingLogisticMasterAddsSnapshot.length} rows, got ${savedRows?.length ?? 0}`,
          );
        }
        const byTemp = new Map<number, LogisticRow>();
        pendingLogisticMasterAddsSnapshot.forEach((p, i) => {
          byTemp.set(p.tempId, savedRows[i]!);
        });
        const remapped = lanesRef.current.map((lane) => {
          const lid = lane.logisticId != null ? Number(lane.logisticId) : null;
          if (lid == null || lid >= 0) return lane;
          const saved = byTemp.get(lid);
          if (!saved) return lane;
          const realId = Number(saved.id);
          if (!Number.isFinite(realId) || realId <= 0) return lane;
          return {
            ...lane,
            logisticId: realId,
            logisticsCompany: String(saved.logisticName ?? "").trim(),
            plate: String(saved.carPlate ?? "").trim(),
            driver: String(saved.driverName ?? "").trim(),
            phone:
              saved.driverNumber != null &&
              Number.isFinite(Number(saved.driverNumber))
                ? String(saved.driverNumber)
                : "",
          };
        });
        lanesRef.current = remapped;
        setLanes(remapped);
        setPendingLogisticMasterAdds([]);
        await reloadLogisticNamesFromDb();
        laneLogisticChangesLive = getLaneLogisticChanges();
      }

      const laneIdsFromPendingNewLanes = new Set<string>();
      if (pendingNewLanesSnapshot.length > 0) {
        for (const p of pendingNewLanesSnapshot) {
          const res = await createTruckWithoutShopClient(p.payload);
          assertMsgOk(res, t("api_fail_createLane"));
          laneIdsFromPendingNewLanes.add(p.laneKey);
        }
        setPendingNewLanes([]);
        if (laneIdsFromPendingNewLanes.size > 0) {
          await refreshLanesByIds(Array.from(laneIdsFromPendingNewLanes), {
            preserveStagedLogistics: true,
          });
        }
      }

      const laneIdsTouchedByCreate = new Set<string>();
      if (pendingSnapshot.length > 0) {
        for (const p of pendingSnapshot) {
          const lane = lanesRef.current.find((l) => l.id === p.laneId);
          if (!lane) continue;
          const sid = normalizeStoreId(lane.storeId);
          const remark =
            sid === "4F" &&
            lane.remark != null &&
            String(lane.remark).trim() !== ""
              ? String(lane.remark).trim()
              : null;
          const res = await createTruckClient({
            store_id: sid,
            truckLanceCode: lane.truckLanceCode,
            departureTime: parseTimeForBackend(lane.startTime),
            shopId: p.shopId,
            shopName: p.shopName,
            shopCode: p.shopCode,
            loadingSequence: p.loadingSequence,
            districtReference: null,
            remark,
          });
          assertMsgOk(res, t("api_fail_addShop"));
          laneIdsTouchedByCreate.add(p.laneId);
        }
        setPendingShopAdds([]);
        setLanes((prev) => stripDraftShopRows(prev));
        if (laneIdsTouchedByCreate.size > 0) {
          await refreshLanesByIds(Array.from(laneIdsTouchedByCreate), {
            preserveStagedLogistics: true,
          });
        }
      }

      // build a map shopId -> lane + shopCard（以 ref 最新列為準，含剛 refresh）
      const shopById = new Map<number, { lane: Lane; shop: ShopCard }>();
      for (const lane of lanesRef.current) {
        for (const s of lane.shops) {
          shopById.set(s.id, { lane, shop: s });
        }
      }

      const updates: SaveTruckLane[] = [];
      const deletes = Array.from(dirtyDeletes);
      const deleteSet = new Set<number>(deletes);
      for (const shopId of Array.from(dirtyMoves.keys())) {
        if (shopId <= 0) continue;
        if (deleteSet.has(shopId)) continue; // delete wins
        const current = shopById.get(shopId);
        if (!current) continue;

        const s = current.shop;
        updates.push({
          id: s.id,
          truckLanceCode: current.lane.truckLanceCode,
          departureTime: parseTimeForBackend(s.departureTime),
          loadingSequence: Number(s.loadingSequence ?? 0) || 0,
          districtReference:
            s.districtReferenceRaw != null &&
            String(s.districtReferenceRaw).trim() !== ""
              ? String(s.districtReferenceRaw).trim()
              : null,
          storeId: normalizeStoreId(s.storeId),
          remark:
            s.remark != null && String(s.remark).trim() !== ""
              ? String(s.remark)
              : null,
          /** 與目標車線桶一致；跨線拖曳時否則後端不會改 truck.logistic（Save 未帶 updateLogistic 時略過） */
          logisticId: current.lane.logisticId ?? null,
          updateLogistic: true,
        });
      }

      // lane logistic（整桶更新）：避免「只改物流商」或「跨線拖曳後 lane 內 logistic 不一致」冇落 DB
      const laneIdsToUpdateLogistic = new Set<string>([
        ...laneLogisticChangesLive.map((c) => c.laneId),
      ]);
      const laneLogisticUpdateReqs = Array.from(laneIdsToUpdateLogistic)
        .map((laneId) => lanesRef.current.find((l) => l.id === laneId))
        .filter((l): l is Lane => l != null)
        .map((lane) => ({
          truckLanceCode: lane.truckLanceCode,
          remark:
            lane.remark != null && String(lane.remark).trim() !== ""
              ? String(lane.remark).trim()
              : null,
          logisticId: lane.logisticId != null ? Number(lane.logisticId) : null,
        }));

      const results = await Promise.allSettled(
        updates.map(async (u) => {
          const res = await updateTruckLaneClient(u);
          assertMsgOk(res, t("api_fail_updateLane"));
          return res;
        }),
      );
      const deleteResults = await Promise.allSettled(
        deletes.map(async (id) => {
          const res = await deleteTruckLaneClient({ id });
          assertMsgOk(res, t("api_fail_deleteShop"));
          return res;
        }),
      );
      const logisticResults = await Promise.allSettled(
        laneLogisticUpdateReqs.map(async (req) => {
          const res = await updateLaneLogisticClient(req);
          assertMsgOk(res, t("api_fail_updateLogistics"));
          return res;
        }),
      );
      const failedIdx: number[] = [];
      results.forEach((r, idx) => {
        if (r.status === "rejected") failedIdx.push(idx);
      });
      const failedDeleteIds = new Set<number>();
      deleteResults.forEach((r, idx) => {
        if (r.status === "rejected") failedDeleteIds.add(deletes[idx]);
      });
      const failedLaneLogistics = new Set<string>();
      logisticResults.forEach((r, idx) => {
        if (r.status !== "rejected") return;
        const lane = lanesRef.current.find(
          (l) =>
            l.truckLanceCode === laneLogisticUpdateReqs[idx]?.truckLanceCode &&
            String(l.remark ?? "").trim() ===
              String(laneLogisticUpdateReqs[idx]?.remark ?? "").trim(),
        );
        if (lane) failedLaneLogistics.add(lane.id);
      });

      if (
        failedIdx.length === 0 &&
        failedDeleteIds.size === 0 &&
        failedLaneLogistics.size === 0
      ) {
        const hadPendingImport = pendingImportFile != null;
        if (hadPendingImport) {
          const importFd = new FormData();
          importFd.append("multipartFileList", pendingImportFile);
          const importRes = await importRouteLanesExcelClient(importFd);
          assertMsgOk(importRes, t("err_import"));
          pendingImportFileRef.current = null;
          setPendingImportMeta(null);
        }

        const dm = dirtyMoves;
        const laneIdsToRefresh = new Set<string>(
          lanesNeedingRefreshOnSaveRef.current,
        );
        lanesNeedingRefreshOnSaveRef.current.clear();
        for (const lid of Array.from(dm.values())) laneIdsToRefresh.add(lid);
        for (const lid of Array.from(laneIdsToUpdateLogistic))
          laneIdsToRefresh.add(lid);
        setDirtyMoves(new Map());
        clearDirtyDeletesState();
        setPendingEmptyDistrictsByLane({});
        for (const c of laneLogisticChangesLive) {
          laneLogisticBaselineRef.current.set(c.laneId, c.logisticId);
        }
        if (hadPendingImport) {
          await loadLanes();
        } else if (laneIdsToRefresh.size > 0) {
          await refreshLanesByIds(Array.from(laneIdsToRefresh), {
            preserveStagedLogistics: false,
          });
        }
        try {
          await createTruckLaneSnapshotClient({
            truckLanceCode: null,
            note: "board save",
          });
          await refreshLogVersions();
        } catch (snapErr: any) {
          console.warn("Auto snapshot after board save failed:", snapErr);
        }
        setSaveResult({ ok: true, message: t("Saved") });
        return;
      }

      const failedIds = new Set<number>(failedIdx.map((i) => updates[i].id));
      setDirtyMoves((prev) => {
        const next = new Map<number, string>();
        prev.forEach((laneId, shopId) => {
          if (failedIds.has(shopId)) next.set(shopId, laneId);
        });
        return next;
      });
      setDirtyDeletes((prev) => {
        const next = new Set<number>();
        prev.forEach((id) => {
          if (failedDeleteIds.has(id)) next.add(id);
        });
        return next;
      });

      const firstReason = (results[failedIdx[0]] as PromiseRejectedResult)
        ?.reason as any;
      const reasonText =
        firstReason?.message ??
        (firstReason != null ? String(firstReason) : "");
      setSaveResult({
        ok: false,
        message: `Saved ${updates.length - failedIdx.length}, Failed ${
          failedIdx.length
        }, Deleted ${deletes.length - failedDeleteIds.size}, DeleteFailed ${
          failedDeleteIds.size
        }, LaneLogisticFailed ${failedLaneLogistics.size}${
          reasonText ? `: ${reasonText}` : ""
        }`,
      });
    } catch (e: any) {
      console.error("Save failed:", e);
      setSaveResult({
        ok: false,
        message: e?.message ?? String(e) ?? t("Failed to save"),
      });
    } finally {
      setSaving(false);
      saveInFlightRef.current = false;
    }
  };

  const handleCancel = async () => {
    const pendingLaneKeys = new Set(
      pendingNewLanesRef.current.map((p) => p.laneKey),
    );
    setPendingShopAdds([]);
    setPendingNewLanes([]);
    setPendingLogisticMasterAdds([]);
    setPendingLogisticMasterEdits(new Map());
    setPendingLogisticMasterDeletes(new Set());
    pendingImportFileRef.current = null;
    setPendingImportMeta(null);
    pendingLaneKeys.forEach((id) =>
      laneLogisticBaselineRef.current.delete(id),
    );
    const snapshot = stripDraftShopRows(lanesRef.current).filter(
      (l) => !pendingLaneKeys.has(l.id),
    );
    const dm = dirtyMoves;
    const laneIdsToRefresh = new Set<string>(
      lanesNeedingRefreshOnSaveRef.current,
    );
    lanesNeedingRefreshOnSaveRef.current.clear();
    dm.forEach((lid) => laneIdsToRefresh.add(lid));
    snapshot.forEach((lane) => {
      if (lane.shops.some((s) => dm.has(s.id))) laneIdsToRefresh.add(lane.id);
      const currentLogisticId =
        lane.logisticId != null ? Number(lane.logisticId) : null;
      const baselineLogisticId = laneLogisticBaselineRef.current.has(lane.id)
        ? laneLogisticBaselineRef.current.get(lane.id) ?? null
        : null;
      if (currentLogisticId !== baselineLogisticId)
        laneIdsToRefresh.add(lane.id);
    });
    const restoredLogistics = snapshot.map((lane) => {
      const currentLogisticId =
        lane.logisticId != null ? Number(lane.logisticId) : null;
      const baselineLogisticId = laneLogisticBaselineRef.current.has(lane.id)
        ? laneLogisticBaselineRef.current.get(lane.id) ?? null
        : null;
      if (currentLogisticId === baselineLogisticId) return lane;
      const master =
        baselineLogisticId != null
          ? logisticRowsFromDb.find((r) => Number(r.id) === baselineLogisticId)
          : null;
      return {
        ...lane,
        logisticId: baselineLogisticId,
        logisticsCompany:
          baselineLogisticId == null
            ? ""
            : master?.logisticName?.trim() ||
              logisticNameById.get(baselineLogisticId) ||
              "",
        plate: master?.carPlate?.trim() || "",
        driver: master?.driverName?.trim() || "",
        phone:
          master?.driverNumber != null &&
          Number.isFinite(Number(master.driverNumber))
            ? String(master.driverNumber)
            : "",
      };
    });
    lanesRef.current = restoredLogistics;
    setLanes(restoredLogistics);
    setDirtyMoves(new Map());
    clearDirtyDeletesState();
    setPendingEmptyDistrictsByLane({});
    closeDistrictEdit();
    setSaveResult(null);
    setPendingRestoreVersionId(null);
    if (laneIdsToRefresh.size > 0) {
      await refreshLanesByIds(Array.from(laneIdsToRefresh), {
        preserveStagedLogistics: false,
      });
    } else {
      await loadLanes();
    }
  };

  const closeDepartureEdit = () => {
    setDepartureEditLaneId(null);
  };

  const openDepartureEdit = (lane: Lane) => {
    setDepartureEditDraft(toTimeInputValue(lane.startTime));
    setDepartureEditLaneId(lane.id);
  };

  const applyDepartureEdit = () => {
    if (!departureEditLaneId) return;
    const backendTime = parseTimeForBackend(departureEditDraft);
    const targetLane = lanesRef.current.find(
      (l) => l.id === departureEditLaneId,
    );
    const shopIds = targetLane?.shops.map((s) => s.id) ?? [];
    if (shopIds.length === 0) {
      closeDepartureEdit();
      return;
    }
    const nextLanes = lanesRef.current.map((lane) =>
      lane.id !== departureEditLaneId
        ? lane
        : {
            ...lane,
            startTime: backendTime,
            shops: lane.shops.map((s) => ({
              ...s,
              departureTime: backendTime,
            })),
          },
    );
    const wr = computeTruckLaneWarnings(lanesToWarningInputRows(nextLanes));
    if (wr.warnings.length > 0) {
      const ok = window.confirm(
        t("confirm_departureConflict", { count: wr.warnings.length }),
      );
      if (!ok) return;
    }
    setLanes(nextLanes);
    setDirtyMoves((prev) => {
      const next = new Map(prev);
      for (const id of shopIds) {
        if (id > 0) next.set(id, departureEditLaneId);
      }
      return next;
    });
    setSaveResult(null);
    closeDepartureEdit();
  };

  const closeSeqEdit = () => {
    setSeqEditTarget(null);
  };

  const openSeqEdit = (lane: Lane, shop: ShopCard) => {
    setSeqEditDraft(String(shop.loadingSequence ?? 0));
    setSeqEditTarget({ laneId: lane.id, shopId: shop.id });
  };

  const applySeqEdit = () => {
    if (!seqEditTarget) return;
    const n = Number(seqEditDraft);
    const seq = Number.isFinite(n) ? Math.trunc(n) : 0;
    setLanes((prev) =>
      prev.map((lane) =>
        lane.id !== seqEditTarget.laneId
          ? lane
          : {
              ...lane,
              shops: lane.shops.map((s) =>
                s.id === seqEditTarget.shopId
                  ? { ...s, loadingSequence: seq }
                  : s,
              ),
            },
      ),
    );
    setDirtyMoves((prev) => {
      const next = new Map(prev);
      next.set(seqEditTarget.shopId, seqEditTarget.laneId);
      return next;
    });
    setSaveResult(null);
    closeSeqEdit();
  };

  const normalizeChangedLines = (diff: any) => {
    const lines = (diff?.changed || []) as any[];
    return lines
      .map((line: any) => {
        const truckRowId = Number(line?.truckRowId);
        const shopCode = line?.shopCode != null ? String(line.shopCode) : null;
        const changes = Array.isArray(line?.changes)
          ? (line.changes as any[]).map((c) => ({
              field: c?.field != null ? String(c.field) : "",
              from: c?.from != null ? String(c.from) : null,
              to: c?.to != null ? String(c.to) : null,
            }))
          : [];
        return {
          truckRowId,
          shopCode,
          changes: changes.filter((c) => c.field),
          truckLanceCode:
            line?.truckLanceCode != null ? String(line.truckLanceCode) : null,
          remark: line?.remark != null ? String(line.remark) : null,
        };
      })
      .filter((x) => Number.isFinite(x.truckRowId) && x.truckRowId > 0);
  };

  const loadVersionDiff = async (versionId: number, list: any[]) => {
    const ticket = ++versionDiffReqSeq.current;
    setDiffLoading(true);
    setDiffError(null);
    try {
      const idx = list.findIndex((v) => Number(v?.id) === versionId);
      const olderId =
        idx >= 0 && idx < list.length - 1 ? Number(list[idx + 1]?.id) : null;
      if (olderId == null || !Number.isFinite(olderId) || olderId <= 0) {
        if (versionDiffReqSeq.current === ticket) {
          setDiffLines([]);
          setLogisticMasterDiffLines([]);
          setChangedShopIds(new Set());
        }
        return;
      }
      const diff = await diffTruckLaneVersionsClient(olderId, versionId);
      if (versionDiffReqSeq.current !== ticket) return;
      const normalized = normalizeChangedLines(diff);
      const logisticMaster = Array.isArray(diff?.logisticMasterChanges)
        ? (diff.logisticMasterChanges as LogisticMasterDiffLine[])
        : [];
      if (versionDiffReqSeq.current !== ticket) return;
      const ids = new Set<number>();
      normalized.forEach((line) => {
        const id = Number(line?.truckRowId);
        if (Number.isFinite(id) && id > 0) ids.add(id);
      });
      setChangedShopIds(ids);
      setDiffLines(normalized);
      setLogisticMasterDiffLines(logisticMaster);
    } catch (e: any) {
      if (versionDiffReqSeq.current === ticket) {
        setDiffError(e?.message ?? String(e) ?? t("diff_loadFail"));
        setChangedShopIds(new Set());
        setDiffLines([]);
        setLogisticMasterDiffLines([]);
      }
    } finally {
      if (versionDiffReqSeq.current === ticket) setDiffLoading(false);
    }
  };

  const refreshLogVersions = useCallback(async () => {
    try {
      const list = await listTruckLaneVersionsClient();
      const arr = Array.isArray(list) ? list : [];
      setLogVersions(arr);
      return arr;
    } catch (e) {
      console.warn("Failed to load truck lane versions:", e);
      return [];
    }
  }, []);

  useEffect(() => {
    void refreshLogVersions();
  }, [refreshLogVersions]);

  const openLogDialog = async () => {
    setLogDialogOpen(true);
    setDiffError(null);
    setChangedShopIds(new Set());
    setSelectedLogVersionId(null);
    setDiffLines([]);

    setLoadingVersions(true);
    try {
      const arr = await refreshLogVersions();
      const head = resolveHeadVersionId(arr);
      setSelectedLogVersionId(head);
      if (head != null) {
        await loadVersionDiff(head, arr);
      }
    } finally {
      setLoadingVersions(false);
    }
  };

  const closeLogDialog = () => {
    versionDiffReqSeq.current += 1;
    setLogDialogOpen(false);
    setDiffError(null);
    setChangedShopIds(new Set());
    setSelectedLogVersionId(null);
    setDiffLines([]);
    setLogisticMasterDiffLines([]);
    setVersionNoteDrafts({});
    setVersionNoteSaveError(null);
    setSavingVersionNoteId(null);
  };

  const saveVersionNote = useCallback(
    async (id: number, serverNote: string, currentValue: string) => {
      const trimmed = currentValue.trim();
      const prev = (serverNote ?? "").trim();
      if (trimmed === prev) {
        setVersionNoteDrafts((p) => {
          if (p[id] === undefined) return p;
          const n = { ...p };
          delete n[id];
          return n;
        });
        return;
      }
      setVersionNoteSaveError(null);
      setSavingVersionNoteId(id);
      try {
        const res = await updateTruckLaneVersionNoteClient(id, {
          note: trimmed === "" ? null : trimmed,
        });
        setLogVersions((list) =>
          list.map((x) =>
            Number(x?.id) === id ? { ...x, note: res.note } : x,
          ),
        );
        setVersionNoteDrafts((p) => {
          const n = { ...p };
          delete n[id];
          return n;
        });
      } catch (e: any) {
        setVersionNoteSaveError({
          id,
          message: e?.message ?? String(e) ?? t("versionNote_saveFail"),
        });
      } finally {
        setSavingVersionNoteId(null);
      }
    },
    [t],
  );

  const restoreVersion = (versionId: number) => {
    if (!Number.isFinite(versionId) || versionId <= 0) return;

    if (pendingRestoreVersionId === versionId) {
      setSaveResult({ ok: true, message: t("diff_restoreAlreadyPending") });
      closeLogDialog();
      return;
    }

    const hasOtherPending =
      dirtyMoves.size > 0 ||
      dirtyDeletes.size > 0 ||
      pendingShopAdds.length > 0 ||
      pendingNewLanes.length > 0 ||
      pendingLogisticMasterAdds.length > 0 ||
      pendingLogisticMasterEdits.size > 0 ||
      pendingLogisticMasterDeletes.size > 0 ||
      pendingImportMeta != null ||
      getLaneLogisticChanges().length > 0 ||
      Object.values(pendingEmptyDistrictsByLane).some(
        (a) => (a?.length ?? 0) > 0,
      );

    if (hasOtherPending && !window.confirm(t("confirm_restoreDiscardsEdits")))
      return;

    if (!hasOtherPending && pendingRestoreVersionId != null) {
      laneDisplayOverlayRef.current.clear();
      setPendingRestoreVersionId(versionId);
      setSaveResult({
        ok: true,
        message: t("diff_restoreScheduled", { versionId }),
      });
      closeLogDialog();
      return;
    }

    laneDisplayOverlayRef.current.clear();

    const pendingLaneKeys = new Set(
      pendingNewLanesRef.current.map((p) => p.laneKey),
    );
    pendingLaneKeys.forEach((id) => laneLogisticBaselineRef.current.delete(id));
    setPendingNewLanes([]);

    setLanes((prev) => {
      const next = stripDraftShopRows(prev).filter(
        (l) => !pendingLaneKeys.has(l.id),
      );
      lanesRef.current = next;
      return next;
    });
    setPendingShopAdds([]);
    setDirtyMoves(new Map());
    clearDirtyDeletesState();
    setPendingEmptyDistrictsByLane({});
    closeDistrictEdit();
    closeDepartureEdit();
    setSeqEditTarget(null);
    lanesNeedingRefreshOnSaveRef.current.clear();

    for (const lane of lanesRef.current) {
      laneLogisticBaselineRef.current.set(
        lane.id,
        lane.logisticId != null ? Number(lane.logisticId) : null,
      );
    }

    setPendingRestoreVersionId(versionId);
    setSaveResult({
      ok: true,
      message: t("diff_restoreScheduled", { versionId }),
    });
    closeLogDialog();
  };

  const handleExportVersionLogReportExcel = useCallback(async () => {
    if (routeExcelExportLockRef.current) return;
    if (hasUnsavedChanges) {
      setDiffError(t("diff_export_blockedError"));
      return;
    }
    routeExcelExportLockRef.current = true;
    if (selectedLogVersionId == null) {
      routeExcelExportLockRef.current = false;
      return;
    }
    const idx = logVersions.findIndex(
      (v) => Number(v?.id) === Number(selectedLogVersionId),
    );
    const olderId =
      idx >= 0 && idx < logVersions.length - 1
        ? Number(logVersions[idx + 1]?.id)
        : null;
    if (olderId == null || !Number.isFinite(olderId) || olderId <= 0) {
      setDiffError(t("diff_noOlderCompare"));
      routeExcelExportLockRef.current = false;
      return;
    }
    setRouteExcelBusy(true);
    setDiffError(null);
    try {
      const { base64, filename } =
        await exportTruckLaneVersionReportExcelClient(
          olderId,
          selectedLogVersionId,
        );
      downloadBase64Xlsx(base64, filename);
    } catch (e: any) {
      setDiffError(e?.message ?? String(e) ?? t("err_export"));
    } finally {
      setRouteExcelBusy(false);
      routeExcelExportLockRef.current = false;
    }
  }, [hasUnsavedChanges, logVersions, selectedLogVersionId, t]);

  const renderLaneHeader = (lane: Lane) => {
    const isDirty = lane.shops.some((s) => dirtyMoves.has(s.id));
    return (
      <Box
        sx={{
          px: 2,
          py: 2,
          bgcolor: "background.paper",
          borderBottom: "1px solid",
          borderColor: "divider",
          position: "sticky",
          top: 0,
          zIndex: 2,
        }}
      >
        <Stack
          direction="row"
          justifyContent="space-between"
          alignItems="flex-start"
          spacing={1.5}
        >
          <Stack spacing={1} sx={{ flex: 1, minWidth: 0, pr: 0.5 }}>
            <Box>
              <Typography
                component="h3"
                sx={{
                  m: 0,
                  fontWeight: 800,
                  color: "primary.main",
                  fontSize: "1rem",
                  lineHeight: 1.35,
                  letterSpacing: 0.01,
                  wordBreak: "break-word",
                  overflowWrap: "anywhere",
                }}
              >
                {lane.truckLanceCode}
              </Typography>
              <Stack
                direction="row"
                alignItems="center"
                spacing={0.75}
                flexWrap="wrap"
                useFlexGap
                sx={{ mt: 0.75 }}
              >
                {lane.remark != null && String(lane.remark).trim() !== "" && (
                  <Chip
                    label={lane.remark}
                    size="small"
                    variant="outlined"
                    color="secondary"
                    sx={{
                      height: 22,
                      maxWidth: "100%",
                      "& .MuiChip-label": {
                        px: 1,
                        fontWeight: 700,
                        fontSize: "0.7rem",
                        whiteSpace: "normal",
                        lineHeight: 1.2,
                      },
                    }}
                  />
                )}
                {isDirty && (
                  <Typography
                    variant="caption"
                    sx={{ color: "warning.main", fontWeight: 700 }}
                  >
                    {t("Changed")}
                  </Typography>
                )}
              </Stack>
            </Box>

            <Stack spacing={0.5} sx={{ color: "text.secondary" }}>
              <Stack direction="row" spacing={0.75} alignItems="center">
                <Box
                  sx={{
                    display: "flex",
                    color: "text.secondary",
                    flexShrink: 0,
                  }}
                >
                  <TruckIcon size={14} />
                </Box>
                <Typography variant="body2" sx={{ fontWeight: 600 }}>
                  {lane.logisticsCompany || t("Logistic")}
                </Typography>
              </Stack>
              <Stack
                direction="row"
                spacing={0.75}
                alignItems="center"
                flexWrap="wrap"
                useFlexGap
              >
                <Box sx={{ display: "flex", flexShrink: 0 }}>
                  <Users size={14} />
                </Box>
                <Typography variant="body2" component="span">
                  {lane.driver ? lane.driver : t("Driver")}
                </Typography>
                <Box
                  sx={{
                    display: "inline-flex",
                    alignItems: "center",
                    gap: 0.35,
                    ml: 0.5,
                  }}
                >
                  <Phone size={14} />
                  <Typography
                    variant="caption"
                    component="span"
                    sx={{ color: "primary.main", fontWeight: 700 }}
                  >
                    {lane.phone || "—"}
                  </Typography>
                </Box>
              </Stack>
            </Stack>
          </Stack>

          <Stack alignItems="flex-end" spacing={1} sx={{ flexShrink: 0 }}>
            <Typography
              variant="caption"
              sx={{
                fontFamily: "monospace",
                px: 1,
                py: 0.35,
                border: "1px solid",
                borderColor: "divider",
                borderRadius: 1,
                bgcolor: "grey.50",
                display: "block",
                textAlign: "center",
              }}
            >
              {lane.plate || t("Plate")}
            </Typography>
            <Stack direction="row" alignItems="center" spacing={0.25}>
              <Typography
                variant="caption"
                sx={{
                  fontWeight: 700,
                  bgcolor: "grey.100",
                  px: 1,
                  py: 0.35,
                  borderRadius: 1,
                  whiteSpace: "nowrap",
                }}
              >
                {t("Departure")}: {lane.startTime || "-"}
              </Typography>
              <Tooltip
                title={
                  lane.shops.length === 0
                    ? t("departureTooltipNeedShops")
                    : t("departureTooltipEditSave")
                }
              >
                <span>
                  <IconButton
                    size="small"
                    onClick={(e) => {
                      e.stopPropagation();
                      openDepartureEdit(lane);
                    }}
                    disabled={loading || lane.shops.length === 0}
                    aria-label={t("departureEditAria")}
                  >
                    <Pencil size={14} />
                  </IconButton>
                </span>
              </Tooltip>
            </Stack>
            <Typography
              variant="caption"
              sx={{ color: "text.secondary", fontWeight: 600 }}
            >
              {t("Shops")}: {lane.shops.length}
            </Typography>
          </Stack>
        </Stack>
      </Box>
    );
  };

  return (
    <Box
      sx={{
        flex: 1,
        minHeight: 0,
        height: "100%",
        width: "100%",
        display: "flex",
        flexDirection: "column",
        bgcolor: "grey.50",
        overflow: "hidden",
      }}
    >
      {/* Header (match your reference code structure) */}
      <Box
        sx={{
          bgcolor: "background.paper",
          borderBottom: "1px solid",
          borderColor: "divider",
          px: 2,
          py: 1.5,
          position: "sticky",
          top: 0,
          zIndex: 10,
        }}
      >
        <Stack
          direction="row"
          justifyContent="space-between"
          alignItems="center"
          spacing={2}
        >
          <Box>
            <Typography variant="h6" sx={{ fontWeight: 800 }}>
              {t("pageTitle")}
            </Typography>
            <Typography variant="caption" color="text.secondary">
              {t("Current version")}: {displayedVersionLabel}
            </Typography>
          </Box>

          <Stack
            direction="row"
            spacing={1}
            alignItems="center"
            useFlexGap
            flexWrap="wrap"
            justifyContent="flex-end"
            sx={{ minWidth: 0, rowGap: 0.5 }}
          >
            <input
              ref={importRouteFileInputRef}
              type="file"
              accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
              style={{ display: "none" }}
              onChange={handleImportRouteExcelChange}
            />
            <ButtonGroup
              variant="outlined"
              size="small"
              sx={{
                flexShrink: 0,
                "& .MuiButtonGroup-grouped": {
                  minWidth: 0,
                  px: 1.5,
                  fontWeight: 700,
                },
              }}
            >
              <Button
                startIcon={<Upload size={16} />}
                disabled={loading || routeExcelBusy}
                onClick={() => importRouteFileInputRef.current?.click()}
              >
                {t("importRoutes")}
              </Button>
              <Button
                startIcon={<Download size={16} />}
                disabled={loading || routeExcelBusy}
                onClick={() => void handleExportSelectedLanesExcel()}
              >
                {t("exportRoutes")}
              </Button>
            </ButtonGroup>
            <Button
              variant="outlined"
              size="small"
              startIcon={<FileText size={16} />}
              disabled={loading || routeExcelBusy}
              onClick={() => void handleExportRouteReportExcel()}
            >
              {t("routeReport")}
            </Button>
            <ButtonGroup
              variant="outlined"
              size="small"
              sx={{
                flexShrink: 0,
                "& .MuiButtonGroup-grouped": {
                  minWidth: 0,
                  px: 1.5,
                  fontWeight: 700,
                },
              }}
            >
              <Button
                startIcon={<Clock size={16} />}
                disabled={loading || lanes.length === 0}
                onClick={() => setScheduleModalOpen(true)}
              >
                {t("btn_scheduleChange")}
              </Button>
              <Tooltip
                title={
                  failedScheduleCount > 0
                    ? t("schedule_log_failed_hint", { count: failedScheduleCount })
                    : ""
                }
              >
                <Button
                  startIcon={
                    <Badge
                      color="error"
                      badgeContent={failedScheduleCount}
                      invisible={failedScheduleCount === 0}
                      sx={{
                        "& .MuiBadge-badge": {
                          fontSize: 10,
                          height: 16,
                          minWidth: 16,
                          padding: "0 4px",
                        },
                      }}
                    >
                      <FileText size={16} />
                    </Badge>
                  }
                  disabled={loading}
                  {...(failedScheduleCount > 0 ? { color: "error" as const } : {})}
                  onClick={() => setScheduleHistoryOpen(true)}
                >
                  {t("btn_scheduleHistory")}
                </Button>
              </Tooltip>
            </ButtonGroup>
            <Tooltip
              title={
                laneWarnCount > 0
                  ? t("mtmsRouteWarn_tooltipHas", { count: laneWarnCount })
                  : t("mtmsRouteWarn_tooltipNone")
              }
            >
              <Box
                component="span"
                sx={{ flexShrink: 0, display: "inline-flex" }}
              >
                <IconButton
                  size="small"
                  onClick={() => setLaneWarnDrawerOpen(true)}
                  aria-label={t("mtmsRouteWarn_title")}
                  sx={{
                    color:
                      laneWarnCount > 0 ? "warning.main" : "text.secondary",
                  }}
                >
                  <Badge
                    color="error"
                    badgeContent={laneWarnCount}
                    invisible={laneWarnCount === 0}
                  >
                    <Bell size={20} />
                  </Badge>
                </IconButton>
              </Box>
            </Tooltip>
            <Divider flexItem orientation="vertical" />
            <Button
              variant="contained"
              size="small"
              startIcon={<Save size={16} />}
              onClick={handleSave}
              disabled={saving || !hasUnsavedChanges}
              title={
                saving || hasUnsavedChanges
                  ? undefined
                  : t("saveDisabledTooltip")
              }
            >
              {saving ? t("Submitting...") : t("saveChanges")}
            </Button>
            <Button
              variant="outlined"
              size="small"
              startIcon={<X size={16} />}
              onClick={handleCancel}
              disabled={saving}
            >
              {t("cancel")}
            </Button>
          </Stack>
        </Stack>
        {saveResult && (
          <Alert sx={{ mt: 1 }} severity={saveResult.ok ? "success" : "error"}>
            {saveResult.message}
          </Alert>
        )}
      </Box>

      <Drawer
        anchor="right"
        open={laneWarnDrawerOpen}
        onClose={() => setLaneWarnDrawerOpen(false)}
        PaperProps={{
          sx: {
            width: { xs: "100%", sm: 440 },
            p: 0,
            display: "flex",
            flexDirection: "column",
            maxHeight: "100vh",
          },
        }}
      >
        <Box sx={{ p: 2, borderBottom: 1, borderColor: "divider" }}>
          <Stack
            direction="row"
            alignItems="center"
            justifyContent="space-between"
            spacing={1}
          >
            <Typography variant="h6" sx={{ fontWeight: 800 }}>
              {t("mtmsRouteWarn_title")}
            </Typography>
            <IconButton
              size="small"
              aria-label={t("drawerClose")}
              onClick={() => setLaneWarnDrawerOpen(false)}
            >
              <X size={18} />
            </IconButton>
          </Stack>
          <Stack direction="row" spacing={1} sx={{ mt: 1.5 }} flexWrap="wrap">
            <Button
              size="small"
              variant="outlined"
              disabled={loading}
              onClick={() => void loadLanes()}
            >
              {loading ? t("mtmsRouteWarn_refreshing") : t("mtmsRouteWarn_refresh")}
            </Button>
            <Button
              size="small"
              variant="outlined"
              disabled={laneWarningsMemo.warnings.length === 0}
              onClick={async () => {
                const text = formatLaneWarningsClipboard(
                  laneWarningsMemo.warnings,
                  t,
                );
                try {
                  await navigator.clipboard.writeText(text);
                } catch {
                  console.warn("clipboard write failed");
                }
              }}
            >
              {t("mtmsRouteWarn_copyAll")}
            </Button>
          </Stack>
        </Box>
        <Box sx={{ p: 1.5, overflow: "auto", flex: 1, minHeight: 0 }}>
          {laneWarningsMemo.weekdayParseFailures.length > 0 && (
            <Alert severity="info" sx={{ mb: 1 }}>
              {t("mtmsRouteWarn_parseHint", {
                count: laneWarningsMemo.weekdayParseFailures.length,
              })}
            </Alert>
          )}
          {laneWarningsMemo.warnings.length === 0 ? (
            <Typography variant="body2" color="text.secondary">
              {t("mtmsRouteWarn_empty")}
            </Typography>
          ) : (
            laneWarningsMemo.warnings.map((w, i) => {
              const shopHeadline = [w.shopCode, w.shopDisplayName]
                .map((s) => String(s ?? "").trim())
                .filter(Boolean)
                .join(" ");
              const expanded = laneWarnExpandedIdx === i;
              return (
                <Card
                  key={`${w.rule}-${w.shopCode}-${w.triggerValue}-${i}`}
                  variant="outlined"
                  sx={{ mb: 1 }}
                >
                  <CardContent
                    sx={{ py: 1, px: 1.5, "&:last-child": { pb: 1 } }}
                  >
                    <Stack
                      direction="row"
                      alignItems="flex-start"
                      spacing={0.5}
                    >
                      <Box
                        role="button"
                        tabIndex={0}
                        onClick={() => selectLanesFromWarning(w)}
                        onKeyDown={(e) => {
                          if (e.key === "Enter" || e.key === " ") {
                            e.preventDefault();
                            selectLanesFromWarning(w);
                          }
                        }}
                        sx={{ flex: 1, minWidth: 0, cursor: "pointer" }}
                      >
                        <Typography variant="subtitle1" fontWeight={800}>
                          {t("mtmsRouteWarn_shop")}: {shopHeadline || "—"}
                        </Typography>
                        <Typography variant="body2" color="text.secondary">
                          {formatWarningSummary(w, t)}
                        </Typography>
                      </Box>
                      <IconButton
                        size="small"
                        aria-label={expanded ? t("warnCollapse") : t("warnExpand")}
                        onClick={(e) => {
                          e.stopPropagation();
                          setLaneWarnExpandedIdx((prev) =>
                            prev === i ? null : i,
                          );
                        }}
                      >
                        <ExpandMoreIcon
                          sx={{
                            transform: expanded
                              ? "rotate(180deg)"
                              : "none",
                            transition: "transform 0.2s",
                          }}
                        />
                      </IconButton>
                    </Stack>
                  </CardContent>
                  <Collapse in={expanded} timeout="auto" unmountOnExit>
                    <Box sx={{ px: 1.5, pb: 1.5 }}>
                      <Stack spacing={1}>
                        {w.lanes.map((L) => (
                          <Paper
                            key={L.laneKey}
                            variant="outlined"
                            sx={{ p: 1 }}
                          >
                            <Typography
                              variant="body2"
                              sx={{ fontWeight: 600 }}
                            >
                              {L.truckLanceCode}
                              {L.laneRemark ? ` · ${L.laneRemark}` : ""}
                            </Typography>
                            <Typography variant="caption" display="block">
                              {formatLaneWarningDetail(L, t)}
                            </Typography>
                          </Paper>
                        ))}
                      </Stack>
                    </Box>
                  </Collapse>
                </Card>
              );
            })
          )}
        </Box>
      </Drawer>

      <Snackbar
        open={laneWarnSnackbar != null}
        autoHideDuration={9000}
        onClose={() => setLaneWarnSnackbar(null)}
        anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
      >
        <Alert
          onClose={() => setLaneWarnSnackbar(null)}
          severity="warning"
          variant="filled"
          sx={{ width: "100%" }}
        >
          {laneWarnSnackbar}
        </Alert>
      </Snackbar>

      <Box sx={{ flex: 1, minHeight: 0, display: "flex" }}>
        {scheduleModalOpen ? (
          <ScheduleChangeModal
            open
            onClose={() => setScheduleModalOpen(false)}
            lanes={scheduleLaneOptions}
            shops={scheduleShopRows}
            allShopsMaster={allShopsMaster}
            pendingTruckRowIds={pendingScheduleShopIds}
            onConfirmManual={handleScheduleConfirmManual}
            onAfterScheduleChange={async () => {
              await refreshScheduleIndicators();
            }}
          />
        ) : null}
        {scheduleHistoryOpen ? (
          <ScheduleTaskHistoryModal
            open
            onClose={() => setScheduleHistoryOpen(false)}
            lanes={scheduleLaneOptions}
            onAfterChange={async () => {
              await refreshScheduleIndicators();
              await loadLanes();
            }}
          />
        ) : null}

        <Dialog
          open={logDialogOpen}
          onClose={closeLogDialog}
          maxWidth="lg"
          fullWidth
          PaperProps={{ sx: { height: "min(85vh, 880px)", maxHeight: "90vh" } }}
        >
          <DialogTitle
            sx={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              py: 1.5,
              pl: 2,
              pr: 1,
            }}
          >
            <Typography variant="h6" sx={{ fontWeight: 800, textAlign: "left" }}>
              {t("versionLogDialogTitle")}
            </Typography>
            <IconButton
              aria-label={t("drawerClose")}
              onClick={closeLogDialog}
              size="small"
              disabled={saving}
            >
              <X size={20} />
            </IconButton>
          </DialogTitle>
          <DialogContent
            dividers
            sx={{
              p: 0,
              display: "flex",
              flexDirection: "column",
              minHeight: 0,
            }}
          >
            <Stack direction="row" sx={{ flex: 1, minHeight: 0 }}>
              {/* 左：版本列表 */}
              <Box
                sx={{
                  width: "50%",
                  borderRight: 1,
                  borderColor: "divider",
                  bgcolor: "grey.50",
                  display: "flex",
                  flexDirection: "column",
                  minHeight: 0,
                }}
              >
                <Box
                  sx={{
                    px: 2,
                    py: 1.5,
                    borderBottom: 1,
                    borderColor: "divider",
                    bgcolor: "background.paper",
                  }}
                >
                  <Stack
                    direction="row"
                    alignItems="center"
                    justifyContent="space-between"
                    spacing={1}
                  >
                    <Stack direction="row" alignItems="baseline" spacing={1}>
                      <Typography
                        variant="overline"
                        sx={{ fontWeight: 800, color: "text.secondary" }}
                      >
                        {t("version_ui_historyTitle")}
                      </Typography>
                      {!loadingVersions && logVersions.length > 0 && (
                        <Typography variant="caption" color="text.secondary">
                          {filteredLogVersions.length}/{logVersions.length}
                        </Typography>
                      )}
                    </Stack>
                    <IconButton
                      size="small"
                      onClick={(e) => setVersionFilterAnchor(e.currentTarget)}
                      disabled={loadingVersions || logVersions.length === 0}
                      aria-label={t("version_ui_filterAria")}
                    >
                      <FilterListIcon
                        fontSize="small"
                        color={versionFilterActive ? "primary" : "inherit"}
                      />
                    </IconButton>
                  </Stack>
                </Box>
                <Box sx={{ flex: 1, overflow: "auto", p: 2 }}>
                  {loadingVersions ? (
                    <Box
                      sx={{ display: "flex", justifyContent: "center", py: 4 }}
                    >
                      <CircularProgress size={28} />
                    </Box>
                  ) : (
                    <List
                      disablePadding
                      component="div"
                      role="list"
                      aria-label={t("version_ui_listAria")}
                    >
                      {filteredLogVersions.map((v) => {
                        const id = Number(v?.id);
                        const created = String(v?.created || "");
                        const { date, time } = splitVersionCreated(created);
                        const note = v?.note != null ? String(v.note) : "";
                        const isSel = selectedLogVersionId === id;
                        const isHead =
                          headVersionId != null && id === headVersionId;
                        return (
                          <ListItemButton
                            key={id}
                            selected={isSel}
                            onClick={() => {
                              setSelectedLogVersionId(id);
                              void loadVersionDiff(id, logVersions);
                            }}
                            sx={{
                              p: 2,
                              mb: 1.5,
                              flexDirection: "column",
                              alignItems: "stretch",
                              border: 2,
                              borderColor: isSel
                                ? "primary.main"
                                : "transparent",
                              bgcolor: "background.paper",
                              borderRadius: 2,
                              boxShadow: isSel ? 2 : 1,
                              outline: isSel ? "4px solid" : "none",
                              outlineColor: isSel
                                ? "primary.light"
                                : "transparent",
                              transition:
                                "box-shadow 0.15s, border-color 0.15s",
                              "&.Mui-selected": {
                                bgcolor: "background.paper",
                              },
                              "&:hover": {
                                borderColor: isSel ? "primary.main" : "divider",
                              },
                            }}
                          >
                            <Stack
                              direction="row"
                              justifyContent="space-between"
                              alignItems="flex-start"
                              sx={{ mb: 1 }}
                            >
                              <Typography
                                variant="caption"
                                sx={{
                                  fontWeight: 800,
                                  color: "primary.main",
                                  letterSpacing: 0.5,
                                }}
                              >
                                {date}
                                {time ? ` ${time}` : ""}
                              </Typography>
                              {isHead && (
                                <Typography
                                  component="span"
                                  variant="caption"
                                  sx={{
                                    px: 1,
                                    py: 0.25,
                                    bgcolor: "success.main",
                                    color: "success.contrastText",
                                    fontWeight: 800,
                                    borderRadius: 1,
                                  }}
                                >
                                  {t("version_ui_snapshotBadge")}
                                </Typography>
                              )}
                            </Stack>
                            <Typography
                              variant="subtitle2"
                              sx={{ fontWeight: 900, mb: 0.5 }}
                            >
                              {t("version_ui_id", { id })}
                            </Typography>
                            {(() => {
                              const actor = resolveVersionActor(v ?? {});
                              return actor ? (
                                <Typography
                                  variant="caption"
                                  color="text.secondary"
                                  sx={{ display: "block", mb: 0.5 }}
                                >
                                  {t("version_ui_editedBy", {
                                    name: actor,
                                  })}
                                </Typography>
                              ) : null;
                            })()}
                            <Box
                              onMouseDown={(e) => e.stopPropagation()}
                              onClick={(e) => e.stopPropagation()}
                              sx={{ mb: 1 }}
                            >
                              <TextField
                                size="small"
                                fullWidth
                                multiline
                                maxRows={3}
                                placeholder={t("version_note_placeholder")}
                                disabled={savingVersionNoteId === id}
                                value={
                                  Object.prototype.hasOwnProperty.call(
                                    versionNoteDrafts,
                                    id,
                                  )
                                    ? versionNoteDrafts[id] ?? ""
                                    : note
                                }
                                onChange={(e) =>
                                  setVersionNoteDrafts((p) => ({
                                    ...p,
                                    [id]: e.target.value,
                                  }))
                                }
                                onBlur={(e) => {
                                  void saveVersionNote(
                                    id,
                                    note,
                                    e.target.value,
                                  );
                                }}
                                inputProps={{ maxLength: 500 }}
                                helperText={
                                  savingVersionNoteId === id
                                    ? t("version_note_saving")
                                    : versionNoteSaveError?.id === id
                                      ? versionNoteSaveError.message
                                      : ""
                                }
                                FormHelperTextProps={{
                                  sx:
                                    versionNoteSaveError?.id === id
                                      ? { mt: 0.25, color: "error.main" }
                                      : { mt: 0.25 },
                                }}
                                onFocus={() => setVersionNoteSaveError(null)}
                                sx={{
                                  "& .MuiInputBase-input": {
                                    fontSize: "0.8125rem",
                                    fontStyle: "italic",
                                  },
                                }}
                              />
                            </Box>
                          </ListItemButton>
                        );
                      })}
                      {!loadingVersions &&
                        logVersions.length > 0 &&
                        filteredLogVersions.length === 0 && (
                          <Typography variant="body2" color="text.secondary">
                            {t("version_empty_filtered")}
                          </Typography>
                        )}
                      {logVersions.length === 0 && (
                        <Typography variant="body2" color="text.secondary">
                          {t("version_empty_list")}
                        </Typography>
                      )}
                    </List>
                  )}
                </Box>

                <Popover
                  open={Boolean(versionFilterAnchor)}
                  anchorEl={versionFilterAnchor}
                  onClose={() => setVersionFilterAnchor(null)}
                  anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
                  transformOrigin={{ vertical: "top", horizontal: "right" }}
                >
                  <Box sx={{ p: 2, width: 340 }}>
                    <Stack spacing={1.5}>
                      <TextField
                        size="small"
                        label={t("version_search_label")}
                        placeholder={t("version_search_placeholder")}
                        value={versionFilterQuery}
                        onChange={(e) => setVersionFilterQuery(e.target.value)}
                        autoFocus
                      />
                      <TextField
                        size="small"
                        label={t("version_date_label")}
                        type="date"
                        value={versionFilterDate}
                        onChange={(e) => setVersionFilterDate(e.target.value)}
                        InputLabelProps={{ shrink: true }}
                        fullWidth
                      />
                      <Stack direction="row" justifyContent="space-between">
                        <Button
                          size="small"
                          onClick={() => {
                            setVersionFilterQuery("");
                            setVersionFilterDate("");
                          }}
                          disabled={!versionFilterActive}
                        >
                          {t("filter_clear")}
                        </Button>
                        <Button
                          size="small"
                          variant="contained"
                          onClick={() => setVersionFilterAnchor(null)}
                        >
                          {t("filter_apply")}
                        </Button>
                      </Stack>
                    </Stack>
                  </Box>
                </Popover>
              </Box>

              {/* 右：異動詳情 */}
              <Box
                sx={{
                  width: "50%",
                  display: "flex",
                  flexDirection: "column",
                  minHeight: 0,
                  bgcolor: "background.paper",
                }}
              >
                <Box
                  sx={{
                    flex: 1,
                    overflow: "auto",
                    p: 2,
                    display: "flex",
                    flexDirection: "column",
                  }}
                >
                  {diffError && (
                    <Alert severity="error" sx={{ mb: 2 }}>
                      {diffError}
                    </Alert>
                  )}

                  {selectedLogVersionId == null && !loadingVersions && (
                    <Stack
                      alignItems="center"
                      justifyContent="center"
                      sx={{ flex: 1, color: "text.disabled", py: 6 }}
                    >
                      <History size={48} strokeWidth={1} opacity={0.25} />
                      <Typography variant="body2" sx={{ mt: 1 }}>
                        {t("diff_clickLeft")}
                      </Typography>
                    </Stack>
                  )}

                  {selectedLogVersionId != null && (
                    <>
                      {(() => {
                        const idx = logVersions.findIndex(
                          (v) => Number(v?.id) === selectedLogVersionId,
                        );
                        const hasOlder =
                          idx >= 0 && idx < logVersions.length - 1;
                        const sel = logVersions[idx];
                        const note = sel?.note != null ? String(sel.note) : "";
                        const editor = sel ? resolveVersionActor(sel) : null;

                        return (
                          <>
                            {!hasOlder && !diffLoading && (
                              <Alert severity="info" sx={{ mb: 2 }}>
                                {t("diff_oldestSnapshot")}
                              </Alert>
                            )}

                            <Paper
                              variant="outlined"
                              sx={{
                                p: 2,
                                mb: 2,
                                bgcolor: (theme) =>
                                  alpha(theme.palette.primary.main, 0.08),
                                borderColor: "primary.light",
                                flexShrink: 0,
                              }}
                            >
                              <Stack
                                direction="row"
                                spacing={1}
                                alignItems="center"
                                sx={{ mb: 1, color: "primary.dark" }}
                              >
                                <Info size={18} />
                                <Typography
                                  variant="subtitle2"
                                  sx={{ fontWeight: 800 }}
                                >
                                  {t("diff_summary_title")}
                                </Typography>
                                <Box sx={{ flex: 1 }} />
                                <Tooltip
                                  title={
                                    hasUnsavedChanges
                                      ? t("diff_export_blockedTooltip")
                                      : ""
                                  }
                                >
                                  <span>
                                    <Button
                                      variant="outlined"
                                      size="small"
                                      startIcon={<FileText size={16} />}
                                      disabled={
                                        saving ||
                                        routeExcelBusy ||
                                        hasUnsavedChanges ||
                                        selectedLogVersionId == null ||
                                        logVersions.length < 2
                                      }
                                      onClick={() =>
                                        void handleExportVersionLogReportExcel()
                                      }
                                    >
                                      {t("diff_export_reportBtn")}
                                    </Button>
                                  </span>
                                </Tooltip>
                              </Stack>
                              {editor != null && (
                                <Typography
                                  variant="caption"
                                  color="text.secondary"
                                  sx={{ display: "block", mb: 1 }}
                                >
                                  {t("version_ui_editedBy", { name: editor })}
                                </Typography>
                              )}
                              <Typography
                                variant="body2"
                                sx={{ color: "text.primary", mb: 2 }}
                              >
                                {note || "—"}
                              </Typography>
                              <Stack direction="row" spacing={1}>
                                <Box
                                  flex={1}
                                  sx={{
                                    bgcolor: "background.paper",
                                    borderRadius: 1,
                                    p: 1,
                                    textAlign: "center",
                                    border: 1,
                                    borderColor: "divider",
                                  }}
                                >
                                  <Typography
                                    variant="caption"
                                    color="text.secondary"
                                  >
                                    {t("diff_summary_added")}
                                  </Typography>
                                  <Typography
                                    variant="h6"
                                    sx={{
                                      fontWeight: 900,
                                      color: "success.main",
                                    }}
                                  >
                                    {diffLoading
                                      ? t("diff_loadingEllipsis")
                                      : versionRowSummary.added}
                                  </Typography>
                                </Box>
                                <Box
                                  flex={1}
                                  sx={{
                                    bgcolor: "background.paper",
                                    borderRadius: 1,
                                    p: 1,
                                    textAlign: "center",
                                    border: 1,
                                    borderColor: "divider",
                                  }}
                                >
                                  <Typography
                                    variant="caption"
                                    color="text.secondary"
                                  >
                                    {t("diff_summary_moved")}
                                  </Typography>
                                  <Typography
                                    variant="h6"
                                    sx={{
                                      fontWeight: 900,
                                      color: "warning.main",
                                    }}
                                  >
                                    {diffLoading
                                      ? t("diff_loadingEllipsis")
                                      : versionRowSummary.moved}
                                  </Typography>
                                </Box>
                                <Box
                                  flex={1}
                                  sx={{
                                    bgcolor: "background.paper",
                                    borderRadius: 1,
                                    p: 1,
                                    textAlign: "center",
                                    border: 1,
                                    borderColor: "divider",
                                  }}
                                >
                                  <Typography
                                    variant="caption"
                                    color="text.secondary"
                                  >
                                    {t("diff_summary_deleted")}
                                  </Typography>
                                  <Typography
                                    variant="h6"
                                    sx={{
                                      fontWeight: 900,
                                      color: "error.main",
                                    }}
                                  >
                                    {diffLoading
                                      ? t("diff_loadingEllipsis")
                                      : versionRowSummary.deleted}
                                  </Typography>
                                </Box>
                                <Box
                                  flex={1}
                                  sx={{
                                    bgcolor: "background.paper",
                                    borderRadius: 1,
                                    p: 1,
                                    textAlign: "center",
                                    border: 1,
                                    borderColor: "divider",
                                  }}
                                >
                                  <Typography
                                    variant="caption"
                                    color="text.secondary"
                                  >
                                    {t("diff_summary_fieldChange")}
                                  </Typography>
                                  <Typography
                                    variant="h6"
                                    sx={{
                                      fontWeight: 900,
                                      color: "text.secondary",
                                    }}
                                  >
                                    {diffLoading
                                      ? t("diff_loadingEllipsis")
                                      : versionRowSummary.fieldChanges}
                                  </Typography>
                                </Box>
                              </Stack>
                              {hasOlder && stagedLogEntriesView.length > 0 && (
                                <Typography
                                  variant="caption"
                                  color="text.secondary"
                                  sx={{ display: "block", mt: 1 }}
                                >
                                  {t("diff_staged_boardPendingLine", {
                                    count: stagedLogEntriesView.length,
                                  })}
                                </Typography>
                              )}
                            </Paper>

                            <Stack
                              direction="row"
                              alignItems="baseline"
                              justifyContent="space-between"
                              spacing={1}
                              sx={{ mb: 1.5, flexShrink: 0 }}
                            >
                              <Typography
                                variant="overline"
                                sx={{
                                  fontWeight: 800,
                                  color: "text.secondary",
                                }}
                              >
                                {t("diff_shopList_title")}
                              </Typography>
                              {changedShopIds.size > 0 && (
                                <Typography
                                  variant="caption"
                                  color="text.secondary"
                                >
                                  {t("diff_markedCount", {
                                    count: changedShopIds.size,
                                  })}
                                </Typography>
                              )}
                            </Stack>

                            <Box
                              sx={{ flex: 1, minHeight: 0, overflow: "auto" }}
                            >
                              <Stack spacing={1.5} sx={{ pb: 2 }}>
                                {stagedLogEntriesView.length > 0 && (
                                  <>
                                    <Typography
                                      variant="overline"
                                      sx={{
                                        fontWeight: 800,
                                        color: "warning.dark",
                                        display: "block",
                                      }}
                                    >
                                      {t("diff_staged_section_title")}
                                    </Typography>
                                    <Typography
                                      variant="caption"
                                      color="text.secondary"
                                      sx={{ display: "block", mb: 0.5 }}
                                    >
                                      {t("diff_staged_section_subtitle")}
                                    </Typography>
                                    <Stack spacing={1.25}>
                                      {stagedLogEntriesView.map((entry) => {
                                        if (entry.kind === "restore") {
                                          return (
                                            <Alert
                                              key={entry.key}
                                              severity="info"
                                              variant="outlined"
                                              sx={{ py: 0.75 }}
                                            >
                                              {t("diff_staged_restoreScheduled", {
                                                versionId: entry.versionId,
                                              })}
                                            </Alert>
                                          );
                                        }
                                        if (entry.kind === "text") {
                                          return (
                                            <Paper
                                              key={entry.key}
                                              variant="outlined"
                                              sx={{
                                                p: 1.25,
                                                bgcolor: "grey.50",
                                                borderColor: "warning.light",
                                              }}
                                            >
                                              <Stack
                                                direction="row"
                                                alignItems="center"
                                                spacing={1}
                                                flexWrap="wrap"
                                                useFlexGap
                                              >
                                                <Chip
                                                  size="small"
                                                  label={t("diff_staged_tag_unsaved")}
                                                  color="warning"
                                                />
                                                <Typography variant="body2">
                                                  {t(
                                                    entry.titleKey as
                                                      | "diff_staged_deleteUnknown"
                                                      | "diff_staged_newLane"
                                                      | "diff_staged_laneLogistic"
                                                      | "diff_staged_emptyDistricts"
                                                      | "diff_staged_shopPendingOnLane"
                                                      | "diff_staged_shopDistrictOnly"
                                                      | "diff_staged_pendingLogisticMaster"
                                                      | "diff_staged_editLogisticMaster"
                                                      | "diff_staged_deleteLogisticMaster"
                                                      | "diff_staged_importPending",
                                                    entry.titleParams as Record<
                                                      string,
                                                      string | number
                                                    >,
                                                  )}
                                                </Typography>
                                              </Stack>
                                            </Paper>
                                          );
                                        }
                                        const row = entry.row;
                                        const { headline, detail } =
                                          resolveVersionLogShopHeadline(
                                            row,
                                            shopNameByCodeMap,
                                          );
                                        return (
                                          <Paper
                                            key={entry.key}
                                            variant="outlined"
                                            sx={{
                                              p: 1.25,
                                              bgcolor: "grey.50",
                                              borderColor: "warning.light",
                                            }}
                                          >
                                            <Stack
                                              direction="row"
                                              spacing={1}
                                              alignItems="flex-start"
                                            >
                                              <Chip
                                                size="small"
                                                label={t("diff_staged_tag_unsaved")}
                                                color="warning"
                                                sx={{ mt: 0.25, flexShrink: 0 }}
                                              />
                                              <Box sx={{ flex: 1, minWidth: 0 }}>
                                                <Typography
                                                  variant="body2"
                                                  sx={{ fontWeight: 800 }}
                                                >
                                                  {headline}
                                                </Typography>
                                                {detail != null && detail !== "" && (
                                                  <Typography
                                                    variant="caption"
                                                    color="text.secondary"
                                                    sx={{ display: "block", mt: 0.25 }}
                                                  >
                                                    {detail}
                                                  </Typography>
                                                )}
                                                <Typography
                                                  variant="caption"
                                                  sx={{
                                                    fontFamily: "monospace",
                                                    display: "block",
                                                    mt: 0.5,
                                                  }}
                                                >
                                                  {row.shopCode || "—"}
                                                </Typography>
                                              </Box>
                                            </Stack>
                                          </Paper>
                                        );
                                      })}
                                    </Stack>
                                    <Divider sx={{ my: 1.5 }} />
                                  </>
                                )}
                                {diffLoading && (
                                  <Box
                                    sx={{
                                      display: "flex",
                                      justifyContent: "center",
                                      py: 4,
                                    }}
                                  >
                                    <CircularProgress size={24} />
                                  </Box>
                                )}
                                {!diffLoading && (
                                  <>
                                  {logisticMasterDiffLines.length > 0 && (
                                    <>
                                      <Typography
                                        variant="overline"
                                        sx={{
                                          fontWeight: 800,
                                          color: "text.secondary",
                                        }}
                                      >
                                        {t("diff_logisticMaster_section")}
                                      </Typography>
                                      {logisticMasterDiffLines.map((lm) => (
                                        <Paper
                                          key={`lm-${lm.logisticId}`}
                                          variant="outlined"
                                          sx={{
                                            p: 1.25,
                                            bgcolor: "grey.50",
                                            borderColor:
                                              lm.type === "ADDED"
                                                ? "success.light"
                                                : "divider",
                                          }}
                                        >
                                          <Stack
                                            direction="row"
                                            spacing={1}
                                            alignItems="center"
                                            flexWrap="wrap"
                                            useFlexGap
                                          >
                                            <Chip
                                              size="small"
                                              label={
                                                lm.type === "ADDED"
                                                  ? t("diff_logisticMaster_added")
                                                  : t("diff_logisticMaster_edited")
                                              }
                                              color={
                                                lm.type === "ADDED"
                                                  ? "success"
                                                  : "default"
                                              }
                                            />
                                            <Typography variant="body2">
                                              {lm.changeText ||
                                                `${lm.logisticName}（${lm.carPlate}）`}
                                            </Typography>
                                          </Stack>
                                        </Paper>
                                      ))}
                                    </>
                                  )}
                                  {hasOlder &&
                                    versionShopRows.length === 0 &&
                                    logisticMasterDiffLines.length === 0 &&
                                    stagedLogEntriesView.length === 0 &&
                                    !diffError && (
                                      <Alert severity="success">
                                        {t("diff_noDiffFromPrev")}
                                      </Alert>
                                    )}
                                  {hasOlder &&
                                    versionShopRows.length === 0 &&
                                    logisticMasterDiffLines.length === 0 &&
                                    stagedLogEntriesView.length > 0 &&
                                    !diffError && (
                                      <Alert severity="info">
                                        {t("diff_noShopDiffHasBoardStaged")}
                                      </Alert>
                                    )}
                                  {versionShopRows.map((row, ri) => {
                                    const { headline, detail } =
                                      resolveVersionLogShopHeadline(
                                        row,
                                        shopNameByCodeMap,
                                      );
                                    const laneLabelForFields =
                                      resolveVersionLogLaneLabel(row);
                                    return (
                                      <Paper
                                        key={`${row.truckRowId}-${ri}`}
                                        variant="outlined"
                                        sx={{
                                          p: 1.5,
                                          display: "flex",
                                          gap: 1.5,
                                          alignItems: "stretch",
                                          bgcolor: "grey.50",
                                          borderColor: "divider",
                                        }}
                                      >
                                        <Box
                                          sx={{
                                            width: 6,
                                            alignSelf: "stretch",
                                            borderRadius: 1,
                                            bgcolor:
                                              row.type === "moved"
                                                ? "warning.main"
                                                : row.type === "added"
                                                  ? "success.main"
                                                  : row.type === "deleted"
                                                    ? "error.main"
                                                    : "grey.400",
                                          }}
                                        />
                                        <Box sx={{ flex: 1, minWidth: 0 }}>
                                          <Stack
                                            direction="row"
                                            justifyContent="space-between"
                                            alignItems="flex-start"
                                            spacing={1}
                                          >
                                            <Box sx={{ minWidth: 0 }}>
                                              <Typography
                                                variant="body2"
                                                sx={{ fontWeight: 800 }}
                                              >
                                                {headline}
                                              </Typography>
                                              {detail != null &&
                                                detail !== "" && (
                                                  <Typography
                                                    variant="caption"
                                                    color="text.secondary"
                                                    sx={{
                                                      display: "block",
                                                      mt: 0.25,
                                                    }}
                                                  >
                                                    {detail}
                                                  </Typography>
                                                )}
                                            </Box>
                                            <Typography
                                              variant="caption"
                                              sx={{
                                                fontFamily: "monospace",
                                                bgcolor: "background.paper",
                                                px: 0.5,
                                                borderRadius: 0.5,
                                                flexShrink: 0,
                                              }}
                                            >
                                              {row.shopCode || "—"}
                                            </Typography>
                                          </Stack>
                                          <Stack
                                            direction="row"
                                            alignItems="center"
                                            spacing={0.5}
                                            sx={{ mt: 0.75 }}
                                            flexWrap="wrap"
                                          >
                                            {row.type === "moved" && (
                                              <>
                                                <Typography
                                                  variant="caption"
                                                  sx={{
                                                    px: 0.75,
                                                    py: 0.25,
                                                    bgcolor: "grey.200",
                                                    borderRadius: 1,
                                                  }}
                                                >
                                                  {t("diff_moveFrom", {
                                                    lane: row.fromLane ?? t("emDash"),
                                                  })}
                                                </Typography>
                                                <ArrowRight size={14} />
                                                <Typography
                                                  variant="caption"
                                                  sx={{
                                                    px: 0.75,
                                                    py: 0.25,
                                                    bgcolor: "primary.light",
                                                    color: "primary.dark",
                                                    fontWeight: 800,
                                                  }}
                                                >
                                                  {t("diff_moveTo", {
                                                    lane: row.toLane ?? t("emDash"),
                                                  })}
                                                </Typography>
                                              </>
                                            )}
                                            {row.type === "added" && (
                                              <Typography
                                                variant="caption"
                                                sx={{
                                                  px: 0.75,
                                                  py: 0.25,
                                                  bgcolor: "success.light",
                                                  color: "success.dark",
                                                  fontWeight: 800,
                                                }}
                                              >
                                                {t("diff_addedToLane", {
                                                  lane: row.toLane ?? t("emDash"),
                                                })}
                                              </Typography>
                                            )}
                                            {row.type === "deleted" && (
                                              <Typography
                                                variant="caption"
                                                sx={{
                                                  px: 0.75,
                                                  py: 0.25,
                                                  bgcolor: "error.light",
                                                  color: "error.dark",
                                                  fontWeight: 800,
                                                }}
                                              >
                                                {t("diff_removedFromLane", {
                                                  lane: row.fromLane ?? t("emDash"),
                                                })}
                                              </Typography>
                                            )}
                                            {row.fieldEdits != null &&
                                              row.fieldEdits.length > 0 && (
                                                <Box
                                                  sx={{
                                                    width: "100%",
                                                    flexBasis: "100%",
                                                    mt: 0.5,
                                                  }}
                                                >
                                                  <Stack spacing={0.35}>
                                                    {row.fieldEdits.map(
                                                      (fe, fei) => {
                                                        const isLogistic =
                                                          fe.label ===
                                                          "versionLogField_logisticId";
                                                        const resolveLogisticDisplay =
                                                          (raw: string) => {
                                                            const s = String(
                                                              raw ?? "",
                                                            ).trim();
                                                            if (
                                                              s === "" ||
                                                              s === "—" ||
                                                              s === "未分配" ||
                                                              s === "未分配物流商"
                                                            )
                                                              return t(
                                                                "diffLogistic_unassigned",
                                                              );
                                                            const n = Number(s);
                                                            if (
                                                              Number.isFinite(n)
                                                            ) {
                                                              return (
                                                                logisticNameById.get(
                                                                  n,
                                                                ) ?? s
                                                              );
                                                            }
                                                            return s;
                                                          };
                                                        const from = isLogistic
                                                          ? resolveLogisticDisplay(
                                                              fe.from,
                                                            )
                                                          : fe.from;
                                                        const to = isLogistic
                                                          ? resolveLogisticDisplay(
                                                              fe.to,
                                                            )
                                                          : fe.to;
                                                        const isLoadingSeq =
                                                          fe.label ===
                                                            VERSION_LOG_LOADING_SEQUENCE_LABEL ||
                                                          fe.label ===
                                                            "loadingSequence";
                                                        const showLaneOnSeq =
                                                          isLoadingSeq &&
                                                          laneLabelForFields !=
                                                            null &&
                                                          laneLabelForFields !==
                                                            "";

                                                        return (
                                                          <Typography
                                                            key={`${fe.label}-${fei}`}
                                                            variant="caption"
                                                            color="text.secondary"
                                                            sx={{
                                                              display: "block",
                                                            }}
                                                          >
                                                            <Box
                                                              component="span"
                                                              sx={{
                                                                fontWeight: 700,
                                                                color:
                                                                  "text.primary",
                                                              }}
                                                            >
                                                              {formatDiffFieldLabel(
                                                                fe.label,
                                                                t,
                                                              )}
                                                            </Box>
                                                            {showLaneOnSeq && (
                                                              <Box
                                                                component="span"
                                                                sx={{
                                                                  fontWeight: 600,
                                                                  color:
                                                                    "text.secondary",
                                                                }}
                                                              >
                                                                {" "}
                                                                (
                                                                {t(
                                                                  "diff_onLane",
                                                                  {
                                                                    lane: laneLabelForFields,
                                                                  },
                                                                )}
                                                                )
                                                              </Box>
                                                            )}
                                                            {"："}
                                                            {from} → {to}
                                                          </Typography>
                                                        );
                                                      },
                                                    )}
                                                  </Stack>
                                                </Box>
                                              )}
                                            {row.type === "edited" &&
                                              (!row.fieldEdits ||
                                                row.fieldEdits.length ===
                                                  0) && (
                                                <Typography
                                                  variant="caption"
                                                  color="text.secondary"
                                                >
                                                  {t("diff_editedCaption")}
                                                </Typography>
                                              )}
                                          </Stack>
                                        </Box>
                                      </Paper>
                                    );
                                  })}
                                  </>
                                )}
                              </Stack>
                            </Box>

                            <Box sx={{ pt: 1, flexShrink: 0 }}>
                              <Button
                                fullWidth
                                variant={
                                  headVersionId != null &&
                                  selectedLogVersionId === headVersionId
                                    ? "outlined"
                                    : "contained"
                                }
                                color="primary"
                                disabled={
                                  saving || selectedLogVersionId == null
                                }
                                startIcon={<RotateCcw size={18} />}
                                sx={{ py: 1.5, fontWeight: 800 }}
                                onClick={() =>
                                  selectedLogVersionId != null &&
                                  restoreVersion(selectedLogVersionId)
                                }
                              >
                                {headVersionId != null &&
                                selectedLogVersionId === headVersionId
                                  ? t("diff_restoreToHead")
                                  : t("diff_restoreToSelected")}
                              </Button>
                            </Box>
                          </>
                        );
                      })()}
                    </>
                  )}
                </Box>
              </Box>
            </Stack>
          </DialogContent>
          <DialogActions sx={{ px: 2, py: 1 }}>
            <Button onClick={closeLogDialog} disabled={saving}>
              {t("dialog_close")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={addShopDialogOpen}
          onClose={closeAddShopDialog}
          maxWidth="sm"
          fullWidth
        >
          <DialogTitle>
            {t("addShop_dialogTitle")}{" "}
            {(() => {
              const lane = addShopLaneId
                ? lanes.find((l) => l.id === addShopLaneId)
                : null;
              if (!lane) return "";
              return `「${lane.truckLanceCode}${
                lane.remark != null && String(lane.remark).trim() !== ""
                  ? ` · ${lane.remark}`
                  : ""
              }」`;
            })()}
          </DialogTitle>
          <DialogContent dividers>
            <Stack spacing={2} sx={{ pt: 1 }}>
              <Autocomplete
                options={addShopCandidates}
                getOptionLabel={(o) => `${o.name} (${o.code})`}
                isOptionEqualToValue={(a, b) => a.id === b.id}
                value={addShopPick}
                onChange={(_e, v) => setAddShopPick(v)}
                renderInput={(params) => (
                  <TextField
                    {...params}
                    label={t("shop_autocomplete_label")}
                    placeholder={t("shop_autocomplete_ph")}
                  />
                )}
                noOptionsText={
                  allShopsMaster.length === 0
                    ? t("shop_autocomplete_loading")
                    : t("shop_autocomplete_noOptions")
                }
              />
            </Stack>
          </DialogContent>
          <DialogActions>
            <Button onClick={closeAddShopDialog}>{t("cancel")}</Button>
            <Button
              onClick={() => submitAddShop()}
              variant="contained"
              disabled={!addShopPick}
            >
              {t("addShop_confirm")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={districtEditOpen}
          onClose={closeDistrictEdit}
          maxWidth="xs"
          fullWidth
        >
          <DialogTitle>
            {districtEditCtx?.mode === "add"
              ? t("district_dialog_add")
              : t("district_dialog_edit")}
          </DialogTitle>
          <DialogContent dividers>
            <TextField
              autoFocus
              margin="dense"
              label={t("district_name_label")}
              fullWidth
              value={districtEditDraft}
              onChange={(e) => {
                setDistrictEditDraft(e.target.value);
                setDistrictEditError(null);
              }}
              error={Boolean(districtEditError)}
              InputLabelProps={{ shrink: true }}
            />
          </DialogContent>
          <DialogActions>
            <Button onClick={closeDistrictEdit}>{t("cancel")}</Button>
            <Button variant="contained" onClick={() => applyDistrictEdit()}>
              {t("btn_apply")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={departureEditLaneId != null}
          onClose={closeDepartureEdit}
          maxWidth="xs"
          fullWidth
        >
          <DialogTitle>{t("departureDialog_title")}</DialogTitle>
          <DialogContent>
            <TextField
              margin="dense"
              fullWidth
              type="time"
              label={t("seq_edit_departureLabel")}
              value={departureEditDraft}
              onChange={(e) => setDepartureEditDraft(e.target.value)}
              InputLabelProps={{ shrink: true }}
              sx={{ mt: 1 }}
            />
            <Typography
              variant="caption"
              color="text.secondary"
              sx={{ mt: 1, display: "block" }}
            >
              {t("departureDialog_hint")}
            </Typography>
          </DialogContent>
          <DialogActions>
            <Button onClick={closeDepartureEdit}>{t("cancel")}</Button>
            <Button variant="contained" onClick={applyDepartureEdit}>
              {t("filter_apply")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={seqEditTarget != null}
          onClose={closeSeqEdit}
          maxWidth="xs"
          fullWidth
        >
          <DialogTitle>{t("seqDialog_title")}</DialogTitle>
          <DialogContent>
            <TextField
              margin="dense"
              fullWidth
              type="number"
              label={t("seq_edit_seqLabel")}
              value={seqEditDraft}
              onChange={(e) => setSeqEditDraft(e.target.value)}
              inputProps={{ step: 1 }}
              sx={{ mt: 1 }}
            />
          </DialogContent>
          <DialogActions>
            <Button onClick={closeSeqEdit}>{t("cancel")}</Button>
            <Button variant="contained" onClick={applySeqEdit}>
              {t("filter_apply")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={addRouteDialogOpen}
          onClose={() => {
            if (!addRouteSubmitting) closeAddRouteDialog();
          }}
          maxWidth="sm"
          fullWidth
          PaperProps={{ sx: { borderRadius: 3 } }}
        >
          <DialogTitle
            sx={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              pr: 1,
              py: 2,
              borderBottom: 1,
              borderColor: "divider",
              bgcolor: "grey.50",
            }}
          >
            <Stack direction="row" spacing={1.5} alignItems="center">
              <Box
                sx={{
                  bgcolor: "primary.main",
                  p: 1,
                  borderRadius: 1.5,
                  display: "inline-flex",
                }}
              >
                <Plus size={20} color="white" />
              </Box>
              <Typography variant="h6" sx={{ fontWeight: 800 }}>
                {t("addRoute_dialogTitle")}
              </Typography>
            </Stack>
            <IconButton
              onClick={closeAddRouteDialog}
              size="small"
              disabled={addRouteSubmitting}
              aria-label={t("drawerClose")}
            >
              <X size={20} />
            </IconButton>
          </DialogTitle>
          <DialogContent dividers>
            {addRouteError && (
              <Alert severity="error" sx={{ mb: 2 }}>
                {addRouteError}
              </Alert>
            )}
            <Box
              sx={{
                display: "grid",
                gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
                gap: 2,
                alignItems: "start",
                // small Outlined 預設約 40px，整體略加高 ≈5px
                "& .MuiOutlinedInput-root": { minHeight: 45 },
              }}
            >
              <TextField
                size="small"
                fullWidth
                required
                label={t("route_new_code_label")}
                value={newRouteForm.truckLanceCode}
                onChange={(e) =>
                  setNewRouteForm((p) => ({
                    ...p,
                    truckLanceCode: e.target.value,
                  }))
                }
                sx={{
                  "& .MuiInputBase-input": {
                    fontWeight: 800,
                    color: "primary.main",
                  },
                }}
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <TruckIcon size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("route_new_time_label")}
                type="time"
                value={newRouteForm.startTime}
                onChange={(e) =>
                  setNewRouteForm((p) => ({ ...p, startTime: e.target.value }))
                }
                InputLabelProps={{ shrink: true }}
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Clock size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <FormControl
                size="small"
                fullWidth
                sx={{ gridColumn: { xs: "1", sm: "1 / -1" } }}
              >
                <InputLabel id="new-route-logistic-label">{t("route_new_logistic_label")}</InputLabel>
                <Select
                  labelId="new-route-logistic-label"
                  label={t("route_new_logistic_label")}
                  value={
                    newRouteForm.logisticId == null
                      ? ""
                      : String(newRouteForm.logisticId)
                  }
                  onChange={(e) => {
                    const v = e.target.value;
                    setNewRouteForm((p) => ({
                      ...p,
                      logisticId:
                        v === "" ? null : Number.parseInt(String(v), 10),
                    }));
                  }}
                >
                  <MenuItem value="">{t("route_logisticUnspecified")}</MenuItem>
                  {logisticRowsSortedForSelect.map((row) => (
                    <MenuItem key={row.id} value={String(row.id)}>
                      {row.logisticName}
                    </MenuItem>
                  ))}
                </Select>
              </FormControl>
              <FormControl
                size="small"
                fullWidth
                sx={{ gridColumn: { xs: "1", sm: "1 / -1" } }}
              >
                <InputLabel>{t("route_new_store_label")}</InputLabel>
                <Select
                  label={t("route_new_store_label")}
                  value={newRouteForm.storeId}
                  onChange={(e) =>
                    setNewRouteForm((p) => ({
                      ...p,
                      storeId: e.target.value as "2F" | "4F",
                    }))
                  }
                >
                  <MenuItem value="2F">2F</MenuItem>
                  <MenuItem value="4F">4F</MenuItem>
                </Select>
              </FormControl>
              {newRouteForm.storeId === "4F" && (
                <TextField
                  size="small"
                  fullWidth
                  sx={{ gridColumn: { xs: "1", sm: "1 / -1" } }}
                  label={t("route_new_remark_label")}
                  value={newRouteForm.remark}
                  onChange={(e) =>
                    setNewRouteForm((p) => ({ ...p, remark: e.target.value }))
                  }
                />
              )}
            </Box>
          </DialogContent>
          <DialogActions
            sx={{
              px: 3,
              py: 2,
              bgcolor: "grey.50",
              borderTop: 1,
              borderColor: "divider",
              gap: 1.5,
              justifyContent: "flex-end",
              flexWrap: "nowrap",
            }}
          >
            <Button
              variant="outlined"
              onClick={closeAddRouteDialog}
              disabled={addRouteSubmitting}
            >
              {t("btn_cancelBack")}
            </Button>
            <Button
              variant="contained"
              disabled={
                addRouteSubmitting ||
                !String(newRouteForm.truckLanceCode || "").trim() ||
                !String(newRouteForm.startTime || "").trim()
              }
              onClick={() => void submitAddRoute()}
            >
              {addRouteSubmitting
                ? t("addRoute_submitting")
                : t("addRoute_confirm")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={addLogisticOpen}
          onClose={() => {
            if (!addLogisticSubmitting) setAddLogisticOpen(false);
          }}
          maxWidth="sm"
          fullWidth
          PaperProps={{ sx: { borderRadius: 3 } }}
        >
          <DialogTitle sx={{ fontWeight: 800 }}>{t("dialog_addLogisticsTitle")}</DialogTitle>
          <DialogContent dividers>
            {addLogisticError && (
              <Alert severity="error" sx={{ mb: 2 }}>
                {addLogisticError}
              </Alert>
            )}
            <Stack spacing={2} sx={{ pt: 0.5 }}>
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_companyName")}
                value={addLogisticForm.logisticName}
                onChange={(e) =>
                  setAddLogisticForm((p) => ({
                    ...p,
                    logisticName: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Building2 size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_plate")}
                value={addLogisticForm.carPlate}
                onChange={(e) =>
                  setAddLogisticForm((p) => ({
                    ...p,
                    carPlate: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <CreditCard size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_driver")}
                value={addLogisticForm.driverName}
                onChange={(e) =>
                  setAddLogisticForm((p) => ({
                    ...p,
                    driverName: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Users size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_phone")}
                value={addLogisticForm.driverPhone}
                onChange={(e) =>
                  setAddLogisticForm((p) => ({
                    ...p,
                    driverPhone: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Phone size={18} />
                    </InputAdornment>
                  ),
                }}
              />
            </Stack>
          </DialogContent>
          <DialogActions sx={{ px: 3, py: 2 }}>
            <Button
              onClick={() => setAddLogisticOpen(false)}
              disabled={addLogisticSubmitting}
            >
              {t("cancel")}
            </Button>
            <Button
              variant="contained"
              disabled={addLogisticSubmitting}
              onClick={() => void submitAddLogistic()}
            >
              {addLogisticSubmitting
                ? t("Submitting...")
                : t("logistic_btn_save")}
            </Button>
          </DialogActions>
        </Dialog>

        <Dialog
          open={editLogisticOpen}
          onClose={() => {
            if (!editLogisticSubmitting) {
              setEditLogisticOpen(false);
              setEditLogisticError(null);
            }
          }}
          maxWidth="sm"
          fullWidth
          PaperProps={{ sx: { borderRadius: 3 } }}
        >
          <DialogTitle sx={{ fontWeight: 800 }}>{t("dialog_editLogisticsTitle")}</DialogTitle>
          <DialogContent dividers>
            {editLogisticError && (
              <Alert severity="error" sx={{ mb: 2 }}>
                {editLogisticError}
              </Alert>
            )}
            <Stack spacing={2} sx={{ pt: 0.5 }}>
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_companyName")}
                value={editLogisticForm.logisticName}
                onChange={(e) =>
                  setEditLogisticForm((p) => ({
                    ...p,
                    logisticName: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Building2 size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_plate")}
                value={editLogisticForm.carPlate}
                onChange={(e) =>
                  setEditLogisticForm((p) => ({
                    ...p,
                    carPlate: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <CreditCard size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_driver")}
                value={editLogisticForm.driverName}
                onChange={(e) =>
                  setEditLogisticForm((p) => ({
                    ...p,
                    driverName: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Users size={18} />
                    </InputAdornment>
                  ),
                }}
              />
              <TextField
                size="small"
                fullWidth
                required
                label={t("logistic_phone")}
                value={editLogisticForm.driverPhone}
                onChange={(e) =>
                  setEditLogisticForm((p) => ({
                    ...p,
                    driverPhone: e.target.value,
                  }))
                }
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <Phone size={18} />
                    </InputAdornment>
                  ),
                }}
              />
            </Stack>
          </DialogContent>
          <DialogActions sx={{ px: 3, py: 2 }}>
            <Button
              onClick={() => {
                setEditLogisticOpen(false);
                setEditLogisticError(null);
              }}
              disabled={editLogisticSubmitting}
            >
              {t("cancel")}
            </Button>
            <Button
              variant="contained"
              disabled={editLogisticSubmitting}
              onClick={() => void submitEditLogistic()}
            >
              {editLogisticSubmitting
                ? t("Submitting...")
                : t("logistic_btn_apply")}
            </Button>
          </DialogActions>
        </Dialog>

        {/* Sidebar */}
        <Box
          sx={{
            width: 320,
            flexShrink: 0,
            minHeight: 0,
            bgcolor: "background.paper",
            borderRight: "1px solid",
            borderColor: "divider",
            p: 2,
            overflow: "auto",
          }}
        >
          <Stack
            direction="row"
            spacing={0.5}
            sx={{
              bgcolor: "grey.100",
              p: 0.5,
              borderRadius: 2,
              mb: 2,
            }}
          >
            <Button
              fullWidth
              size="small"
              variant={routeBoardTab === "board" ? "contained" : "text"}
              color={routeBoardTab === "board" ? "primary" : "inherit"}
              onClick={() => setRouteBoardTab("board")}
              startIcon={<LayoutDashboard size={14} />}
              sx={{
                py: 1,
                fontSize: "0.75rem",
                fontWeight: 800,
                textTransform: "none",
              }}
            >
              {t("tabBoard")}
            </Button>
            <Button
              fullWidth
              size="small"
              variant={routeBoardTab === "logistics" ? "contained" : "text"}
              color={routeBoardTab === "logistics" ? "primary" : "inherit"}
              onClick={() => setRouteBoardTab("logistics")}
              startIcon={<Building2 size={14} />}
              sx={{
                py: 1,
                fontSize: "0.75rem",
                fontWeight: 800,
                textTransform: "none",
              }}
            >
              {t("tabLogistics")}
            </Button>
          </Stack>

          {(() => {
            const laneIds = visibleLaneOptions.map((l) => l.id);
            const total = laneIds.length;
            const selectedVisible = laneIds.filter((id) =>
              selectedLaneIds.includes(id),
            ).length;
            const filterActive =
              laneFilter.floor !== "all" ||
              String(laneFilter.query || "").trim() !== "";
            return (
              <Box sx={{ mb: routeBoardTab === "logistics" ? 2 : 0 }}>
                <Typography
                  variant="overline"
                  sx={{ fontWeight: 800, color: "text.secondary" }}
                >
                  {t("lane_selectTitle")}
                </Typography>

                <Select
                  multiple
                  size="small"
                  value={selectedLaneIds}
                  displayEmpty
                  fullWidth
                  renderValue={(selected) => {
                    const arr = selected as string[];
                    if (arr.length === 0) {
                      return (
                        <Box component="span" sx={{ color: "text.secondary" }}>
                          {t("lane_selectedNone")}
                        </Box>
                      );
                    }
                    if (filterActive)
                      return t("lane_selectedCount", {
                        count: selectedVisible,
                      });
                    return t("lane_selectedCount", { count: arr.length });
                  }}
                  onChange={(e) => {
                    const value = e.target.value as unknown as string[];
                    setSelectedLaneIds(value);
                  }}
                  MenuProps={{
                    autoFocus: false,
                    MenuListProps: { autoFocus: false, dense: false },
                    PaperProps: {
                      sx: {
                        maxHeight: 420,
                        minWidth: 360,
                        maxWidth: "min(100vw - 32px, 480px)",
                      },
                    },
                  }}
                  sx={{ mt: 1 }}
                >
                  <ListSubheader
                    sx={{
                      px: 1.5,
                      py: 1,
                      lineHeight: 1.2,
                      position: "sticky",
                      top: 0,
                      zIndex: 2,
                      bgcolor: "background.paper",
                      borderBottom: 1,
                      borderColor: "divider",
                    }}
                    onClick={(e) => e.stopPropagation()}
                  >
                    <Stack direction="row" spacing={0.5} alignItems="center">
                      <TextField
                        size="small"
                        fullWidth
                        placeholder={t("lane_searchPh")}
                        value={laneFilter.query}
                        onChange={(e) =>
                          setLaneFilter((prev) => ({
                            ...prev,
                            query: e.target.value,
                          }))
                        }
                        onKeyDown={(e) => e.stopPropagation()}
                        onClick={(e) => e.stopPropagation()}
                        onMouseDown={(e) => e.stopPropagation()}
                        InputProps={{
                          startAdornment: (
                            <Box
                              sx={{
                                mr: 0.5,
                                display: "inline-flex",
                                color: "text.disabled",
                              }}
                            >
                              <Search size={16} />
                            </Box>
                          ),
                        }}
                        sx={{
                          flex: 1,
                          minWidth: 0,
                          "& .MuiOutlinedInput-root": {
                            borderRadius: 2,
                            bgcolor: "grey.50",
                          },
                          "& .MuiInputBase-input": {
                            textAlign: "left",
                            fontSize: "0.8125rem",
                            color: "text.secondary",
                            py: 0.75,
                          },
                          "& .MuiInputBase-input::placeholder": {
                            opacity: 1,
                            color: "text.disabled",
                            textAlign: "left",
                          },
                        }}
                      />
                      <Tooltip title={t("floor_label")}>
                        <span>
                          <IconButton
                            size="small"
                            aria-label={t("floor_label")}
                            disabled={(lanes || []).length === 0}
                            color={filterActive ? "primary" : "default"}
                            onMouseDown={(e) => e.stopPropagation()}
                            onClick={(e) => {
                              e.stopPropagation();
                              setLaneFilterAnchor(e.currentTarget);
                            }}
                            sx={{
                              flexShrink: 0,
                              width: 32,
                              height: 32,
                            }}
                          >
                            <FilterListIcon fontSize="small" />
                          </IconButton>
                        </span>
                      </Tooltip>
                      <Button
                        size="small"
                        variant="text"
                        disabled={total === 0}
                        onMouseDown={(e) => e.stopPropagation()}
                        onClick={(e) => {
                          e.stopPropagation();
                          setSelectedLaneIds((prev) => {
                            const set = new Set(prev);
                            if (laneIds.every((id) => set.has(id))) {
                              laneIds.forEach((id) => set.delete(id));
                            } else {
                              laneIds.forEach((id) => set.add(id));
                            }
                            return Array.from(set);
                          });
                        }}
                        sx={{
                          flexShrink: 0,
                          minWidth: "auto",
                          px: 1,
                          py: 0.25,
                        }}
                      >
                        {t("lane_selectAll")}
                      </Button>
                    </Stack>
                  </ListSubheader>
                  {visibleLaneOptions.map((lane) => {
                      const checked = selectedLaneIds.includes(lane.id);
                      const rem =
                        lane.remark != null &&
                        String(lane.remark).trim() !== ""
                          ? String(lane.remark).trim()
                          : null;
                      const driverPlate =
                        (lane.driver || "—") +
                        (lane.plate ? ` · ${lane.plate}` : "");
                      const secondaryLine = rem
                        ? `${rem} · ${driverPlate}`
                        : driverPlate;
                      return (
                        <MenuItem
                          key={lane.id}
                          value={lane.id}
                          sx={{
                            alignItems: "flex-start",
                            py: 1,
                            gap: 1,
                          }}
                        >
                          <Checkbox
                            checked={checked}
                            size="small"
                            sx={{ p: 0.5, mt: 0.15 }}
                          />
                          <ListItemText
                            primary={lane.truckLanceCode}
                            secondary={secondaryLine}
                            primaryTypographyProps={{
                              sx: {
                                fontWeight: 800,
                                fontSize: "0.9rem",
                                wordBreak: "break-word",
                                overflowWrap: "anywhere",
                              },
                            }}
                            secondaryTypographyProps={{
                              sx: {
                                fontSize: "0.72rem",
                                color: "text.secondary",
                                mt: 0.25,
                              },
                            }}
                          />
                        </MenuItem>
                      );
                    })}
                </Select>

                <Popover
                  open={Boolean(laneFilterAnchor)}
                  anchorEl={laneFilterAnchor}
                  onClose={() => setLaneFilterAnchor(null)}
                  anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
                  transformOrigin={{ vertical: "top", horizontal: "left" }}
                >
                  <Box sx={{ p: 2, width: 260 }}>
                    <Stack spacing={1.5}>
                      <FormControl size="small" fullWidth>
                        <InputLabel>{t("floor_label")}</InputLabel>
                        <Select
                          label={t("floor_label")}
                          value={laneFilter.floor}
                          onChange={(e) =>
                            setLaneFilter((prev) => ({
                              ...prev,
                              floor: e.target.value as any,
                            }))
                          }
                        >
                          <MenuItem value="all">{t("floor_all")}</MenuItem>
                          <MenuItem value="2F">2F</MenuItem>
                          <MenuItem value="4F">4F</MenuItem>
                        </Select>
                      </FormControl>
                      <Stack
                        direction="row"
                        spacing={1}
                        justifyContent="flex-end"
                      >
                        <Button
                          size="small"
                          onClick={() =>
                            setLaneFilter((prev) => ({
                              ...prev,
                              floor: "all",
                            }))
                          }
                        >
                          {t("filter_clear")}
                        </Button>
                        <Button
                          size="small"
                          variant="contained"
                          onClick={() => setLaneFilterAnchor(null)}
                        >
                          {t("filter_apply")}
                        </Button>
                      </Stack>
                    </Stack>
                  </Box>
                </Popover>

                {routeBoardTab === "board" && (
                  <Button
                    variant="outlined"
                    size="small"
                    fullWidth
                    startIcon={<Plus size={16} />}
                    disabled={loading || addRouteSubmitting}
                    sx={{ mt: 1 }}
                    onClick={openAddRouteDialog}
                  >
                    {t("btn_addLane")}
                  </Button>
                )}
              </Box>
            );
          })()}

          {routeBoardTab === "logistics" && (
            <Box sx={{ mb: 2 }}>
              <Typography
                variant="overline"
                sx={{ fontWeight: 800, color: "text.secondary" }}
              >
                {t("quickIndex")}
              </Typography>
              <Button
                fullWidth
                size="small"
                variant="outlined"
                color="primary"
                startIcon={<Plus size={16} />}
                sx={{ mt: 1, fontWeight: 800, textTransform: "none" }}
                onClick={() => {
                  setAddLogisticError(null);
                  setAddLogisticOpen(true);
                }}
              >
                {t("btn_addLogistics")}
              </Button>
              <Stack spacing={1} sx={{ mt: 1 }}>
                {lanesByLogisticsCompany.map(([company, list]) => {
                  const logisticMaster = resolveLogisticMasterRow(
                    company,
                    list,
                    logisticRowsEffective,
                  );
                  const canManage =
                    company !== "未分配物流商" && logisticMaster != null;
                  const masterId =
                    logisticMaster != null
                      ? Number(logisticMaster.id)
                      : null;
                  const isPendingDelete =
                    masterId != null &&
                    Number.isFinite(masterId) &&
                    masterId > 0 &&
                    pendingLogisticMasterDeletes.has(masterId);
                  const canDelete =
                    canManage && list.length === 0 && !isPendingDelete;

                  return (
                    <Box
                      key={
                        logisticMaster
                          ? `log-${logisticMaster.id}`
                          : `co-${company}`
                      }
                      sx={{
                        p: 1,
                        borderRadius: 1,
                        bgcolor: "grey.50",
                        border: "1px solid",
                        borderColor: isPendingDelete
                          ? "warning.light"
                          : "divider",
                        opacity: isPendingDelete ? 0.65 : 1,
                      }}
                    >
                      <Stack
                        direction="row"
                        alignItems="center"
                        spacing={0.75}
                        sx={{ minHeight: 28 }}
                      >
                        <Typography
                          variant="caption"
                          sx={{
                            fontWeight: 700,
                            flex: 1,
                            minWidth: 0,
                            overflow: "hidden",
                            textOverflow: "ellipsis",
                            whiteSpace: "nowrap",
                            lineHeight: 1.25,
                          }}
                        >
                          {company}
                        </Typography>
                        {canManage && (
                          <Stack
                            direction="row"
                            alignItems="center"
                            spacing={0}
                            sx={{ flexShrink: 0 }}
                          >
                            <Tooltip title={t("tooltip_editLogisticsDb")}>
                              <IconButton
                                size="small"
                                aria-label={t("aria_editLogistics")}
                                onClick={() =>
                                  openEditLogistic(logisticMaster)
                                }
                                sx={{
                                  width: 28,
                                  height: 28,
                                  p: 0,
                                }}
                              >
                                <Pencil size={14} />
                              </IconButton>
                            </Tooltip>
                            <Tooltip
                              title={
                                list.length > 0
                                  ? t("err_logisticDeleteHasLanes", {
                                      count: list.length,
                                    })
                                  : t("tooltip_deleteLogistics")
                              }
                            >
                              <Box
                                component="span"
                                sx={{
                                  display: "inline-flex",
                                  alignItems: "center",
                                }}
                              >
                                <IconButton
                                  size="small"
                                  aria-label={t("aria_deleteLogistics")}
                                  disabled={!canDelete}
                                  onClick={() =>
                                    stageDeleteLogistic(
                                      logisticMaster,
                                      company,
                                    )
                                  }
                                  sx={{
                                    width: 28,
                                    height: 28,
                                    p: 0,
                                  }}
                                >
                                  <Trash2 size={14} />
                                </IconButton>
                              </Box>
                            </Tooltip>
                          </Stack>
                        )}
                        <Chip
                          label={t("lane_companyChip", { count: list.length })}
                          size="small"
                          sx={{
                            fontWeight: 800,
                            height: 22,
                            fontSize: "0.65rem",
                            flexShrink: 0,
                            "& .MuiChip-label": {
                              px: 0.75,
                              lineHeight: 1.2,
                            },
                          }}
                        />
                      </Stack>
                      {isPendingDelete && (
                        <Typography
                          variant="caption"
                          color="warning.dark"
                          sx={{ display: "block", mt: 0.5, fontWeight: 700 }}
                        >
                          {t("diff_staged_tag_unsaved")} ·{" "}
                          {t("diff_staged_deleteLogisticMaster", {
                            name: company,
                          })}
                        </Typography>
                      )}
                    </Box>
                  );
                })}
                {lanesByLogisticsCompany.length === 0 && (
                  <Typography variant="caption" color="text.secondary">
                    {t("logistics_sidebarEmpty")}
                  </Typography>
                )}
              </Stack>
            </Box>
          )}

          {routeBoardTab !== "logistics" && (
            <>
              <Typography
                variant="overline"
                sx={{
                  fontWeight: 800,
                  color: "text.secondary",
                  mt: 3,
                  display: "block",
                }}
              >
                {t("tools_title")}
              </Typography>

              <Stack spacing={1} sx={{ mt: 1 }}>
                <TextField
                  size="small"
                  placeholder={t("shop_searchPh")}
                  value={searchTerm}
                  onChange={(e) => setSearchTerm(e.target.value)}
                  InputProps={{
                    startAdornment: (
                      <Box
                        sx={{
                          mr: 1,
                          display: "inline-flex",
                          alignItems: "center",
                          color: "text.secondary",
                        }}
                      >
                        <Search size={16} />
                      </Box>
                    ),
                    sx: {
                      alignItems: "center",
                    },
                  }}
                  sx={{
                    "& .MuiInputBase-root": {
                      alignItems: "center",
                    },
                    "& .MuiInputBase-input": {
                      textAlign: "left",
                      py: 0.75,
                    },
                    "& .MuiInputBase-input::placeholder": {
                      opacity: 1,
                      color: "text.disabled",
                      textAlign: "left",
                    },
                  }}
                />
                <Button
                  variant="outlined"
                  size="small"
                  startIcon={<History size={16} />}
                  onClick={openLogDialog}
                  disabled={loading || lanes.length === 0}
                >
                  {t("btn_openVersionLog")}
                </Button>

                <Button
                  variant="outlined"
                  size="small"
                  onClick={loadLanes}
                  disabled={loading}
                >
                  {loading ? t("btn_loading") : t("btn_refresh")}
                </Button>
              </Stack>
            </>
          )}
        </Box>

        {/* Board */}
        <Box
          sx={{
            flex: 1,
            minWidth: 0,
            minHeight: 0,
            p: 2,
            overflow: "auto",
            bgcolor: "grey.100",
          }}
        >
          {error && (
            <Alert severity="error" sx={{ mb: 2 }}>
              {error}
            </Alert>
          )}
          {loading ? (
            <Box sx={{ display: "flex", justifyContent: "center", py: 6 }}>
              <CircularProgress />
            </Box>
          ) : routeBoardTab === "logistics" ? (
            <Box sx={{ width: "100%", maxWidth: 1400, mx: "auto" }}>
              <Typography variant="h5" sx={{ fontWeight: 900, mb: 3 }}>
                {t("logistics_overviewTitle")}
              </Typography>

              <Box
                sx={{
                  display: "grid",
                  gridTemplateColumns: {
                    xs: "1fr",
                    md: "repeat(2, 1fr)",
                    xl: "repeat(3, 1fr)",
                  },
                  gap: 3,
                }}
              >
                {lanesByLogisticsCompany.map(([company, companyLanes]) => {
                  const colStats = summarizeLogisticsColumnStats(companyLanes);
                  const columnHasDirtyLogistics = companyLanes.some((lane) =>
                    dirtyLaneLogisticIds.has(lane.id),
                  );
                  const logisticMaster = resolveLogisticMasterRow(
                    company,
                    companyLanes,
                    logisticRowsEffective,
                  );
                  return (
                    <Card
                      key={
                        logisticMaster
                          ? `lid:${logisticMaster.id}`
                          : `co:${company}`
                      }
                      variant="outlined"
                      onDragOver={(e) => {
                        e.preventDefault();
                        e.dataTransfer.dropEffect = "move";
                        if (logisticsLaneDragIdRef.current) {
                          setLogisticsDropHoverCompany(company);
                        }
                      }}
                      onDrop={(e) => {
                        e.preventDefault();
                        handleLogisticsDropOnCompany(company, companyLanes);
                      }}
                      sx={{
                        borderRadius: 3,
                        overflow: "hidden",
                        display: "flex",
                        flexDirection: "column",
                        transition: "border-color 0.15s, outline-color 0.15s",
                        borderColor: columnHasDirtyLogistics
                          ? "warning.main"
                          : "divider",
                        outline:
                          logisticsDropHoverCompany === company
                            ? "2px solid"
                            : "1px solid transparent",
                        outlineColor:
                          logisticsDropHoverCompany === company
                            ? "primary.main"
                            : "transparent",
                      }}
                    >
                      <Box
                        sx={{
                          p: 2,
                          borderBottom: 1,
                          borderColor: "divider",
                          bgcolor: columnHasDirtyLogistics
                            ? "warning.50"
                            : "grey.50",
                          display: "flex",
                          alignItems: "flex-start",
                          justifyContent: "space-between",
                          gap: 1,
                        }}
                      >
                        <Stack
                          direction="row"
                          spacing={1.5}
                          alignItems="flex-start"
                          sx={{ minWidth: 0, flex: 1 }}
                        >
                          <Box
                            sx={{
                              p: 1,
                              borderRadius: 1,
                              bgcolor: "primary.50",
                              color: "primary.main",
                              display: "flex",
                              flexShrink: 0,
                            }}
                          >
                            <Building2 size={20} />
                          </Box>
                          <Box sx={{ minWidth: 0 }}>
                            <Stack
                              direction="row"
                              alignItems="center"
                              spacing={0.75}
                              flexWrap="wrap"
                              useFlexGap
                            >
                              <Typography
                                sx={{
                                  fontWeight: 900,
                                  fontSize: "1.1rem",
                                  flex: 1,
                                  minWidth: 0,
                                  lineHeight: 1.25,
                                }}
                              >
                                {company}
                              </Typography>
                              {columnHasDirtyLogistics && (
                                <Chip
                                  size="small"
                                  color="warning"
                                  label={t("logistics_dirtyColumnBadge")}
                                  sx={{ height: 22, fontWeight: 800 }}
                                />
                              )}
                            </Stack>
                            {logisticMaster && (
                              <Stack
                                spacing={0.5}
                                sx={{ mt: 0.75, alignSelf: "stretch", color: "text.secondary" }}
                              >
                                <Stack
                                  direction="row"
                                  spacing={0.75}
                                  alignItems="center"
                                  sx={{ alignSelf: "stretch" }}
                                >
                                  <Box
                                    sx={{
                                      display: "flex",
                                      alignItems: "center",
                                      flexShrink: 0,
                                      color: "inherit",
                                    }}
                                  >
                                    <Users size={13} />
                                  </Box>
                                  <Typography
                                    variant="body2"
                                    sx={{
                                      fontWeight: 500,
                                      fontSize: "0.8125rem",
                                      lineHeight: 1.35,
                                      color: "inherit",
                                    }}
                                  >
                                    {String(
                                      logisticMaster.driverName ?? "",
                                    ).trim() || "—"}
                                  </Typography>
                                </Stack>
                                <Stack
                                  direction="row"
                                  spacing={0.75}
                                  alignItems="center"
                                  sx={{ alignSelf: "stretch" }}
                                >
                                  <Box
                                    sx={{
                                      display: "flex",
                                      alignItems: "center",
                                      flexShrink: 0,
                                      color: "inherit",
                                    }}
                                  >
                                    <Phone size={13} />
                                  </Box>
                                  <Typography
                                    variant="body2"
                                    sx={{
                                      fontWeight: 500,
                                      fontSize: "0.8125rem",
                                      lineHeight: 1.35,
                                      color: "inherit",
                                    }}
                                  >
                                    {logisticMaster.driverNumber != null &&
                                    Number.isFinite(logisticMaster.driverNumber)
                                      ? String(logisticMaster.driverNumber)
                                      : "—"}
                                  </Typography>
                                </Stack>
                                <Stack
                                  direction="row"
                                  spacing={0.75}
                                  alignItems="center"
                                  sx={{ alignSelf: "stretch" }}
                                >
                                  <Box
                                    sx={{
                                      display: "flex",
                                      alignItems: "center",
                                      flexShrink: 0,
                                      color: "inherit",
                                    }}
                                  >
                                    <CarFront size={13} />
                                  </Box>
                                  <Typography
                                    variant="body2"
                                    sx={{
                                      fontWeight: 500,
                                      fontSize: "0.8125rem",
                                      lineHeight: 1.35,
                                      color: "inherit",
                                    }}
                                  >
                                    {String(
                                      logisticMaster.carPlate ?? "",
                                    ).trim() || "—"}
                                  </Typography>
                                </Stack>
                              </Stack>
                            )}
                            <Stack
                              direction="row"
                              flexWrap="wrap"
                              useFlexGap
                              spacing={0.75}
                              sx={{
                                mt: logisticMaster ? 1.5 : 1,
                                pt: logisticMaster ? 1.5 : 0,
                                borderTop: logisticMaster ? 1 : 0,
                                borderColor: "divider",
                                alignItems: "center",
                              }}
                            >
                              <Chip
                                size="small"
                                variant="outlined"
                                color="primary"
                                label={t("logistics_colLaneCount", {
                                  count: colStats.laneCount,
                                })}
                                sx={{ fontWeight: 700, height: 24 }}
                              />
                              <Chip
                                size="small"
                                variant="outlined"
                                label={t("logistics_colShopCount", {
                                  count: colStats.shopCount,
                                })}
                                sx={{ fontWeight: 700, height: 24 }}
                              />
                              <Chip
                                size="small"
                                label={`2F · ${colStats.count2F}`}
                                sx={{
                                  height: 24,
                                  fontWeight: 700,
                                  bgcolor: "action.hover",
                                }}
                              />
                              <Chip
                                size="small"
                                label={`4F · ${colStats.count4F}`}
                                sx={{
                                  height: 24,
                                  fontWeight: 700,
                                  bgcolor: "action.hover",
                                }}
                              />
                            </Stack>
                          </Box>
                        </Stack>
                        <ChevronRight
                          size={20}
                          color="var(--mui-palette-text-disabled)"
                          style={{ flexShrink: 0 }}
                        />
                      </Box>
                      <Stack spacing={1.5} sx={{ p: 2, flex: 1 }}>
                        {companyLanes.map((lane) => {
                          const logisticDirty = dirtyLaneLogisticIds.has(
                            lane.id,
                          );
                          return (
                            <Paper
                              key={lane.id}
                              component="div"
                              draggable
                              onDragStart={(e) => {
                                logisticsLaneDragIdRef.current = lane.id;
                                e.dataTransfer.effectAllowed = "move";
                                e.dataTransfer.setData(
                                  "application/x-fpsms-lane-id",
                                  lane.id,
                                );
                              }}
                              onDragEnd={() => {
                                logisticsLaneDragIdRef.current = null;
                                setLogisticsDropHoverCompany(null);
                              }}
                              variant="outlined"
                              sx={{
                                p: 1.5,
                                borderRadius: 2,
                                display: "flex",
                                flexDirection: "column",
                                gap: 1,
                                bgcolor: logisticDirty
                                  ? "warning.50"
                                  : "grey.50",
                                borderColor: logisticDirty
                                  ? "warning.main"
                                  : "divider",
                                boxShadow: logisticDirty
                                  ? "0 0 0 2px rgba(245, 124, 0, 0.18)"
                                  : "none",
                                minHeight: 88,
                                cursor: "grab",
                                overflow: "hidden",
                                "&:hover": {
                                  bgcolor: logisticDirty
                                    ? "warning.50"
                                    : "action.hover",
                                  borderColor: logisticDirty
                                    ? "warning.dark"
                                    : "primary.light",
                                },
                              }}
                            >
                              <Stack
                                direction="row"
                                alignItems="flex-start"
                                justifyContent="space-between"
                                spacing={1}
                                sx={{ minWidth: 0 }}
                              >
                                <Box sx={{ flex: 1, minWidth: 0 }}>
                                  <Typography
                                    sx={{
                                      fontWeight: 900,
                                      fontStyle: "italic",
                                      color: "primary.main",
                                      fontSize: "1.05rem",
                                      lineHeight: 1.3,
                                      wordBreak: "break-word",
                                      overflowWrap: "anywhere",
                                    }}
                                  >
                                    {lane.truckLanceCode}
                                    {lane.remark != null &&
                                      String(lane.remark).trim() !== "" && (
                                        <Typography
                                          component="span"
                                          variant="caption"
                                          color="secondary"
                                          sx={{ ml: 0.5, fontStyle: "normal" }}
                                        >
                                          ·{lane.remark}
                                        </Typography>
                                      )}
                                  </Typography>
                                  {logisticDirty && (
                                    <Chip
                                      size="small"
                                      color="warning"
                                      label={t("logistics_dirtyLaneBadge")}
                                      sx={{
                                        mt: 0.75,
                                        height: 22,
                                        fontWeight: 800,
                                      }}
                                    />
                                  )}
                                  {(lane.driver || lane.plate) && (
                                    <Stack
                                      direction="row"
                                      spacing={0.75}
                                      alignItems="center"
                                      flexWrap="wrap"
                                      sx={{ mt: 0.5 }}
                                    >
                                      {!!lane.driver && (
                                        <Typography
                                          variant="caption"
                                          sx={{ fontWeight: 700 }}
                                        >
                                          {lane.driver}
                                        </Typography>
                                      )}
                                      {lane.plate && (
                                        <Chip
                                          label={lane.plate}
                                          size="small"
                                          variant="outlined"
                                          sx={{
                                            fontSize: "0.65rem",
                                            height: 22,
                                            maxWidth: "100%",
                                          }}
                                        />
                                      )}
                                    </Stack>
                                  )}
                                </Box>
                                <Stack
                                  direction="row"
                                  spacing={0.25}
                                  alignItems="flex-start"
                                  sx={{ flexShrink: 0, pt: 0.25 }}
                                >
                                  <GripVertical
                                    size={18}
                                    aria-hidden
                                    color="var(--mui-palette-text-secondary)"
                                  />
                                  <Tooltip title={t("tooltip_openLaneBoard")}>
                                    <IconButton
                                      size="small"
                                      onClick={() => {
                                        setRouteBoardTab("board");
                                        setSelectedLaneIds([lane.id]);
                                      }}
                                      aria-label={t("aria_openLaneBoard")}
                                    >
                                      <LayoutDashboard size={18} />
                                    </IconButton>
                                  </Tooltip>
                                </Stack>
                              </Stack>

                              <Stack
                                direction="row"
                                alignItems="center"
                                spacing={2}
                                sx={{
                                  flexWrap: "nowrap",
                                  pt: 0.75,
                                  mt: "auto",
                                  borderTop: 1,
                                  borderColor: "divider",
                                  typography: "caption",
                                  color: "text.secondary",
                                }}
                              >
                                <Box
                                  component="span"
                                  sx={{
                                    display: "inline-flex",
                                    alignItems: "center",
                                    gap: 0.5,
                                    whiteSpace: "nowrap",
                                    flexShrink: 0,
                                  }}
                                >
                                  <Clock size={12} aria-hidden />
                                  {lane.startTime || "—"}
                                </Box>
                                <Box
                                  component="span"
                                  sx={{
                                    display: "inline-flex",
                                    alignItems: "center",
                                    gap: 0.5,
                                    whiteSpace: "nowrap",
                                    flexShrink: 0,
                                  }}
                                >
                                  <MapPin size={12} aria-hidden />
                                  {t("lane_shopCountInline", {
                                    count: lane.shops.length,
                                  })}
                                </Box>
                              </Stack>
                            </Paper>
                          );
                        })}
                      </Stack>
                    </Card>
                  );
                })}
              </Box>
            </Box>
          ) : (
            <Stack
              direction="row"
              spacing={2}
              alignItems="flex-start"
              sx={{ width: "max-content" }}
            >
              {filteredLanes
                .filter((lane) =>
                  lanesMatchingFloorOnly.some((v) => v.id === lane.id),
                )
                .map((lane) => {
                  const districtSections = buildLaneDistrictSections(
                    lane.shops,
                    pendingEmptyDistrictsByLane[lane.id],
                  );
                  return (
                  <Card
                    key={lane.id}
                    data-lane-id={lane.id}
                    onDragOver={(e) => {
                      handleDragOver(e);
                      if (!draggedRef.current) return;
                      const beforeShopId = getBeforeShopIdByPointer(
                        lane.id,
                        e.clientY,
                      );
                      setDropIndicator((prev) => {
                        if (
                          prev?.laneId === lane.id &&
                          prev.beforeShopId === beforeShopId
                        )
                          return prev;
                        return { laneId: lane.id, beforeShopId };
                      });
                    }}
                    onDrop={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                      handleDropToLane(lane.id);
                    }}
                    sx={{
                      width: 360,
                      flexShrink: 0,
                      borderTop: "4px solid",
                      borderTopColor: "primary.main",
                      overflow: "hidden",
                    }}
                  >
                    {renderLaneHeader(lane)}

                    <Box
                      sx={{
                        p: 1.5,
                        maxHeight: "calc(100vh - 220px)",
                        overflow: "auto",
                      }}
                    >
                      {districtSections.map(
                        ({ district, shops, isPendingEmpty }) => (
                          <Box
                            key={`${lane.id}::${district}`}
                            onDragOver={(e) => {
                              e.preventDefault();
                              if (!draggedRef.current) return;
                              setDropIndicator({
                                laneId: lane.id,
                                beforeShopId: null,
                              });
                            }}
                            onDrop={(e) => {
                              e.preventDefault();
                              e.stopPropagation();
                              handleDropToPosition(lane.id, null, district);
                            }}
                            sx={{ mb: 2 }}
                          >
                            <Stack
                              direction="row"
                              spacing={0.5}
                              alignItems="center"
                              sx={{ px: 0.5, mb: 1, minWidth: 0 }}
                            >
                              <MapPin size={14} />
                              <Typography
                                variant="caption"
                                sx={{
                                  fontWeight: 900,
                                  color: "text.secondary",
                                  minWidth: 0,
                                }}
                                noWrap
                              >
                                {district}
                              </Typography>
                              <Box
                                sx={{
                                  flex: 1,
                                  height: 1,
                                  bgcolor: "grey.200",
                                  mx: 0.5,
                                }}
                              />
                              <Typography
                                variant="caption"
                                color="text.secondary"
                              >
                                {shops.length}
                              </Typography>
                              <Tooltip title={t("tooltip_editDistrict")}>
                                <span>
                                  <IconButton
                                    size="small"
                                    sx={{ p: 0.25 }}
                                    onMouseDown={(e) => e.stopPropagation()}
                                    onClick={(e) => {
                                      e.stopPropagation();
                                      openDistrictRename(lane.id, district);
                                    }}
                                    disabled={loading}
                                    aria-label={t("aria_editDistrict")}
                                  >
                                    <Pencil size={14} />
                                  </IconButton>
                                </span>
                              </Tooltip>
                              {isPendingEmpty && (
                                <Tooltip title={t("tooltip_removeEmptyDistrict")}>
                                  <span>
                                    <IconButton
                                      size="small"
                                      sx={{ p: 0.25 }}
                                      onMouseDown={(e) => e.stopPropagation()}
                                      onClick={(e) => {
                                        e.stopPropagation();
                                        removePendingEmptyDistrict(
                                          lane.id,
                                          district,
                                        );
                                      }}
                                      disabled={loading}
                                      aria-label={t("aria_removeEmptyDistrict")}
                                    >
                                      <X size={14} />
                                    </IconButton>
                                  </span>
                                </Tooltip>
                              )}
                            </Stack>

                            <Stack spacing={1}>
                              {shops.map((shop) => {
                                const changed = dirtyMoves.has(shop.id);
                                const isScheduledMove =
                                  shop.id > 0 && scheduledShopIdSet.has(shop.id);
                                const isScheduledLater =
                                  shop.id > 0 &&
                                  !isScheduledMove &&
                                  pendingScheduleShopIds.has(shop.id);
                                const isFailedScheduledMove =
                                  shop.id > 0 &&
                                  failedScheduleShopIds.has(shop.id);
                                const showInsertLine =
                                  dropIndicator != null &&
                                  dropIndicator.laneId === lane.id &&
                                  dropIndicator.beforeShopId === shop.id;
                                return (
                                  <Card
                                    key={shop.id}
                                    variant="outlined"
                                    data-shop-id={shop.id}
                                    draggable={shop.id > 0 && !isScheduledMove}
                                    onDragStart={() =>
                                      handleDragStart(shop.id, lane.id)
                                    }
                                    onDragEnd={() => clearDragState()}
                                    onDragOver={(e) => {
                                      e.preventDefault();
                                      e.stopPropagation();
                                      if (draggedRef.current)
                                        setDropIndicator({
                                          laneId: lane.id,
                                          beforeShopId: shop.id,
                                        });
                                    }}
                                    onDrop={(e) => {
                                      e.preventDefault();
                                      e.stopPropagation();
                                      handleDropToPosition(
                                        lane.id,
                                        shop.id,
                                        district,
                                      );
                                    }}
                                    sx={{
                                      cursor:
                                        shop.id > 0 && !isScheduledMove
                                          ? "grab"
                                          : "default",
                                      opacity: isScheduledMove ? 0.55 : 1,
                                      borderColor: changed
                                        ? "warning.main"
                                        : shop.id < 0
                                          ? "warning.light"
                                          : changedShopIds.has(shop.id)
                                            ? "info.main"
                                            : "divider",
                                      bgcolor: isScheduledMove
                                        ? "action.hover"
                                        : changed
                                          ? "warning.50"
                                          : shop.id < 0
                                            ? "grey.100"
                                            : changedShopIds.has(shop.id)
                                              ? "info.50"
                                              : "background.paper",
                                      "&:active":
                                        shop.id > 0
                                          ? { cursor: "grabbing" }
                                          : undefined,
                                      position: "relative",
                                    }}
                                  >
                                    {isFailedScheduledMove && (
                                      <Tooltip
                                        title={t("schedule_history_status_failed")}
                                      >
                                        <Box
                                          sx={{
                                            position: "absolute",
                                            top: 0,
                                            right: 0,
                                            p: 0.75,
                                            bgcolor: "error.main",
                                            color: "error.contrastText",
                                            borderBottomLeftRadius: 8,
                                            display: "flex",
                                            alignItems: "center",
                                            justifyContent: "center",
                                            zIndex: 2,
                                          }}
                                        >
                                          <AlertTriangle size={12} />
                                        </Box>
                                      </Tooltip>
                                    )}
                                    {isScheduledMove && !isFailedScheduledMove && (
                                      <Tooltip title={t("schedule_shop_locked")}>
                                        <Box
                                          sx={{
                                            position: "absolute",
                                            top: 0,
                                            right: 0,
                                            p: 0.75,
                                            bgcolor: "warning.main",
                                            color: "warning.contrastText",
                                            borderBottomLeftRadius: 8,
                                            display: "flex",
                                            alignItems: "center",
                                            justifyContent: "center",
                                            zIndex: 1,
                                          }}
                                        >
                                          <Clock size={12} />
                                        </Box>
                                      </Tooltip>
                                    )}
                                    {isScheduledLater &&
                                      !isFailedScheduledMove && (
                                        <Tooltip
                                          title={t("schedule_shop_scheduled")}
                                        >
                                          <Box
                                            sx={{
                                              position: "absolute",
                                              top: 0,
                                              right: 0,
                                              p: 0.75,
                                              bgcolor: "info.light",
                                              color: "info.contrastText",
                                              borderBottomLeftRadius: 8,
                                              display: "flex",
                                              alignItems: "center",
                                              justifyContent: "center",
                                              zIndex: 1,
                                            }}
                                          >
                                            <Clock size={12} />
                                          </Box>
                                        </Tooltip>
                                      )}
                                    {showInsertLine && (
                                      <Box
                                        sx={{
                                          position: "absolute",
                                          top: -1,
                                          left: 0,
                                          right: 0,
                                          height: 4,
                                          bgcolor: "primary.main",
                                          borderRadius: 1,
                                        }}
                                      />
                                    )}
                                    <CardContent
                                      sx={{
                                        py: 1.25,
                                        "&:last-child": { pb: 1.25 },
                                      }}
                                    >
                                      <Stack
                                        direction="row"
                                        justifyContent="space-between"
                                        alignItems="flex-start"
                                        spacing={1}
                                      >
                                        <Box sx={{ minWidth: 0 }}>
                                          <Typography
                                            variant="subtitle2"
                                            sx={{ fontWeight: 900 }}
                                            noWrap
                                          >
                                            {(() => {
                                              const codeLower = String(
                                                shop.shopCode || "",
                                              )
                                                .trim()
                                                .toLowerCase();
                                              const realName =
                                                shopNameByCodeMap.get(
                                                  codeLower,
                                                );
                                              return realName &&
                                                String(realName).trim() !== ""
                                                ? realName
                                                : shop.branchName || "-";
                                            })()}
                                          </Typography>
                                          <Typography
                                            variant="caption"
                                            color="text.secondary"
                                          >
                                            {formatShopCardSubtitle(shop)}
                                          </Typography>
                                          <Stack
                                            direction="row"
                                            alignItems="center"
                                            spacing={0.5}
                                          >
                                            <Typography
                                              variant="caption"
                                              sx={{
                                                display: "block",
                                                color: "text.secondary",
                                              }}
                                            >
                                              Seq: {shop.loadingSequence ?? "-"}
                                            </Typography>
                                            <Tooltip title={t("tooltip_editSeq")}>
                                              <span>
                                                <IconButton
                                                  size="small"
                                                  sx={{ p: 0.25 }}
                                                  onMouseDown={(e) =>
                                                    e.stopPropagation()
                                                  }
                                                  onClick={(e) => {
                                                    e.stopPropagation();
                                                    openSeqEdit(lane, shop);
                                                  }}
                                                  disabled={loading}
                                                  aria-label={t("aria_editSeq")}
                                                >
                                                  <Pencil size={12} />
                                                </IconButton>
                                              </span>
                                            </Tooltip>
                                          </Stack>
                                        </Box>
                                        <Stack
                                          direction="row"
                                          spacing={0.5}
                                          alignItems="center"
                                        >
                                          <Tooltip
                                            title={
                                              isScheduledMove
                                                ? t("schedule_shop_locked")
                                                : t("tooltip_removeFromLane")
                                            }
                                          >
                                            <span>
                                              <IconButton
                                                size="small"
                                                onMouseDown={(e) =>
                                                  e.stopPropagation()
                                                }
                                                onClick={(e) => {
                                                  e.stopPropagation();
                                                  void handleDeleteTruckRow(
                                                    shop.id,
                                                  );
                                                }}
                                                disabled={
                                                  loading ||
                                                  dirtyDeletes.has(shop.id) ||
                                                  isScheduledMove
                                                }
                                              >
                                                <Trash2 size={16} />
                                              </IconButton>
                                            </span>
                                          </Tooltip>
                                        </Stack>
                                      </Stack>
                                    </CardContent>
                                  </Card>
                                );
                              })}
                            </Stack>
                          </Box>
                        ),
                      )}

                      <Button
                        size="small"
                        variant="text"
                        startIcon={<Plus size={14} />}
                        onClick={() => openDistrictAdd(lane.id)}
                        disabled={loading}
                        sx={{ alignSelf: "flex-start", mb: 1 }}
                      >
                        {t("btn_addDistrict")}
                      </Button>

                      {dropIndicator != null &&
                        dropIndicator.laneId === lane.id &&
                        dropIndicator.beforeShopId == null &&
                        lane.shops.length > 0 && (
                          <Box
                            sx={{
                              mt: 1,
                              height: 8,
                              borderRadius: 1,
                              bgcolor: "primary.main",
                              opacity: 0.35,
                            }}
                          />
                        )}

                      {districtSections.length === 0 && (
                        <Box
                          sx={{
                            border: "2px dashed",
                            borderColor: "grey.300",
                            borderRadius: 2,
                            height: 120,
                            display: "flex",
                            alignItems: "center",
                            justifyContent: "center",
                            color: "text.secondary",
                          }}
                        >
                          <Stack alignItems="center" spacing={1}>
                            <TruckIcon size={28} />
                            <Typography variant="caption">
                              {t("empty_lane_noShops")}
                            </Typography>
                          </Stack>
                        </Box>
                      )}
                    </Box>

                    <Box
                      sx={{
                        p: 1.25,
                        borderTop: "1px solid",
                        borderColor: "divider",
                        bgcolor: "grey.50",
                      }}
                    >
                      <Stack direction="row" spacing={1}>
                        <Button
                          fullWidth
                          size="small"
                          variant="outlined"
                          startIcon={<Plus size={16} />}
                          onClick={() => openAddShopDialog(lane.id)}
                          disabled={loading}
                        >
                          {t("btn_addShopToLane")}
                        </Button>
                        <Tooltip
                          title={
                            lane.shops.some(
                              (s) =>
                                s.id > 0 && scheduledShopIdSet.has(s.id),
                            )
                              ? t("schedule_shop_locked")
                              : t("tooltip_clearLaneShops")
                          }
                        >
                          <span>
                            <IconButton
                              size="small"
                              onClick={() => handleClearLaneShops(lane)}
                              disabled={
                                loading ||
                                lane.shops.length === 0 ||
                                lane.shops.some(
                                  (s) =>
                                    s.id > 0 &&
                                    scheduledShopIdSet.has(s.id),
                                )
                              }
                            >
                              <Trash2 size={16} />
                            </IconButton>
                          </span>
                        </Tooltip>
                      </Stack>
                    </Box>
                  </Card>
                );
                })}

              <Card
                variant="outlined"
                sx={{
                  width: 56,
                  height: 56,
                  flexShrink: 0,
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  bgcolor: "background.paper",
                }}
              >
                <Tooltip title={t("tooltip_pickLane")}>
                  <span>
                    <IconButton
                      aria-label={t("aria_pickLane")}
                      onClick={(e) => setBoardQuickPickAnchorEl(e.currentTarget)}
                      disabled={loading || lanesMatchingFloorOnly.length === 0}
                    >
                      <Plus size={22} />
                    </IconButton>
                  </span>
                </Tooltip>
              </Card>
              <Popover
                open={boardQuickPickAnchorEl != null}
                anchorEl={boardQuickPickAnchorEl}
                onClose={() => {
                  setBoardQuickPickAnchorEl(null);
                  setBoardQuickPickSearch("");
                }}
                anchorOrigin={{ vertical: "center", horizontal: "right" }}
                transformOrigin={{ vertical: "center", horizontal: "left" }}
                slotProps={{
                  paper: {
                    sx: {
                      width: "min(100vw - 32px, 360px)",
                      maxHeight: 420,
                      overflow: "hidden",
                      display: "flex",
                      flexDirection: "column",
                    },
                  },
                }}
              >
                <Box sx={{ px: 1.5, py: 1, borderBottom: 1, borderColor: "divider" }}>
                  <TextField
                    size="small"
                    fullWidth
                    placeholder={t("lane_searchPh")}
                    value={boardQuickPickSearch}
                    onChange={(e) => setBoardQuickPickSearch(e.target.value)}
                    sx={{
                      "& .MuiOutlinedInput-root": {
                        borderRadius: 2,
                        bgcolor: "grey.50",
                      },
                      "& .MuiInputBase-input": {
                        textAlign: "left",
                        fontSize: "0.8125rem",
                        color: "text.secondary",
                        py: 0.75,
                      },
                      "& .MuiInputBase-input::placeholder": {
                        opacity: 1,
                        color: "text.disabled",
                      },
                    }}
                    InputProps={{
                      startAdornment: (
                        <Box
                          sx={{
                            mr: 0.5,
                            display: "inline-flex",
                            color: "text.disabled",
                          }}
                        >
                          <Search size={16} />
                        </Box>
                      ),
                    }}
                    inputProps={{ "aria-label": t("aria_searchLanes") }}
                    onKeyDown={(e) => e.stopPropagation()}
                  />
                </Box>
                <List dense sx={{ overflow: "auto", py: 0, flex: 1, minHeight: 0 }}>
                  {boardQuickPickFilteredLanes.length === 0 ? (
                    <Box sx={{ px: 2, py: 2 }}>
                      <Typography variant="body2" color="text.secondary">
                        {lanesMatchingFloorOnly.length === 0
                          ? t("quickPick_noLanes")
                          : t("quickPick_noKeyword")}
                      </Typography>
                    </Box>
                  ) : (
                    boardQuickPickFilteredLanes.map((lane) => {
                      const rem =
                        lane.remark != null &&
                        String(lane.remark).trim() !== ""
                          ? String(lane.remark).trim()
                          : null;
                      const picked = selectedLaneIds.includes(lane.id);
                      return (
                        <ListItemButton
                          key={lane.id}
                          selected={picked}
                          onClick={() => applyBoardQuickPickLane(lane.id)}
                          sx={{ alignItems: "flex-start", py: 1 }}
                        >
                          <ListItemText
                            primary={lane.truckLanceCode}
                            secondary={rem ?? undefined}
                            primaryTypographyProps={{
                              sx: { fontWeight: 800, fontSize: "0.9rem" },
                            }}
                            secondaryTypographyProps={{
                              sx: { fontSize: "0.72rem" },
                            }}
                          />
                        </ListItemButton>
                      );
                    })
                  )}
                </List>
              </Popover>
            </Stack>
          )}
        </Box>
      </Box>
    </Box>
  );
};

export default RouteBoard;
