"use client";

import { useState, useCallback, useEffect, useMemo } from "react";
import { cn } from "@/lib/utils";
import { DataGrid, type GridColumn } from "@/components/grid/data-grid";
import { SearchForm, SearchField } from "@/components/layout/search-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { CodeSelect } from "@/components/ui/code-select";
import Swal from "sweetalert2";
import {
  Menu, Shield, Users, Database, FileText, Activity,
  ChevronRight, Settings,
} from "lucide-react";

// admin/adminMainFS.do 대응 - 관리자 팝업 페이지
type AdminTab = "menu" | "auth" | "user" | "dept" | "supply" | "template" | "exchange" | "log-file" | "log-login" | "log-mail" | "ref-customer" | "ref-material" | "ref-car" | "ref-car-grade" | "ref-product-group" | "ref-product" | "spec-data-category" | "car-option";

const ADMIN_MENUS = [
  {
    label: "메뉴관리", icon: Menu,
    items: [{ key: "menu" as AdminTab, label: "메뉴관리" }],
  },
  {
    label: "권한 및 사용자 관리", icon: Shield,
    items: [
      { key: "auth" as AdminTab, label: "권한 관리" },
      { key: "dept" as AdminTab, label: "부서 관리" },
      { key: "user" as AdminTab, label: "사용자 관리" },
    ],
  },
  {
    label: "기준정보관리", icon: Database,
    items: [
      { key: "supply" as AdminTab, label: "공급업체관리" },
      { key: "template" as AdminTab, label: "템플릿 관리" },
      { key: "exchange" as AdminTab, label: "환율관리" },
    ],
  },
  {
    label: "기준정보 관리 (상세)", icon: Settings,
    items: [
      { key: "ref-customer" as AdminTab, label: "고객사 관리" },
      { key: "ref-material" as AdminTab, label: "재질 관리" },
      { key: "ref-car" as AdminTab, label: "차종 관리" },
      { key: "ref-car-grade" as AdminTab, label: "차종 Grade 관리" },
      { key: "ref-product-group" as AdminTab, label: "제품군 관리" },
      { key: "ref-product" as AdminTab, label: "제품 관리" },
      { key: "spec-data-category" as AdminTab, label: "기술자료 카테고리 관리" },
      { key: "car-option" as AdminTab, label: "차량옵션(사양) 관리" },
    ],
  },
  {
    label: "System Log", icon: Activity,
    items: [
      { key: "log-file" as AdminTab, label: "파일 다운로드 로그" },
      { key: "log-login" as AdminTab, label: "로그인 로그" },
      { key: "log-mail" as AdminTab, label: "메일발송 로그" },
    ],
  },
];

// 메뉴 라벨 → 탭 키 매핑 (menu_info의 menu_name_kor 와 매칭)
const LABEL_TO_TAB: Record<string, AdminTab> = {
  "메뉴관리": "menu",
  "권한 관리": "auth",
  "부서 관리": "dept",
  "사용자 관리": "user",
  "공급업체관리": "supply",
  "템플릿 관리": "template",
  "환율관리": "exchange",
  "고객사 관리": "ref-customer",
  "재질 관리": "ref-material",
  "차종 관리": "ref-car",
  "차종 Grade 관리": "ref-car-grade",
  "제품군 관리": "ref-product-group",
  "제품 관리": "ref-product",
  "기술자료 카테고리 관리": "spec-data-category",
  "차량옵션(사양) 관리": "car-option",
  "파일 다운로드 로그": "log-file",
  "로그인 로그": "log-login",
  "메일발송 로그": "log-mail",
};

const SECTION_ICONS: Record<string, React.ComponentType<{ size?: number; className?: string }>> = {
  "메뉴관리": Menu,
  "권한 및 사용자 관리": Shield,
  "기준정보관리": Database,
  "기준정보 관리 (상세)": Settings,
  "System Log": Activity,
};

interface SidebarGroup {
  objid: string;
  label: string;
  items: { objid: string; label: string; url: string }[];
}

const VALID_TABS: AdminTab[] = [
  "menu","auth","user","dept","supply","template","exchange",
  "log-file","log-login","log-mail",
  "ref-customer","ref-material","ref-car","ref-car-grade",
  "ref-product-group","ref-product","spec-data-category","car-option",
];

function readTabFromUrl(): AdminTab {
  if (typeof window === "undefined") return "user";
  const t = new URLSearchParams(window.location.search).get("tab");
  return t && (VALID_TABS as string[]).includes(t) ? (t as AdminTab) : "user";
}

export default function AdminPanelPage() {
  const [activeTab, setActiveTab] = useState<AdminTab>("user");
  const [groups, setGroups] = useState<SidebarGroup[]>([]);
  const [openSections, setOpenSections] = useState<Set<string>>(new Set(["권한 및 사용자 관리"]));

  // 마운트 + popstate(뒤로가기) 시 ?tab= 으로 activeTab 동기화.
  // useSearchParams 는 Next.js 15 의 prerender 단계에서 Suspense 경계를 강제하므로 사용하지 않음.
  useEffect(() => {
    setActiveTab(readTabFromUrl());
    const sync = () => setActiveTab(readTabFromUrl());
    window.addEventListener("popstate", sync);
    return () => window.removeEventListener("popstate", sync);
  }, []);

  const toggleSection = (label: string) => {
    setOpenSections((prev) => {
      const next = new Set(prev);
      if (next.has(label)) next.delete(label);
      else next.add(label);
      return next;
    });
  };

  // menu_info 기반 동적 사이드바 (status='active' 인 메뉴만)
  useEffect(() => {
    fetch("/api/admin/sidebar-menus")
      .then((r) => r.json())
      .then((j) => setGroups(j.groups || []))
      .catch(() => setGroups([]));
  }, []);

  return (
    <div className="flex h-screen bg-gray-100">
      {/* 좌측 메뉴 (adminMenu.jsp 대응) */}
      <aside className="w-[220px] bg-[#2a2a2a] text-gray-300 flex flex-col shrink-0">
        <div className="px-4 py-3 border-b border-white/10 flex items-center justify-between gap-2">
          <h1 className="text-white font-bold text-sm flex items-center gap-2">
            <Shield size={16} className="text-orange-400" />
            관리자
          </h1>
          <a
            href="/dashboard"
            className="text-[10px] text-gray-400 hover:text-white hover:underline"
            title="일반 사용자 메뉴로 돌아가기"
          >
            ← 사용자
          </a>
        </div>
        <nav className="flex-1 overflow-y-auto py-2">
          {/* ★ 메뉴 관리 — DB 상태와 무관하게 항상 고정 노출 */}
          <div className="border-b border-white/10 pb-1 mb-1">
            <div className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase tracking-wider">메뉴관리</div>
            <button
              onClick={() => setActiveTab("menu")}
              className={cn(
                "w-full text-left pl-6 pr-3 py-1.5 text-[11px] transition-colors flex items-center gap-2",
                activeTab === "menu" ? "text-white bg-[#1C90FB]" : "text-gray-400 hover:text-white hover:bg-white/5"
              )}
            >
              <Menu size={12} />
              메뉴 관리
            </button>
          </div>

          {/* DB 기반 동적 메뉴 (없으면 정적 ADMIN_MENUS 폴백)
              "메뉴관리" 카테고리는 위에서 항상 고정 노출하므로 DB groups 에서 중복 제거 */}
          {(groups.length > 0 ? groups : ADMIN_MENUS.slice(1).map((g, i) => ({
            objid: String(i),
            label: g.label,
            items: g.items.map((it, j) => ({ objid: String(j), label: it.label, url: "" })),
          })))
            .filter((section) => section.label.replace(/\s+/g, "") !== "메뉴관리")
            .map((section) => {
            const isOpen = openSections.has(section.label);
            const Icon = SECTION_ICONS[section.label] || FileText;
            return (
              <div key={section.objid}>
                <button
                  onClick={() => toggleSection(section.label)}
                  className="w-full flex items-center px-3 py-2 text-[12px] font-semibold hover:bg-white/5 hover:text-white transition-colors"
                >
                  <Icon size={13} className="mr-2 text-gray-500" />
                  <span className="flex-1 text-left">{section.label}</span>
                  <ChevronRight size={10} className={cn("transition-transform", isOpen && "rotate-90")} />
                </button>
                {isOpen && (
                  <div className="py-0.5">
                    {section.items.map((item) => {
                      const tabKey = LABEL_TO_TAB[item.label] as AdminTab | undefined;
                      const onClick = () => {
                        if (tabKey) setActiveTab(tabKey);
                        else if (item.url) window.location.href = item.url;
                      };
                      return (
                        <button
                          key={item.objid}
                          onClick={onClick}
                          className={cn(
                            "w-full text-left pl-9 pr-3 py-1.5 text-[11px] transition-colors",
                            tabKey && activeTab === tabKey
                              ? "text-white bg-[#1C90FB]"
                              : "text-gray-400 hover:text-white hover:bg-white/5"
                          )}
                        >
                          {item.label}
                        </button>
                      );
                    })}
                  </div>
                )}
              </div>
            );
          })}
        </nav>
      </aside>

      {/* 우측 콘텐츠 */}
      <main className="flex-1 overflow-auto p-4">
        {activeTab === "user" && <UserManagement />}
        {activeTab === "menu" && <MenuManagement />}
        {activeTab === "auth" && <AuthManagement />}
        {activeTab === "dept" && <DeptManagement />}
        {activeTab === "supply" && <SupplyManagement />}
        {activeTab === "log-login" && <LoginLogPage />}
        {activeTab === "log-file" && <FileDownloadLogPage />}
        {activeTab === "log-mail" && <MailSendLogPage />}
        {activeTab === "template" && <TemplateManagement />}
        {activeTab === "exchange" && <ExchangeRateManagement />}
        {activeTab === "ref-customer" && <CustomerManagement />}
        {activeTab === "ref-material" && <MaterialManagement />}
        {activeTab === "ref-car" && <CarManagement />}
        {activeTab === "ref-product-group" && <ProductGroupManagement />}
        {activeTab === "ref-product" && <ProductManagement />}
        {activeTab === "spec-data-category" && <SpecDataCategoryManagement />}
        {activeTab === "car-option" && <CarOptionManagement />}
        {/* 기타 탭은 공통 Placeholder (DB 테이블 없음) */}
        {!["user","menu","auth","dept","supply","log-login","log-file","log-mail","template","exchange","ref-customer","ref-material","ref-car","ref-product-group","ref-product","spec-data-category","car-option"].includes(activeTab) && (
          <PlaceholderContent title={groups.flatMap(g => g.items).find(i => LABEL_TO_TAB[i.label] === activeTab)?.label || activeTab} />
        )}
      </main>
    </div>
  );
}

// ==========================================
// 사용자 관리 (admin/user/userList.jsp 대응)
// ==========================================
function UserManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchDept, setSearchDept] = useState("");
  const [searchType, setSearchType] = useState("");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const [selectedRows, setSelectedRows] = useState<Record<string, unknown>[]>([]);

  const yn = (v: unknown) => v === "Y" ? "✅" : "—";
  const columns: GridColumn[] = [
    { title: "부서명", field: "DEPT_NAME", width: 110 },
    { title: "사용자 ID", field: "USER_ID", width: 110, cellClick: (row) => openUserForm(String(row.USER_ID)) },
    { title: "사용자명", field: "USER_NAME", width: 100, hozAlign: "center" },
    { title: "전화번호", field: "CELL_PHONE", width: 120 },
    { title: "이메일", field: "EMAIL", width: 160 },
    { title: "발주한도무시", field: "UNLIMITED_QTY", width: 110, hozAlign: "center", formatter: (cell) => yn(cell) },
    { title: "숨김품목보기", field: "VIEW_HIDDEN", width: 110, hozAlign: "center", formatter: (cell) => yn(cell) },
    { title: "등록일", field: "REGDATE", width: 100, hozAlign: "center" },
    { title: "상태", field: "STATUS", width: 70, hozAlign: "center" },
  ];

  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ user_name: searchName, dept_name: searchDept, user_type: searchType }),
    });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchDept, searchType]);

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

  const openUserForm = (userId?: string) => {
    window.open(
      `/admin-panel/user-form${userId ? `?userId=${userId}` : "?actionType=regist"}`,
      "userFormPopup", "width=500,height=620"
    );
  };

  const handleDelete = async () => {
    if (selectedRows.length === 0) {
      Swal.fire({ icon: "warning", title: "삭제할 사용자를 선택하세요." });
      return;
    }
    const r = await Swal.fire({
      icon: "warning", title: `${selectedRows.length}명 비활성화`,
      text: "선택한 사용자를 비활성 상태로 전환합니다.",
      showCancelButton: true, confirmButtonText: "삭제", cancelButtonText: "취소",
      confirmButtonColor: "#dc2626",
    });
    if (!r.isConfirmed) return;
    const userIds = selectedRows.map((row) => String(row.USER_ID)).filter(Boolean);
    const res = await fetch("/api/admin/users/delete", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ userIds }),
    });
    const j = await res.json();
    if (j.success) {
      Swal.fire({ icon: "success", title: `${j.count}명 비활성화 완료`, timer: 1200, showConfirmButton: false });
      setSelectedRows([]); fetchData();
    } else {
      Swal.fire({ icon: "error", title: "오류", text: j.message });
    }
  };

  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">사용자 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="구분">
          <select value={searchType} onChange={(e) => setSearchType(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="">전체</option>
            <option value="A">관리자</option>
            <option value="U">사용자</option>
          </select>
        </SearchField>
        <SearchField label="부서명">
          <Input value={searchDept} onChange={(e) => setSearchDept(e.target.value)} className="w-[120px]" />
        </SearchField>
        <SearchField label="사용자명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[120px]" />
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => openUserForm()}>사용자 등록</Button>
        <Button size="sm" variant="danger" onClick={handleDelete}>삭제</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} showCheckbox onSelectionChange={setSelectedRows} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 메뉴 관리 (트리구조 CRUD)
// ==========================================
interface MenuNode {
  OBJID: string; MENU_NAME_KOR: string; MENU_NAME_ENG: string;
  MENU_URL: string; PARENT_OBJ_ID: string; SEQ: string; STATUS: string;
  children: MenuNode[];
}

function MenuManagement() {
  const [tree, setTree] = useState<MenuNode[]>([]);
  const [flatAll, setFlatAll] = useState<MenuNode[]>([]);
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
  const [selected, setSelected] = useState<MenuNode | null>(null);
  const [editForm, setEditForm] = useState<Record<string, string>>({});
  const [saving, setSaving] = useState(false);
  const [showForm, setShowForm] = useState(false);

  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/menus", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) });
    if (!res.ok) return;
    const json = await res.json();
    const flat: MenuNode[] = (json.RESULTLIST || []).map((r: Record<string, unknown>) => ({
      OBJID: String(r.OBJID || ""), MENU_NAME_KOR: String(r.MENU_NAME_KOR || ""),
      MENU_NAME_ENG: String(r.MENU_NAME_ENG || ""), MENU_URL: String(r.MENU_URL || ""),
      PARENT_OBJ_ID: String(r.PARENT_OBJ_ID || "0"), SEQ: String(r.SEQ || "0"),
      STATUS: String(r.STATUS || ""), children: [],
    }));
    const map = new Map<string, MenuNode>();
    flat.forEach((n) => map.set(n.OBJID, n));
    const roots: MenuNode[] = [];
    flat.forEach((n) => {
      const parent = map.get(n.PARENT_OBJ_ID);
      if (parent) parent.children.push(n);
      else roots.push(n);
    });
    const sortNodes = (nodes: MenuNode[]) => {
      nodes.sort((a, b) => parseInt(a.SEQ || "0") - parseInt(b.SEQ || "0"));
      nodes.forEach((n) => sortNodes(n.children));
    };
    sortNodes(roots);
    setTree(roots);
    setFlatAll(flat);
    setExpanded(new Set(roots.map((r) => r.OBJID)));
  }, []);

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

  const parentNameMap = useMemo(() => {
    const m = new Map<string, string>();
    flatAll.forEach((n) => m.set(n.OBJID, n.MENU_NAME_KOR));
    return m;
  }, [flatAll]);

  // 트리를 확장 상태에 맞게 평탄화 (depth 포함)
  const visibleRows = useMemo(() => {
    const out: { node: MenuNode; depth: number; hasChildren: boolean }[] = [];
    const walk = (nodes: MenuNode[], depth: number) => {
      nodes.forEach((n) => {
        const hasChildren = n.children.length > 0;
        out.push({ node: n, depth, hasChildren });
        if (expanded.has(n.OBJID)) walk(n.children, depth + 1);
      });
    };
    walk(tree, 0);
    return out;
  }, [tree, expanded]);

  const toggleExpand = (id: string) => {
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const expandAll = () => setExpanded(new Set(flatAll.map((n) => n.OBJID)));
  const collapseAll = () => setExpanded(new Set(tree.map((r) => r.OBJID)));

  const openEdit = (node: MenuNode) => {
    setSelected(node);
    setEditForm({
      objid: node.OBJID, menu_name_kor: node.MENU_NAME_KOR,
      menu_name_eng: node.MENU_NAME_ENG, menu_url: node.MENU_URL,
      parent_obj_id: node.PARENT_OBJ_ID, seq: node.SEQ, status: node.STATUS,
    });
    setShowForm(true);
  };

  const handleNew = (parentId: string = "0") => {
    setSelected(null);
    setEditForm({ menu_name_kor: "", menu_name_eng: "", menu_url: "", parent_obj_id: parentId, seq: "0", status: "active", actionType: "regist" });
    setShowForm(true);
  };

  const handleSave = async () => {
    if (!editForm.menu_name_kor) { Swal.fire("알림", "메뉴명을 입력하세요.", "warning"); return; }
    setSaving(true);
    try {
      const res = await fetch("/api/admin/menus/save", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify(editForm),
      });
      const data = await res.json();
      if (data.success) {
        Swal.fire({ icon: "success", title: data.message, timer: 1200, showConfirmButton: false });
        setShowForm(false);
        fetchData();
      } else { Swal.fire("오류", data.message, "error"); }
    } catch { Swal.fire("오류", "서버 오류", "error"); }
    finally { setSaving(false); }
  };

  const handleDelete = async () => {
    if (!selected) { Swal.fire("알림", "삭제할 메뉴를 선택하세요.", "warning"); return; }
    const r = await Swal.fire({ title: "삭제 확인", text: `"${selected.MENU_NAME_KOR}" 메뉴를 삭제하시겠습니까?`, icon: "warning", showCancelButton: true, confirmButtonText: "삭제", cancelButtonText: "취소" });
    if (!r.isConfirmed) return;

    const callDelete = async (cascade: boolean) => {
      const res = await fetch("/api/admin/menus/delete", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ objid: selected.OBJID, cascade }),
      });
      return res.json();
    };

    let data = await callDelete(false);
    if (!data.success && data.needsCascade) {
      const r2 = await Swal.fire({
        icon: "warning",
        title: `하위 메뉴 ${data.childCount}개 함께 삭제`,
        text: "선택한 메뉴와 모든 하위 메뉴를 함께 삭제합니다. 계속하시겠습니까?",
        showCancelButton: true, confirmButtonText: "함께 삭제", cancelButtonText: "취소",
        confirmButtonColor: "#dc2626",
      });
      if (!r2.isConfirmed) return;
      data = await callDelete(true);
    }

    if (data.success) {
      Swal.fire({ icon: "success", title: `${data.deleted ?? 1}개 메뉴 삭제됨`, timer: 1500, showConfirmButton: false });
      setSelected(null); setEditForm({}); setShowForm(false);
      fetchData();
    } else { Swal.fire("오류", data.message, "error"); }
  };

  const set = (k: string, v: string) => setEditForm((p) => ({ ...p, [k]: v }));

  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">메뉴 관리</h2>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" variant="secondary" onClick={expandAll}>전체 펼치기</Button>
        <Button size="sm" variant="secondary" onClick={collapseAll}>전체 접기</Button>
        <Button size="sm" onClick={() => handleNew("0")}>최상위 메뉴 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>

      <div className="border border-gray-200 rounded bg-white overflow-auto" style={{ maxHeight: "calc(100vh - 220px)" }}>
        <table className="w-full text-sm">
          <thead className="sticky top-0 bg-gray-100 z-10">
            <tr className="border-b">
              <th className="p-2 text-left w-[420px] font-medium text-gray-700">메뉴명</th>
              <th className="p-2 text-left w-[180px] font-medium text-gray-700">상위메뉴</th>
              <th className="p-2 text-left font-medium text-gray-700">URL</th>
              <th className="p-2 text-center w-[70px] font-medium text-gray-700">순서</th>
              <th className="p-2 text-center w-[80px] font-medium text-gray-700">활성화</th>
              <th className="p-2 text-center w-[110px] font-medium text-gray-700">작업</th>
            </tr>
          </thead>
          <tbody>
            {visibleRows.map(({ node, depth, hasChildren }) => {
              const isSel = selected?.OBJID === node.OBJID;
              const isExp = expanded.has(node.OBJID);
              const parentName = node.PARENT_OBJ_ID === "0" ? "-" : parentNameMap.get(node.PARENT_OBJ_ID) || "-";
              return (
                <tr
                  key={node.OBJID}
                  className={cn("border-t cursor-pointer", isSel ? "bg-blue-50" : "hover:bg-gray-50")}
                  onClick={() => openEdit(node)}
                >
                  <td className="p-1.5">
                    <div className="flex items-center" style={{ paddingLeft: `${depth * 18}px` }}>
                      {hasChildren ? (
                        <button
                          onClick={(e) => { e.stopPropagation(); toggleExpand(node.OBJID); }}
                          className="w-5 h-5 flex items-center justify-center mr-1 text-gray-400 hover:text-gray-700 text-[10px]"
                        >
                          {isExp ? "▼" : "▶"}
                        </button>
                      ) : (
                        <span className="w-5 h-5 mr-1 inline-flex items-center justify-center text-gray-300">·</span>
                      )}
                      <span className={cn("truncate", depth === 0 && "font-bold text-gray-900")}>
                        {node.MENU_NAME_KOR}
                      </span>
                    </div>
                  </td>
                  <td className="p-1.5 text-gray-600 truncate">{parentName}</td>
                  <td className="p-1.5 text-xs text-gray-500 font-mono truncate">{node.MENU_URL || "-"}</td>
                  <td className="p-1.5 text-center text-gray-500">{node.SEQ}</td>
                  <td className="p-1.5 text-center">
                    {node.STATUS === "active" ? (
                      <span className="text-xs text-green-600">활성</span>
                    ) : (
                      <span className="text-xs text-red-500">비활성</span>
                    )}
                  </td>
                  <td className="p-1.5 text-center">
                    <button
                      onClick={(e) => { e.stopPropagation(); handleNew(node.OBJID); }}
                      className="text-[11px] px-2 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"
                    >
                      하위 추가
                    </button>
                  </td>
                </tr>
              );
            })}
            {visibleRows.length === 0 && (
              <tr><td colSpan={6} className="p-8 text-center text-gray-400">메뉴가 없습니다.</td></tr>
            )}
          </tbody>
        </table>
      </div>

      {showForm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
          <div className="bg-white rounded-lg shadow-xl w-[600px] p-6">
            <h3 className="text-base font-bold text-gray-800 mb-4 border-b pb-2">
              {editForm.actionType === "regist" ? "메뉴 등록" : `메뉴 수정 — ${editForm.menu_name_kor}`}
            </h3>
            <div className="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
              <div><label className="block text-xs font-medium text-gray-500 mb-1">메뉴명 (한글) <span className="text-red-400">*</span></label>
                <Input value={editForm.menu_name_kor || ""} onChange={(e) => set("menu_name_kor", e.target.value)} /></div>
              <div><label className="block text-xs font-medium text-gray-500 mb-1">메뉴명 (영문)</label>
                <Input value={editForm.menu_name_eng || ""} onChange={(e) => set("menu_name_eng", e.target.value)} /></div>
              <div className="col-span-2"><label className="block text-xs font-medium text-gray-500 mb-1">URL</label>
                <Input value={editForm.menu_url || ""} onChange={(e) => set("menu_url", e.target.value)} placeholder="/module/page.do" /></div>
              <div><label className="block text-xs font-medium text-gray-500 mb-1">정렬순서</label>
                <Input type="number" value={editForm.seq || "0"} onChange={(e) => set("seq", e.target.value)} /></div>
              <div><label className="block text-xs font-medium text-gray-500 mb-1">상태</label>
                <select value={editForm.status || "active"} onChange={(e) => set("status", e.target.value)}
                  className="h-9 w-full rounded border border-gray-300 bg-white px-2 text-sm">
                  <option value="active">활성</option>
                  <option value="inactive">비활성</option>
                </select></div>
              <div><label className="block text-xs font-medium text-gray-500 mb-1">상위메뉴</label>
                <Input value={editForm.parent_obj_id === "0" ? "(최상위)" : parentNameMap.get(editForm.parent_obj_id || "") || editForm.parent_obj_id || ""} readOnly className="bg-gray-50" /></div>
              {editForm.objid && (
                <div><label className="block text-xs font-medium text-gray-500 mb-1">OBJID</label>
                  <Input value={editForm.objid} readOnly className="bg-gray-50" /></div>
              )}
            </div>
            <div className="flex gap-2 mt-6 pt-4 border-t">
              <Button onClick={handleSave} disabled={saving}>{saving ? "저장 중..." : "저장"}</Button>
              {selected && <Button variant="danger" onClick={handleDelete}>삭제</Button>}
              <Button variant="secondary" onClick={() => setShowForm(false)}>닫기</Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ==========================================
// 권한 관리 — 좌(목록) / 중(있는·없는 직원) / 하(메뉴 트리) 통합 화면
// ==========================================

interface AuthGroup { OBJID: string; AUTH_NAME: string; AUTH_CODE: string; USER_CNT: number; STATUS: string }
interface AuthUserRow { USER_ID: string; USER_NAME: string; DEPT_NAME?: string; OBJID?: string }
interface MenuRow { OBJID: string; PARENT_OBJ_ID: string; MENU_NAME_KOR: string; MENU_URL: string; SEQ: number; MENU_TYPE: number; STATUS: string }

function AuthManagement() {
  const [groups, setGroups] = useState<AuthGroup[]>([]);
  const [groupQuery, setGroupQuery] = useState("");
  const [activeGroup, setActiveGroup] = useState<AuthGroup | null>(null);

  // 좌측 권한 목록 검색
  const filteredGroups = useMemo(() => groups.filter((g) =>
    !groupQuery || g.AUTH_NAME.toLowerCase().includes(groupQuery.toLowerCase()) || g.AUTH_CODE?.toLowerCase().includes(groupQuery.toLowerCase())
  ), [groups, groupQuery]);

  // 권한 그룹 목록 조회
  const fetchGroups = useCallback(async () => {
    const res = await fetch("/api/admin/auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" });
    const j = await res.json();
    setGroups((j.RESULTLIST || []) as AuthGroup[]);
  }, []);

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

  // 권한 그룹 생성/이름 수정/삭제 (인라인)
  const onCreate = async () => {
    const r = await Swal.fire({
      title: "권한그룹 생성",
      html: `<input id="sw_name" class="swal2-input" placeholder="권한명 (예: 영업팀 권한)">`,
      showCancelButton: true, confirmButtonText: "생성",
      preConfirm: () => ({
        auth_name: (document.getElementById("sw_name") as HTMLInputElement).value,
        // auth_code 는 서버에서 자동 생성
      }),
    });
    if (!r.isConfirmed || !r.value?.auth_name) return;
    const res = await fetch("/api/admin/auth/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...r.value, status: "active", actionType: "regist" }) });
    const j = await res.json();
    if (j.success) { fetchGroups(); }
    else Swal.fire("오류", j.message ?? "생성 실패", "error");
  };

  const onRename = async (g: AuthGroup) => {
    const r = await Swal.fire({
      title: "권한 그룹 수정", icon: "info",
      html: `<input id="sw_name" class="swal2-input" value="${g.AUTH_NAME}">`,
      showCancelButton: true, showDenyButton: true, denyButtonText: "삭제", confirmButtonText: "저장",
      preConfirm: () => ({
        auth_name: (document.getElementById("sw_name") as HTMLInputElement).value,
        auth_code: g.AUTH_CODE ?? "", // 기존 코드 유지
      }),
    });
    if (r.isDenied) {
      const ok = await Swal.fire({ icon: "warning", title: "권한그룹 삭제", text: g.AUTH_NAME, showCancelButton: true });
      if (!ok.isConfirmed) return;
      const res = await fetch("/api/admin/auth/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ objid: g.OBJID }) });
      if ((await res.json()).success) { if (activeGroup?.OBJID === g.OBJID) setActiveGroup(null); fetchGroups(); }
      return;
    }
    if (!r.isConfirmed || !r.value?.auth_name) return;
    const res = await fetch("/api/admin/auth/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ objid: g.OBJID, ...r.value, status: g.STATUS }) });
    if ((await res.json()).success) fetchGroups();
  };

  return (
    <div className="space-y-3">
      <div>
        <h2 className="text-lg font-bold text-slate-900">권한 관리</h2>
        <p className="text-xs text-slate-500 mt-0.5">권한 그룹 선택 시 권한있는/없는 직원과 메뉴 권한이 로드되고, 체크 즉시 반영됩니다.</p>
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-[260px_1fr] gap-3">
        {/* 좌측: 권한 그룹 목록 */}
        <div className="bg-white border border-slate-200 rounded-xl flex flex-col">
          <div className="px-3 py-2 border-b border-slate-100 flex items-center justify-between">
            <div className="text-sm font-bold text-slate-700 inline-flex items-center gap-1.5"><Shield size={14} className="text-emerald-700" />권한 목록</div>
            <button onClick={onCreate} className="inline-flex items-center gap-1 h-7 px-2.5 rounded bg-emerald-600 text-white text-[11px] font-bold hover:bg-emerald-700"><span>+ 생성</span></button>
          </div>
          <div className="px-3 py-2 border-b border-slate-100">
            <input value={groupQuery} onChange={(e) => setGroupQuery(e.target.value)} placeholder="검색..." className="w-full h-8 px-2 text-xs border border-slate-200 rounded" />
          </div>
          <div className="flex-1 overflow-auto max-h-[calc(100vh-360px)] divide-y divide-slate-100">
            {filteredGroups.length === 0 ? (
              <div className="p-6 text-center text-xs text-slate-400">권한 그룹이 없습니다.</div>
            ) : filteredGroups.map((g) => (
              <button
                key={g.OBJID}
                onClick={() => setActiveGroup(g)}
                onDoubleClick={() => onRename(g)}
                className={cn("w-full text-left px-3 py-2 hover:bg-slate-50 transition-colors", activeGroup?.OBJID === g.OBJID && "bg-emerald-50/70 border-l-4 border-l-emerald-600")}
                title="더블클릭: 수정/삭제"
              >
                <div className="text-sm font-bold text-slate-800">{g.AUTH_NAME}</div>
              </button>
            ))}
          </div>
        </div>

        {/* 우측: 활성 그룹의 직원 + 메뉴 매핑 */}
        <div className="space-y-3">
          {activeGroup ? (
            <>
              <AuthGroupMembers group={activeGroup} onChanged={fetchGroups} />
              <AuthGroupMenus group={activeGroup} />
            </>
          ) : (
            <div className="bg-white border border-slate-200 rounded-xl p-12 text-center text-slate-400 text-sm">
              왼쪽에서 권한 그룹을 선택하거나 [+ 생성] 으로 새로 만드세요.
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// 직원 — 권한있는 / 권한없는 양쪽 패널
function AuthGroupMembers({ group, onChanged }: { group: AuthGroup; onChanged: () => void }) {
  const [members, setMembers] = useState<AuthUserRow[]>([]);
  const [available, setAvailable] = useState<AuthUserRow[]>([]);
  const [memberQ, setMemberQ] = useState("");
  const [availQ, setAvailQ] = useState("");
  const [chkMember, setChkMember] = useState<Set<string>>(new Set());
  const [chkAvail, setChkAvail] = useState<Set<string>>(new Set());

  const reload = useCallback(async () => {
    setChkMember(new Set()); setChkAvail(new Set());
    const [m, u] = await Promise.all([
      fetch("/api/admin/auth/members", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: group.OBJID }) }).then((r) => r.json()),
      fetch("/api/admin/auth/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: group.OBJID }) }).then((r) => r.json()),
    ]);
    setMembers((m.RESULTLIST || []) as AuthUserRow[]);
    setAvailable((u.RESULTLIST || []) as AuthUserRow[]);
  }, [group.OBJID]);

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

  const filteredMembers = useMemo(() => members.filter((m) => !memberQ || (m.USER_NAME ?? "").includes(memberQ) || (m.USER_ID ?? "").includes(memberQ) || (m.DEPT_NAME ?? "").includes(memberQ)), [members, memberQ]);
  const filteredAvail = useMemo(() => available.filter((m) => !availQ || (m.USER_NAME ?? "").includes(availQ) || (m.USER_ID ?? "").includes(availQ) || (m.DEPT_NAME ?? "").includes(availQ)), [available, availQ]);

  const toggleAllMember = (on: boolean) => setChkMember(on ? new Set(filteredMembers.map((m) => String(m.OBJID))) : new Set());
  const toggleAllAvail = (on: boolean) => setChkAvail(on ? new Set(filteredAvail.map((m) => String(m.USER_ID))) : new Set());

  const addSelected = async () => {
    const userIds = Array.from(chkAvail);
    if (userIds.length === 0) { Swal.fire("알림", "추가할 직원을 선택하세요.", "warning"); return; }
    const res = await fetch("/api/admin/auth/members/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: group.OBJID, userIds }) });
    const j = await res.json().catch(() => ({ success: false, message: "응답 파싱 실패" }));
    if (!j.success) { Swal.fire({ icon: "error", title: "추가 실패", text: j.message }); return; }
    await reload(); onChanged();
  };
  const removeSelected = async () => {
    const memberObjids = Array.from(chkMember);
    if (memberObjids.length === 0) { Swal.fire("알림", "제거할 직원을 선택하세요.", "warning"); return; }
    const res = await fetch("/api/admin/auth/members/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: group.OBJID, memberObjids }) });
    const j = await res.json().catch(() => ({ success: false, message: "응답 파싱 실패" }));
    if (!j.success) { Swal.fire({ icon: "error", title: "제거 실패", text: j.message }); return; }
    await reload(); onChanged();
  };

  return (
    <div className="grid grid-cols-1 lg:grid-cols-[1fr_auto_1fr] gap-3">
      {/* 권한있는 직원 */}
      <div className="bg-white border border-slate-200 rounded-xl flex flex-col">
        <div className="px-3 py-2 border-b border-slate-100 text-sm font-bold text-slate-700 inline-flex items-center gap-1.5">
          <Users size={14} className="text-emerald-700" /> 권한있는 직원 ({members.length})
        </div>
        <div className="p-2 flex items-center gap-2 border-b border-slate-100">
          <label className="text-xs inline-flex items-center gap-1.5">
            <input type="checkbox" onChange={(e) => toggleAllMember(e.target.checked)} className="w-4 h-4 accent-emerald-600" /> 전체선택
          </label>
          <input value={memberQ} onChange={(e) => setMemberQ(e.target.value)} placeholder="검색" className="ml-auto h-7 w-40 px-2 text-xs border border-slate-200 rounded" />
        </div>
        <div className="flex-1 overflow-auto max-h-[40vh]">
          <table className="w-full text-xs">
            <thead className="bg-slate-50 sticky top-0">
              <tr><th className="w-8 p-1.5"></th><th className="text-left p-1.5">부서</th><th className="text-left p-1.5">이름</th><th className="text-left p-1.5">ID</th></tr>
            </thead>
            <tbody>
              {filteredMembers.map((m) => {
                const id = String(m.OBJID);
                return (
                  <tr key={id} className="border-t hover:bg-slate-50">
                    <td className="text-center p-1.5"><input type="checkbox" checked={chkMember.has(id)} onChange={(e) => { const s = new Set(chkMember); if (e.target.checked) s.add(id); else s.delete(id); setChkMember(s); }} className="w-4 h-4 accent-emerald-600" /></td>
                    <td className="p-1.5">{m.DEPT_NAME || "-"}</td>
                    <td className="p-1.5 font-semibold">{m.USER_NAME}</td>
                    <td className="p-1.5 text-slate-400 font-mono">{m.USER_ID}</td>
                  </tr>
                );
              })}
              {filteredMembers.length === 0 && <tr><td colSpan={4} className="p-6 text-center text-slate-400">권한 그룹을 선택하세요</td></tr>}
            </tbody>
          </table>
        </div>
      </div>

      {/* 추가/제거 버튼 — 클릭 영역 확대 + cursor-pointer */}
      <div className="flex flex-row lg:flex-col items-center justify-center gap-3 px-2 lg:px-4 min-w-[120px]">
        <button
          type="button"
          onClick={addSelected}
          className="h-12 w-full lg:w-32 rounded-lg bg-emerald-600 text-white text-sm font-bold hover:bg-emerald-700 active:bg-emerald-800 cursor-pointer disabled:opacity-50 shadow-sm transition-colors"
        >
          ‹ 추가
        </button>
        <button
          type="button"
          onClick={removeSelected}
          className="h-12 w-full lg:w-32 rounded-lg border border-slate-300 bg-white text-slate-700 text-sm font-bold hover:bg-rose-50 hover:border-rose-300 hover:text-rose-700 active:bg-rose-100 cursor-pointer transition-colors"
        >
          제거 ›
        </button>
      </div>

      {/* 권한없는 직원 */}
      <div className="bg-white border border-slate-200 rounded-xl flex flex-col">
        <div className="px-3 py-2 border-b border-slate-100 text-sm font-bold text-slate-700 inline-flex items-center gap-1.5">
          <Users size={14} className="text-slate-400" /> 권한없는 직원 ({available.length})
        </div>
        <div className="p-2 flex items-center gap-2 border-b border-slate-100">
          <label className="text-xs inline-flex items-center gap-1.5">
            <input type="checkbox" onChange={(e) => toggleAllAvail(e.target.checked)} className="w-4 h-4 accent-emerald-600" /> 전체선택
          </label>
          <input value={availQ} onChange={(e) => setAvailQ(e.target.value)} placeholder="검색" className="ml-auto h-7 w-40 px-2 text-xs border border-slate-200 rounded" />
        </div>
        <div className="flex-1 overflow-auto max-h-[40vh]">
          <table className="w-full text-xs">
            <thead className="bg-slate-50 sticky top-0">
              <tr><th className="w-8 p-1.5"></th><th className="text-left p-1.5">부서</th><th className="text-left p-1.5">이름</th><th className="text-left p-1.5">ID</th></tr>
            </thead>
            <tbody>
              {filteredAvail.map((m) => {
                const id = String(m.USER_ID);
                return (
                  <tr key={id} className="border-t hover:bg-slate-50">
                    <td className="text-center p-1.5"><input type="checkbox" checked={chkAvail.has(id)} onChange={(e) => { const s = new Set(chkAvail); if (e.target.checked) s.add(id); else s.delete(id); setChkAvail(s); }} className="w-4 h-4 accent-emerald-600" /></td>
                    <td className="p-1.5">{m.DEPT_NAME || "-"}</td>
                    <td className="p-1.5 font-semibold">{m.USER_NAME}</td>
                    <td className="p-1.5 text-slate-400 font-mono">{m.USER_ID}</td>
                  </tr>
                );
              })}
              {filteredAvail.length === 0 && <tr><td colSpan={4} className="p-6 text-center text-slate-400">권한 그룹을 선택하세요</td></tr>}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

// 메뉴 트리 — 체크 즉시 토글
function AuthGroupMenus({ group }: { group: AuthGroup }) {
  const [menus, setMenus] = useState<MenuRow[]>([]);
  const [assigned, setAssigned] = useState<Set<string>>(new Set());
  const [pending, setPending] = useState<Set<string>>(new Set());

  const reload = useCallback(async () => {
    const res = await fetch("/api/admin/auth/menus", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: group.OBJID }) });
    const j = await res.json();
    setMenus((j.MENULIST || []) as MenuRow[]);
    setAssigned(new Set(((j.ASSIGNED || []) as string[]).map(String)));
  }, [group.OBJID]);

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

  // 트리 빌드
  const tree = useMemo(() => {
    const byParent = new Map<string, MenuRow[]>();
    for (const m of menus) {
      const p = String(m.PARENT_OBJ_ID ?? "");
      if (!byParent.has(p)) byParent.set(p, []);
      byParent.get(p)!.push(m);
    }
    const roots = menus.filter((m) => !m.PARENT_OBJ_ID || m.PARENT_OBJ_ID === "0" || m.PARENT_OBJ_ID === "" || !menus.some((x) => String(x.OBJID) === String(m.PARENT_OBJ_ID)));
    return { byParent, roots };
  }, [menus]);

  const toggle = async (menu: MenuRow) => {
    const id = String(menu.OBJID);
    const on = !assigned.has(id);
    setPending((p) => new Set(p).add(id));
    setAssigned((prev) => { const n = new Set(prev); if (on) n.add(id); else n.delete(id); return n; });
    try {
      const res = await fetch("/api/admin/auth/menus/toggle", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: group.OBJID, menuObjid: id, on }) });
      const j = await res.json();
      if (!j.success) {
        Swal.fire({ icon: "error", title: "메뉴 토글 실패", text: j.message ?? "" });
        // 롤백
        setAssigned((prev) => { const n = new Set(prev); if (on) n.delete(id); else n.add(id); return n; });
      }
    } finally {
      setPending((p) => { const n = new Set(p); n.delete(id); return n; });
    }
  };

  const renderNode = (m: MenuRow, depth: number): React.ReactNode => {
    const id = String(m.OBJID);
    const children = tree.byParent.get(id) || [];
    return (
      <div key={id}>
        <label className={cn("flex items-center gap-2 py-1 px-2 hover:bg-slate-50 cursor-pointer text-sm", depth > 0 && "pl-[" + (depth * 20 + 8) + "px]")} style={{ paddingLeft: depth * 20 + 8 }}>
          <input
            type="checkbox"
            checked={assigned.has(id)}
            disabled={pending.has(id)}
            onChange={() => toggle(m)}
            className="w-4 h-4 accent-emerald-600 cursor-pointer"
          />
          <span className={cn("font-medium", depth === 0 ? "text-slate-900 font-bold" : "text-slate-700")}>{m.MENU_NAME_KOR}</span>
          {m.MENU_URL && <span className="text-[10px] text-slate-400 font-mono">{m.MENU_URL}</span>}
          {m.STATUS !== "active" && <span className="text-[10px] text-rose-500">비활성</span>}
        </label>
        {children.map((c) => renderNode(c, depth + 1))}
      </div>
    );
  };

  return (
    <div className="bg-white border border-slate-200 rounded-xl">
      <div className="px-3 py-2 border-b border-slate-100">
        <div className="text-sm font-bold text-slate-700 inline-flex items-center gap-1.5"><Menu size={14} className="text-emerald-700" /> 메뉴 전체 트리구조</div>
        <p className="text-[11px] text-slate-400 mt-0.5">체크된 것들만 시스템에서 해당 메뉴가 노출됩니다 · 체크 즉시 서버 반영</p>
      </div>
      <div className="max-h-[40vh] overflow-auto py-1">
        {tree.roots.length === 0 ? (
          <div className="p-6 text-center text-slate-400 text-sm">메뉴가 없습니다.</div>
        ) : tree.roots.map((m) => renderNode(m, 0))}
      </div>
    </div>
  );
}

// 권한 관리 — 레거시 모달형 (단계적 deprecation)
function _AuthManagementLegacy() {
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const [selectedRows, setSelectedRows] = useState<Record<string, unknown>[]>([]);
  // 등록/수정 폼
  const [showForm, setShowForm] = useState(false);
  const [editForm, setEditForm] = useState<Record<string, string>>({});
  // 멤버 관리
  const [showMembers, setShowMembers] = useState(false);
  const [memberTarget, setMemberTarget] = useState<{ objid: string; name: string } | null>(null);
  const [members, setMembers] = useState<Record<string, unknown>[]>([]);
  const [availableUsers, setAvailableUsers] = useState<Record<string, unknown>[]>([]);
  const [selectedMembers, setSelectedMembers] = useState<Set<number>>(new Set());
  const [selectedUsers, setSelectedUsers] = useState<Set<number>>(new Set());
  const [userSearch, setUserSearch] = useState({ userName: "", deptName: "" });

  const columns: GridColumn[] = [
    { title: "권한그룹명", field: "AUTH_NAME", width: 200, cellClick: (row) => openDetail(row) },
    { title: "권한CODE", field: "AUTH_CODE", width: 120 },
    { title: "사용자수", field: "USER_CNT", width: 100, hozAlign: "center", cellClick: (row) => openMembers(row) },
    { title: "등록자", field: "WRITER", width: 100, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 120, hozAlign: "center" },
    { title: "상태", field: "STATUS", width: 80, hozAlign: "center" },
  ];

  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, []);

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

  const openDetail = (row: Record<string, unknown>) => {
    setEditForm({ objid: String(row.OBJID || ""), auth_name: String(row.AUTH_NAME || ""), auth_code: String(row.AUTH_CODE || ""), status: String(row.STATUS || "active") });
    setShowForm(true);
    setShowMembers(false);
  };

  const openNew = () => {
    setEditForm({ auth_name: "", auth_code: "", status: "active", actionType: "regist" });
    setShowForm(true);
    setShowMembers(false);
  };

  const handleSave = async () => {
    if (!editForm.auth_name) { Swal.fire("알림", "권한명을 입력하세요.", "warning"); return; }
    const res = await fetch("/api/admin/auth/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(editForm) });
    const json = await res.json();
    if (json.success) { Swal.fire({ icon: "success", title: json.message, timer: 1200, showConfirmButton: false }); setShowForm(false); fetchData(); }
    else Swal.fire("오류", json.message, "error");
  };

  const handleDelete = async (objid: string, name: string) => {
    const r = await Swal.fire({ title: "삭제 확인", text: `"${name}" 권한그룹을 삭제하시겠습니까?`, icon: "warning", showCancelButton: true, confirmButtonText: "삭제", cancelButtonText: "취소" });
    if (!r.isConfirmed) return;
    const res = await fetch("/api/admin/auth/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ objid }) });
    const json = await res.json();
    if (json.success) { Swal.fire({ icon: "success", title: json.message, timer: 1200, showConfirmButton: false }); setShowForm(false); fetchData(); }
    else Swal.fire("오류", json.message, "error");
  };

  const handleStatusChange = async (status: string) => {
    const objids = selectedRows.map((r) => r.OBJID).filter(Boolean);
    if (objids.length === 0) { Swal.fire("알림", "대상을 선택하세요.", "warning"); return; }
    const res = await fetch("/api/admin/auth/status", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ objids, status }) });
    const json = await res.json();
    if (json.success) { Swal.fire({ icon: "success", title: json.message, timer: 1200, showConfirmButton: false }); setSelectedRows([]); fetchData(); }
  };

  // === 멤버 관리 ===
  const openMembers = async (row: Record<string, unknown>) => {
    const objid = String(row.OBJID || "");
    const name = String(row.AUTH_NAME || "");
    setMemberTarget({ objid, name });
    setShowMembers(true);
    setShowForm(false);
    setSelectedMembers(new Set());
    setSelectedUsers(new Set());
    // 소속 멤버 조회
    const res = await fetch("/api/admin/auth/members", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: objid }) });
    if (res.ok) { const json = await res.json(); setMembers(json.RESULTLIST || []); }
  };

  const searchAvailableUsers = async () => {
    if (!memberTarget) return;
    const res = await fetch("/api/admin/auth/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...userSearch, masterObjid: memberTarget.objid }) });
    if (res.ok) { const json = await res.json(); setAvailableUsers(json.RESULTLIST || []); }
  };

  const addMembers = async () => {
    if (!memberTarget) return;
    const userIds = Array.from(selectedUsers).map((i) => availableUsers[i]?.USER_ID).filter(Boolean);
    if (userIds.length === 0) { Swal.fire("알림", "추가할 사용자를 선택하세요.", "warning"); return; }
    try {
      const res = await fetch("/api/admin/auth/members/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: memberTarget.objid, userIds }) });
      const json = await res.json().catch(() => ({ success: false, message: "응답 파싱 실패" }));
      if (json.success) {
        Swal.fire({ icon: "success", title: json.message, timer: 1500, showConfirmButton: false });
        setSelectedUsers(new Set());
        openMembers({ OBJID: memberTarget.objid, AUTH_NAME: memberTarget.name });
        searchAvailableUsers();
        fetchData();
      } else {
        Swal.fire({ icon: "error", title: "추가 실패", text: json.message || `HTTP ${res.status}` });
      }
    } catch (err) {
      Swal.fire({ icon: "error", title: "추가 실패", text: err instanceof Error ? err.message : String(err) });
    }
  };

  const removeMembers = async () => {
    if (!memberTarget) return;
    const memberObjids = Array.from(selectedMembers).map((i) => members[i]?.OBJID).filter(Boolean);
    if (memberObjids.length === 0) { Swal.fire("알림", "제거할 멤버를 선택하세요.", "warning"); return; }
    try {
      const res = await fetch("/api/admin/auth/members/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ masterObjid: memberTarget.objid, memberObjids }) });
      const json = await res.json().catch(() => ({ success: false, message: "응답 파싱 실패" }));
      if (json.success) {
        Swal.fire({ icon: "success", title: json.message, timer: 1500, showConfirmButton: false });
        setSelectedMembers(new Set());
        openMembers({ OBJID: memberTarget.objid, AUTH_NAME: memberTarget.name });
        searchAvailableUsers();
        fetchData();
      } else {
        Swal.fire({ icon: "error", title: "제거 실패", text: json.message || `HTTP ${res.status}` });
      }
    } catch (err) {
      Swal.fire({ icon: "error", title: "제거 실패", text: err instanceof Error ? err.message : String(err) });
    }
  };

  const set = (k: string, v: string) => setEditForm((p) => ({ ...p, [k]: v }));

  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">권한 관리</h2>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={openNew}>권한그룹 등록</Button>
        <Button size="sm" variant="secondary" onClick={() => handleStatusChange("active")}>활성화</Button>
        <Button size="sm" variant="secondary" onClick={() => handleStatusChange("inActive")}>비활성화</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} showCheckbox height="calc(100vh - 260px)"
        onSelectionChange={setSelectedRows} />

      {/* 등록/수정 폼 */}
      {showForm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
          <div className="bg-white rounded-lg shadow-xl w-[420px] p-6">
            <h3 className="text-base font-bold mb-4 border-b pb-2">{editForm.actionType === "regist" ? "권한그룹 등록" : "권한그룹 수정"}</h3>
            <div className="space-y-3 text-sm">
              <div><label className="block text-xs font-medium text-gray-500 mb-1">권한명 <span className="text-red-400">*</span></label>
                <Input value={editForm.auth_name || ""} onChange={(e) => set("auth_name", e.target.value)} /></div>
              <div><label className="block text-xs font-medium text-gray-500 mb-1">권한CODE</label>
                <Input value={editForm.auth_code || ""} onChange={(e) => set("auth_code", e.target.value)} /></div>
              <div><label className="block text-xs font-medium text-gray-500 mb-1">상태</label>
                <select value={editForm.status || "active"} onChange={(e) => set("status", e.target.value)}
                  className="h-9 w-full rounded border border-gray-300 bg-white px-2 text-sm">
                  <option value="active">활성</option>
                  <option value="inActive">비활성</option>
                </select></div>
            </div>
            <div className="flex gap-2 mt-5 pt-3 border-t">
              <Button onClick={handleSave}>저장</Button>
              {editForm.objid && <Button variant="danger" onClick={() => handleDelete(editForm.objid, editForm.auth_name)}>삭제</Button>}
              <Button variant="secondary" onClick={() => setShowForm(false)}>닫기</Button>
            </div>
          </div>
        </div>
      )}

      {/* 멤버 관리 */}
      {showMembers && memberTarget && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
          <div className="bg-white rounded-lg shadow-xl w-[900px] h-[600px] flex flex-col">
            <div className="px-5 py-3 border-b flex items-center justify-between">
              <h3 className="text-base font-bold">소속인원 관리 — {memberTarget.name}</h3>
              <button onClick={() => setShowMembers(false)} className="text-gray-400 hover:text-gray-700 text-lg">&times;</button>
            </div>
            <div className="flex flex-1 overflow-hidden">
              {/* 좌: 소속 멤버 */}
              <div className="w-[42%] border-r flex flex-col p-3">
                <div className="text-xs font-semibold text-gray-500 mb-2">소속 멤버 ({members.length}명)</div>
                <div className="flex-1 overflow-auto border rounded">
                  <table className="w-full text-xs">
                    <thead className="bg-gray-50 sticky top-0">
                      <tr><th className="p-1.5 w-8"><input type="checkbox" onChange={(e) => {
                        setSelectedMembers(e.target.checked ? new Set(members.map((_, i) => i)) : new Set());
                      }} /></th><th className="p-1.5 text-left">부서</th><th className="p-1.5 text-left">이름</th><th className="p-1.5 text-left">ID</th></tr>
                    </thead>
                    <tbody>
                      {members.map((m, i) => (
                        <tr key={i} className="border-t hover:bg-gray-50">
                          <td className="p-1.5 text-center"><input type="checkbox" checked={selectedMembers.has(i)}
                            onChange={(e) => { const s = new Set(selectedMembers); if (e.target.checked) s.add(i); else s.delete(i); setSelectedMembers(s); }} /></td>
                          <td className="p-1.5">{String(m.DEPT_NAME || "")}</td>
                          <td className="p-1.5">{String(m.USER_NAME || "")}</td>
                          <td className="p-1.5 text-gray-400">{String(m.USER_ID || "")}</td>
                        </tr>
                      ))}
                      {members.length === 0 && <tr><td colSpan={4} className="p-4 text-center text-gray-400">소속 멤버가 없습니다.</td></tr>}
                    </tbody>
                  </table>
                </div>
              </div>

              {/* 중: 이동 버튼 */}
              <div className="w-[16%] flex flex-col items-center justify-center gap-3 px-2">
                <Button size="sm" onClick={addMembers} title="추가">&laquo; 추가</Button>
                <Button size="sm" variant="danger" onClick={removeMembers} title="제거">제거 &raquo;</Button>
              </div>

              {/* 우: 미소속 사용자 */}
              <div className="w-[42%] flex flex-col p-3">
                <div className="text-xs font-semibold text-gray-500 mb-2">사용자 검색</div>
                <div className="flex gap-1 mb-2">
                  <Input placeholder="부서명" className="text-xs h-7" value={userSearch.deptName}
                    onChange={(e) => setUserSearch((p) => ({ ...p, deptName: e.target.value }))} />
                  <Input placeholder="이름" className="text-xs h-7" value={userSearch.userName}
                    onChange={(e) => setUserSearch((p) => ({ ...p, userName: e.target.value }))} />
                  <Button size="sm" className="h-7 text-xs shrink-0" onClick={searchAvailableUsers}>조회</Button>
                </div>
                <div className="flex-1 overflow-auto border rounded">
                  <table className="w-full text-xs">
                    <thead className="bg-gray-50 sticky top-0">
                      <tr><th className="p-1.5 w-8"><input type="checkbox" onChange={(e) => {
                        setSelectedUsers(e.target.checked ? new Set(availableUsers.map((_, i) => i)) : new Set());
                      }} /></th><th className="p-1.5 text-left">부서</th><th className="p-1.5 text-left">이름</th><th className="p-1.5 text-left">ID</th></tr>
                    </thead>
                    <tbody>
                      {availableUsers.map((u, i) => (
                        <tr key={i} className="border-t hover:bg-gray-50">
                          <td className="p-1.5 text-center"><input type="checkbox" checked={selectedUsers.has(i)}
                            onChange={(e) => { const s = new Set(selectedUsers); if (e.target.checked) s.add(i); else s.delete(i); setSelectedUsers(s); }} /></td>
                          <td className="p-1.5">{String(u.DEPT_NAME || "")}</td>
                          <td className="p-1.5">{String(u.USER_NAME || "")}</td>
                          <td className="p-1.5 text-gray-400">{String(u.USER_ID || "")}</td>
                        </tr>
                      ))}
                      {availableUsers.length === 0 && <tr><td colSpan={4} className="p-4 text-center text-gray-400">조회 버튼을 클릭하세요.</td></tr>}
                    </tbody>
                  </table>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ==========================================
// 부서 관리
// ==========================================
function DeptManagement() {
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const [showForm, setShowForm] = useState(false);
  const [editForm, setEditForm] = useState<Record<string, string>>({});
  const [saving, setSaving] = useState(false);

  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/dept", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, []);

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

  const openNew = () => {
    setEditForm({ actionType: "regist", dept_code: "", dept_name: "", company_name: "", status: "" });
    setShowForm(true);
  };

  const openEdit = (row: Record<string, unknown>) => {
    setEditForm({
      actionType: "update",
      dept_code: String(row.DEPT_CODE || ""),
      dept_name: String(row.DEPT_NAME || ""),
      company_name: String(row.COMPANY_NAME || ""),
      status: String(row.STATUS || "active"),
    });
    setShowForm(true);
  };

  const columns: GridColumn[] = [
    { title: "부서명", field: "DEPT_NAME", width: 320, cellClick: (row) => openEdit(row) },
    { title: "활성화 여부", field: "STATUS_NAME", width: 120, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 130, hozAlign: "center" },
  ];

  const set = (k: string, v: string) => setEditForm((p) => ({ ...p, [k]: v }));

  const handleSave = async () => {
    if (!editForm.dept_name) {
      Swal.fire("알림", "부서명은 필수입니다.", "warning");
      return;
    }
    setSaving(true);
    try {
      const res = await fetch("/api/admin/dept/save", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(editForm),
      });
      const json = await res.json();
      if (json.success) {
        Swal.fire({ icon: "success", title: json.message, timer: 1200, showConfirmButton: false });
        setShowForm(false);
        fetchData();
      } else {
        Swal.fire("오류", json.message || "저장 실패", "error");
      }
    } catch {
      Swal.fire("오류", "서버 오류", "error");
    } finally {
      setSaving(false);
    }
  };

  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">부서 관리</h2>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={openNew}>부서 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 200px)" />

      {showForm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30">
          <div className="bg-white rounded-lg shadow-xl w-[560px] p-6">
            <h3 className="text-base font-bold mb-4 border-b pb-2">
              {editForm.actionType === "regist" ? "부서 등록" : `부서 수정`}
            </h3>
            <table className="w-full border-collapse text-sm">
              <tbody>
                <tr>
                  <th className="bg-[#5c6d97] text-white font-medium text-center py-2 w-[130px] border border-white">부서명 <span className="text-red-300">*</span></th>
                  <td className="border border-gray-200 p-1.5">
                    <Input value={editForm.dept_name || ""} onChange={(e) => set("dept_name", e.target.value)} />
                  </td>
                </tr>
                <tr>
                  <th className="bg-[#5c6d97] text-white font-medium text-center py-2 border border-white">활성화 여부</th>
                  <td className="border border-gray-200 p-1.5">
                    <select
                      value={editForm.status || ""}
                      onChange={(e) => set("status", e.target.value)}
                      className="h-9 w-full rounded border border-gray-300 bg-white px-2 text-sm"
                    >
                      <option value="">선택</option>
                      <option value="active">활성</option>
                      <option value="inActive">비활성</option>
                    </select>
                  </td>
                </tr>
              </tbody>
            </table>
            <div className="flex gap-2 justify-center mt-5 pt-3">
              <Button onClick={handleSave} disabled={saving}>{saving ? "저장 중..." : "저장"}</Button>
              {editForm.actionType !== "regist" && (
                <Button variant="danger" onClick={async () => {
                  const r = await Swal.fire({ icon: "warning", title: `"${editForm.dept_name}" 비활성화`, showCancelButton: true, confirmButtonText: "삭제", cancelButtonText: "취소", confirmButtonColor: "#dc2626" });
                  if (!r.isConfirmed) return;
                  const res = await fetch("/api/admin/dept/delete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ deptCode: editForm.dept_code }) });
                  const j = await res.json();
                  if (j.success) {
                    Swal.fire({ icon: "success", title: "삭제됨", timer: 1200, showConfirmButton: false });
                    setShowForm(false); fetchData();
                  } else { Swal.fire("오류", j.message, "error"); }
                }}>삭제</Button>
              )}
              <Button variant="secondary" onClick={() => setShowForm(false)}>닫기</Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ==========================================
// 공급업체 관리
// ==========================================
function SupplyManagement() {
  const [searchCode, setSearchCode] = useState("");
  const [searchName, setSearchName] = useState("");
  const [data, setData] = useState<Record<string, unknown>[]>([]);

  const openSupplyPopup = (objid: string = "") => {
    const q = objid ? `?objid=${encodeURIComponent(objid)}` : "";
    window.open(`/admin-panel/supply-form${q}`, "supplyFormPopup", "width=1300,height=780");
  };

  const columns: GridColumn[] = [
    { title: "구분", field: "SUPPLY_CODE_NAME", width: 100 },
    { title: "고객명", field: "SUPPLY_NAME", width: 200, cellClick: (row) => openSupplyPopup(String(row.OBJID || "")) },
    { title: "지역", field: "AREA_CD_NAME", width: 110 },
    { title: "대표자명", field: "CHARGE_USER_NAME", width: 100 },
    { title: "업종", field: "SUPPLY_STOCKNAME", width: 220 },
    { title: "업태", field: "SUPPLY_BUSNAME", width: 160 },
    { title: "주소", field: "SUPPLY_ADDRESS", width: 260 },
    { title: "핸드폰", field: "SUPPLY_TEL_NO", width: 140 },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
  ];

  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/supply", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ supply_code: searchCode, supply_name: searchName }),
    });
    if (res.ok) {
      const json = await res.json();
      setData(json.RESULTLIST || []);
    }
  }, [searchCode, searchName]);

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

  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">공급업체관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="공급업체">
          <CodeSelect codeId="0000167" value={searchCode} onChange={(v) => setSearchCode(v)} className="w-[180px]" />
        </SearchField>
        <SearchField label="고객명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[200px]" />
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => openSupplyPopup()}>공급업체 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <div className="text-xs text-gray-500 mb-2">총 {data.length}건</div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 310px)" />
    </div>
  );
}

// ==========================================
// 로그인 로그
// ==========================================
function LoginLogPage() {
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "로그시간", field: "LOG_TIME", width: 160 },
    { title: "사용자ID", field: "USER_ID", width: 120 },
    { title: "결과", field: "LOGIN_RESULT", width: 80, hozAlign: "center" },
    { title: "IP주소", field: "REMOTE_ADDR", width: 130 },
    { title: "에러메시지", field: "ERROR_MESSAGE", width: 250 },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/log-login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, []);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">로그인 로그</h2>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 200px)" />
    </div>
  );
}

// ==========================================
// 파일 다운로드 로그
// ==========================================
function FileDownloadLogPage() {
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "다운로드시간", field: "LOG_TIME", width: 160 },
    { title: "사용자", field: "USER_NAME", width: 100 },
    { title: "파일명", field: "REAL_FILE_NAME", width: 250 },
    { title: "문서유형", field: "DOC_TYPE_NAME", width: 150 },
    { title: "IP주소", field: "REMOTE_ADDR", width: 130 },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/log-file", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, []);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">파일 다운로드 로그</h2>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 200px)" />
    </div>
  );
}

// ==========================================
// 메일발송 로그 (admin/log/mailSendLogList.do)
// ==========================================
function MailSendLogPage() {
  const [searchTitle, setSearchTitle] = useState("");
  const [searchUser, setSearchUser] = useState("");
  const [searchType, setSearchType] = useState("");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "발송시간", field: "LOG_TIME", width: 160, hozAlign: "center" },
    { title: "시스템", field: "SYSTEM_NAME", width: 100, hozAlign: "center" },
    { title: "발신자", field: "SEND_USER_ID", width: 120 },
    { title: "수신자", field: "RECEIVER_TO", width: 200 },
    { title: "제목", field: "TITLE", width: 280 },
    { title: "유형", field: "MAIL_TYPE", width: 100, hozAlign: "center" },
    { title: "발송여부", field: "IS_SEND", width: 80, hozAlign: "center" },
    { title: "에러", field: "ERROR_LOG", width: 200 },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/log-mail", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: searchTitle, send_user_id: searchUser, mail_type: searchType }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchTitle, searchUser, searchType]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">메일발송 로그</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="제목">
          <Input value={searchTitle} onChange={(e) => setSearchTitle(e.target.value)} className="w-[180px]" />
        </SearchField>
        <SearchField label="발신자ID">
          <Input value={searchUser} onChange={(e) => setSearchUser(e.target.value)} className="w-[120px]" />
        </SearchField>
        <SearchField label="유형">
          <Input value={searchType} onChange={(e) => setSearchType(e.target.value)} className="w-[120px]" />
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 템플릿 관리 (admin/templateList.do)
// ==========================================
function TemplateManagement() {
  const [searchTitle, setSearchTitle] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "템플릿코드", field: "TEMPLATE_CODE", width: 150 },
    { title: "상세코드", field: "TEMPLATE_CODE_DETAIL", width: 150 },
    { title: "제목", field: "TITLE", width: 300 },
    { title: "등록일", field: "REG_DATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 80, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/template", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: searchTitle, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchTitle, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">템플릿 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="제목">
          <Input value={searchTitle} onChange={(e) => setSearchTitle(e.target.value)} className="w-[200px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>템플릿 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 환율관리 (admin/getExchangeRateMngList.do)
// ==========================================
function ExchangeRateManagement() {
  const [searchYm, setSearchYm] = useState("");
  const [searchFrom, setSearchFrom] = useState("");
  const [searchTo, setSearchTo] = useState("");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "년월", field: "YYYY_MM", width: 90, hozAlign: "center" },
    { title: "기준일자", field: "YYYY_MM_DD", width: 100, hozAlign: "center" },
    { title: "USD", field: "COST_USD", width: 90, hozAlign: "right" },
    { title: "EUR", field: "COST_EU", width: 90, hozAlign: "right" },
    { title: "JPY", field: "COST_JAPAN", width: 90, hozAlign: "right" },
    { title: "CNY", field: "COST_CHINA", width: 90, hozAlign: "right" },
    { title: "VND", field: "COST_VIETNAM", width: 90, hozAlign: "right" },
    { title: "RUB", field: "COST_RUB", width: 90, hozAlign: "right" },
    { title: "INR", field: "COST_INR", width: 90, hozAlign: "right" },
    { title: "THB", field: "COST_THB", width: 90, hozAlign: "right" },
    { title: "HKD", field: "COST_HKD", width: 90, hozAlign: "right" },
    { title: "등록자", field: "WRITER", width: 100 },
    { title: "API수집일", field: "API_DATE", width: 140, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/exchange", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ yyyy_mm: searchYm, from_date: searchFrom, to_date: searchTo }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchYm, searchFrom, searchTo]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">환율관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="년월">
          <Input value={searchYm} onChange={(e) => setSearchYm(e.target.value)} placeholder="YYYYMM" className="w-[120px]" />
        </SearchField>
        <SearchField label="시작">
          <Input value={searchFrom} onChange={(e) => setSearchFrom(e.target.value)} placeholder="YYYYMM" className="w-[120px]" />
        </SearchField>
        <SearchField label="종료">
          <Input value={searchTo} onChange={(e) => setSearchTo(e.target.value)} placeholder="YYYYMM" className="w-[120px]" />
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>환율 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 고객사 관리 (admin/oemMngPagingList.do)
// ==========================================
function CustomerManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "고객사코드", field: "OEM_CODE", width: 150 },
    { title: "고객사명", field: "OEM_NAME", width: 260 },
    { title: "등록자", field: "WRITER", width: 120, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/ref-customer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ oem_name: searchName, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">고객사 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="고객사명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[200px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>고객사 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 재질 관리 (admin/getMaterialMngList.do)
// ==========================================
function MaterialManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "재질명", field: "MATERIAL_NAME", width: 320 },
    { title: "등록자", field: "WRITER", width: 120, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/ref-material", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ material_name: searchName, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">재질 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="재질명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[200px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>재질 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 차종 관리 (admin/getCarMngList.do)
// ==========================================
function CarManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchCode, setSearchCode] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "차종코드", field: "CAR_CODE", width: 120 },
    { title: "모델코드", field: "MODEL_CODE", width: 120 },
    { title: "차종명", field: "CAR_NAME", width: 180 },
    { title: "고객사", field: "OEM_NAME", width: 140 },
    { title: "설명", field: "DESCRIPTION", width: 260 },
    { title: "등록자", field: "WRITER", width: 100, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/ref-car", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ car_name: searchName, car_code: searchCode, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchCode, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">차종 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="차종명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[180px]" />
        </SearchField>
        <SearchField label="차종코드">
          <Input value={searchCode} onChange={(e) => setSearchCode(e.target.value)} className="w-[140px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>차종 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 제품군 관리 (admin/getProductGroupMngList.do)
// ==========================================
function ProductGroupManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "제품군명", field: "PRODUCT_GROUP_NAME", width: 240 },
    { title: "설명", field: "DESCRIPTION", width: 320 },
    { title: "등록자", field: "WRITER", width: 120, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/ref-product-group", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ product_group_name: searchName, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">제품군 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="제품군명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[200px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>제품군 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 제품 관리 (admin/getProductMngList.do)
// ==========================================
function ProductManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchCode, setSearchCode] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "제품코드", field: "PRODUCT_CODE", width: 160 },
    { title: "제품명", field: "PRODUCT_NAME", width: 220 },
    { title: "제품군", field: "PRODUCT_GROUP_NAME", width: 160 },
    { title: "설명", field: "PRODUCT_DESC", width: 260 },
    { title: "등록자", field: "WRITER", width: 100, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/ref-product", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ product_name: searchName, product_code: searchCode, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchCode, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">제품 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="제품명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[180px]" />
        </SearchField>
        <SearchField label="제품코드">
          <Input value={searchCode} onChange={(e) => setSearchCode(e.target.value)} className="w-[140px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>제품 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 기술자료 카테고리 관리 (admin/specDataCategoryMngList.do)
// ==========================================
function SpecDataCategoryManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchType, setSearchType] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "카테고리명", field: "CATEGORY_NAME", width: 200 },
    { title: "유형", field: "CATEGORY_TYPE", width: 100, hozAlign: "center" },
    { title: "사양No", field: "SPEC_NO", width: 100 },
    { title: "사양입력유형", field: "SPEC_INPUT_TYPE", width: 130, hozAlign: "center" },
    { title: "문서번호규칙", field: "DOC_NO_RULE", width: 200 },
    { title: "순서", field: "SEQ", width: 70, hozAlign: "right" },
    { title: "등록자", field: "WRITER", width: 100, hozAlign: "center" },
    { title: "등록일", field: "REGDATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/spec-data-category", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ category_name: searchName, category_type: searchType, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchType, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">기술자료 카테고리 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="카테고리명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[180px]" />
        </SearchField>
        <SearchField label="유형">
          <Input value={searchType} onChange={(e) => setSearchType(e.target.value)} className="w-[120px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>카테고리 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// 차량옵션(사양) 관리 (admin/getCarOptionMngList.do)
// ==========================================
function CarOptionManagement() {
  const [searchName, setSearchName] = useState("");
  const [searchNo, setSearchNo] = useState("");
  const [searchType, setSearchType] = useState("");
  const [searchStatus, setSearchStatus] = useState("active");
  const [data, setData] = useState<Record<string, unknown>[]>([]);
  const columns: GridColumn[] = [
    { title: "옵션번호", field: "OPTION_NO", width: 130 },
    { title: "옵션명", field: "OPTION_NAME", width: 220 },
    { title: "영문옵션명", field: "OPTION_NAME_ENG", width: 200 },
    { title: "표준코드", field: "STD_CODE", width: 120 },
    { title: "옵션유형", field: "OPTION_TYPE", width: 100, hozAlign: "center" },
    { title: "카테고리", field: "CATEGORY", width: 120 },
    { title: "단가", field: "PRICE", width: 100, hozAlign: "right" },
    { title: "등록자", field: "WRITER", width: 100, hozAlign: "center" },
    { title: "등록일", field: "REG_DATE", width: 110, hozAlign: "center" },
    { title: "상태", field: "STATUS_STR", width: 90, hozAlign: "center" },
  ];
  const fetchData = useCallback(async () => {
    const res = await fetch("/api/admin/car-option", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ option_name: searchName, option_no: searchNo, option_type: searchType, status: searchStatus }) });
    if (res.ok) { const json = await res.json(); setData(json.RESULTLIST || []); }
  }, [searchName, searchNo, searchType, searchStatus]);

  useEffect(() => { fetchData(); }, [fetchData]);
  return (
    <div>
      <h2 className="text-lg font-bold text-gray-800 mb-4">차량옵션(사양) 관리</h2>
      <SearchForm onSearch={fetchData}>
        <SearchField label="옵션명">
          <Input value={searchName} onChange={(e) => setSearchName(e.target.value)} className="w-[180px]" />
        </SearchField>
        <SearchField label="옵션번호">
          <Input value={searchNo} onChange={(e) => setSearchNo(e.target.value)} className="w-[140px]" />
        </SearchField>
        <SearchField label="옵션유형">
          <Input value={searchType} onChange={(e) => setSearchType(e.target.value)} className="w-[120px]" />
        </SearchField>
        <SearchField label="상태">
          <select value={searchStatus} onChange={(e) => setSearchStatus(e.target.value)}
            className="h-9 w-[120px] rounded border border-gray-300 bg-white px-3 text-sm">
            <option value="active">활성화</option>
            <option value="inActive">비활성화</option>
            <option value="">전체</option>
          </select>
        </SearchField>
      </SearchForm>
      <div className="flex justify-end gap-2 mb-3">
        <Button size="sm" onClick={() => Swal.fire("알림", "준비 중입니다.", "info")}>옵션 등록</Button>
        <Button size="sm" variant="secondary" onClick={fetchData}>조회</Button>
      </div>
      <DataGrid columns={columns} data={data} height="calc(100vh - 300px)" />
    </div>
  );
}

// ==========================================
// Placeholder
// ==========================================
function PlaceholderContent({ title }: { title: string }) {
  return (
    <div className="flex items-center justify-center h-full">
      <div className="text-center text-gray-400">
        <Settings size={48} className="mx-auto mb-4 opacity-30" />
        <h3 className="text-lg font-semibold">{title}</h3>
        <p className="text-sm mt-2">구현 준비 중입니다.</p>
      </div>
    </div>
  );
}
