/* QMES module: common — icon registry, shared state, database and UI primitives. */

window.__LUCIDE__ = {"FlaskConical":[["path",{"d":"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"}],["path",{"d":"M6.453 15h11.094"}],["path",{"d":"M8.5 2h7"}]],"Activity":[["path",{"d":"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"}]],"AlertTriangle":[["path",{"d":"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"}],["path",{"d":"M12 9v4"}],["path",{"d":"M12 17h.01"}]],"CheckCircle2":[["circle",{"cx":"12","cy":"12","r":"10"}],["path",{"d":"m9 12 2 2 4-4"}]],"XCircle":[["circle",{"cx":"12","cy":"12","r":"10"}],["path",{"d":"m15 9-6 6"}],["path",{"d":"m9 9 6 6"}]],"Gauge":[["path",{"d":"m12 14 4-4"}],["path",{"d":"M3.34 19a10 10 0 1 1 17.32 0"}]],"Package":[["path",{"d":"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"}],["path",{"d":"M12 22V12"}],["polyline",{"points":"3.29 7 12 12 20.71 7"}],["path",{"d":"m7.5 4.27 9 5.15"}]],"ClipboardCheck":[["rect",{"width":"8","height":"4","x":"8","y":"2","rx":"1","ry":"1"}],["path",{"d":"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{"d":"m9 14 2 2 4-4"}]],"Cpu":[["path",{"d":"M12 20v2"}],["path",{"d":"M12 2v2"}],["path",{"d":"M17 20v2"}],["path",{"d":"M17 2v2"}],["path",{"d":"M2 12h2"}],["path",{"d":"M2 17h2"}],["path",{"d":"M2 7h2"}],["path",{"d":"M20 12h2"}],["path",{"d":"M20 17h2"}],["path",{"d":"M20 7h2"}],["path",{"d":"M7 20v2"}],["path",{"d":"M7 2v2"}],["rect",{"x":"4","y":"4","width":"16","height":"16","rx":"2"}],["rect",{"x":"8","y":"8","width":"8","height":"8","rx":"1"}]],"LayoutDashboard":[["rect",{"width":"7","height":"9","x":"3","y":"3","rx":"1"}],["rect",{"width":"7","height":"5","x":"14","y":"3","rx":"1"}],["rect",{"width":"7","height":"9","x":"14","y":"12","rx":"1"}],["rect",{"width":"7","height":"5","x":"3","y":"16","rx":"1"}]],"Bell":[["path",{"d":"M10.268 21a2 2 0 0 0 3.464 0"}],["path",{"d":"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"}]],"Search":[["path",{"d":"m21 21-4.34-4.34"}],["circle",{"cx":"11","cy":"11","r":"8"}]],"TrendingUp":[["path",{"d":"M16 7h6v6"}],["path",{"d":"m22 7-8.5 8.5-5-5L2 17"}]],"TrendingDown":[["path",{"d":"M16 17h6v-6"}],["path",{"d":"m22 17-8.5-8.5-5 5L2 7"}]],"Thermometer":[["path",{"d":"M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"}]],"Droplets":[["path",{"d":"M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.84-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z"}],["path",{"d":"M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"}]],"Plus":[["path",{"d":"M5 12h14"}],["path",{"d":"M12 5v14"}]],"ChevronRight":[["path",{"d":"m9 18 6-6-6-6"}]],"Truck":[["path",{"d":"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"}],["path",{"d":"M15 18H9"}],["path",{"d":"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"}],["circle",{"cx":"17","cy":"18","r":"2"}],["circle",{"cx":"7","cy":"18","r":"2"}]],"Boxes":[["path",{"d":"M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"}],["path",{"d":"m7 16.5-4.74-2.85"}],["path",{"d":"m7 16.5 5-3"}],["path",{"d":"M7 16.5v5.17"}],["path",{"d":"M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"}],["path",{"d":"m17 16.5-5-3"}],["path",{"d":"m17 16.5 4.74-2.85"}],["path",{"d":"M17 16.5v5.17"}],["path",{"d":"M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"}],["path",{"d":"M12 8 7.26 5.15"}],["path",{"d":"m12 8 4.74-2.85"}],["path",{"d":"M12 13.5V8"}]],"GitBranch":[["path",{"d":"M15 6a9 9 0 0 0-9 9V3"}],["circle",{"cx":"18","cy":"6","r":"3"}],["circle",{"cx":"6","cy":"18","r":"3"}]],"ArrowDownToLine":[["path",{"d":"M12 17V3"}],["path",{"d":"m6 11 6 6 6-6"}],["path",{"d":"M19 21H5"}]],"ArrowUpFromLine":[["path",{"d":"m18 9-6-6-6 6"}],["path",{"d":"M12 3v14"}],["path",{"d":"M5 21h14"}]],"CircleDot":[["circle",{"cx":"12","cy":"12","r":"10"}],["circle",{"cx":"12","cy":"12","r":"1"}]],"Beaker":[["path",{"d":"M4.5 3h15"}],["path",{"d":"M6 3v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V3"}],["path",{"d":"M6 14h12"}]],"Filter":[["path",{"d":"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"}]],"Warehouse":[["path",{"d":"M18 21V10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v11"}],["path",{"d":"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 1.132-1.803l7.95-3.974a2 2 0 0 1 1.837 0l7.948 3.974A2 2 0 0 1 22 8z"}],["path",{"d":"M6 13h12"}],["path",{"d":"M6 17h12"}]],"FileText":[["path",{"d":"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{"d":"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{"d":"M10 9H8"}],["path",{"d":"M16 13H8"}],["path",{"d":"M16 17H8"}]],"Wind":[["path",{"d":"M12.8 19.6A2 2 0 1 0 14 16H2"}],["path",{"d":"M17.5 8a2.5 2.5 0 1 1 2 4H2"}],["path",{"d":"M9.8 4.4A2 2 0 1 1 11 8H2"}]],"RotateCw":[["path",{"d":"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"}],["path",{"d":"M21 3v5h-5"}]],"BarChart3":[["path",{"d":"M3 3v16a2 2 0 0 0 2 2h16"}],["path",{"d":"M18 17V9"}],["path",{"d":"M13 17V5"}],["path",{"d":"M8 17v-3"}]],"Repeat":[["path",{"d":"m17 2 4 4-4 4"}],["path",{"d":"M3 11v-1a4 4 0 0 1 4-4h14"}],["path",{"d":"m7 22-4-4 4-4"}],["path",{"d":"M21 13v1a4 4 0 0 1-4 4H3"}]],"ShieldAlert":[["path",{"d":"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{"d":"M12 8v4"}],["path",{"d":"M12 16h.01"}]],"Printer":[["path",{"d":"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"}],["path",{"d":"M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6"}],["rect",{"x":"6","y":"14","width":"12","height":"8","rx":"1"}]],"Users":[["path",{"d":"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"}],["path",{"d":"M16 3.128a4 4 0 0 1 0 7.744"}],["path",{"d":"M22 21v-2a4 4 0 0 0-3-3.87"}],["circle",{"cx":"9","cy":"7","r":"4"}]],"Wrench":[["path",{"d":"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"}]],"ClipboardList":[["rect",{"width":"8","height":"4","x":"8","y":"2","rx":"1","ry":"1"}],["path",{"d":"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"}],["path",{"d":"M12 11h4"}],["path",{"d":"M12 16h4"}],["path",{"d":"M8 11h.01"}],["path",{"d":"M8 16h.01"}]],"Lock":[["rect",{"width":"18","height":"11","x":"3","y":"11","rx":"2","ry":"2"}],["path",{"d":"M7 11V7a5 5 0 0 1 10 0v4"}]],"Unlock":[["rect",{"width":"18","height":"11","x":"3","y":"11","rx":"2","ry":"2"}],["path",{"d":"M7 11V7a5 5 0 0 1 9.9-1"}]],"ShieldCheck":[["path",{"d":"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{"d":"m9 12 2 2 4-4"}]],"Tablet":[["rect",{"width":"16","height":"20","x":"4","y":"2","rx":"2","ry":"2"}],["line",{"x1":"12","x2":"12.01","y1":"18","y2":"18"}]],"MessageSquareWarning":[["path",{"d":"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"}],["path",{"d":"M12 15h.01"}],["path",{"d":"M12 7v4"}]],"PhoneCall":[["path",{"d":"M13 2a9 9 0 0 1 9 9"}],["path",{"d":"M13 6a5 5 0 0 1 5 5"}],["path",{"d":"M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"}]]};

const { useState, useEffect } = React;
const { LineChart, Line, BarChart, Bar, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, ReferenceLine, LabelList, Cell } = Recharts;
const __ATTR_FIX = { "stroke-width": "strokeWidth", "stroke-linecap": "strokeLinecap", "stroke-linejoin": "strokeLinejoin", "fill-rule": "fillRule", "clip-rule": "clipRule" };
function __fixAttrs(a) { const o = {}; for (const k in a) o[__ATTR_FIX[k] || k] = a[k]; return o; }
const __mkIcon = (name) => ({ size = 16, className = "" }) => React.createElement(
  "svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className },
  window.__LUCIDE__[name].map((n, i) => React.createElement(n[0], Object.assign({ key: i }, __fixAttrs(n[1]))))
);
const [FlaskConical, Activity, AlertTriangle, CheckCircle2, XCircle, Gauge, Package, ClipboardCheck, Cpu, LayoutDashboard, Bell, Search, TrendingUp, TrendingDown, Thermometer, Droplets, Plus, ChevronRight, Truck, Boxes, GitBranch, ArrowDownToLine, ArrowUpFromLine, CircleDot, Beaker, Filter, Warehouse, FileText, Wind, RotateCw, BarChart3, Repeat, ShieldAlert, Printer, Users, Wrench, ClipboardList, Lock, Unlock, ShieldCheck, Tablet, MessageSquareWarning, PhoneCall] = ["FlaskConical", "Activity", "AlertTriangle", "CheckCircle2", "XCircle", "Gauge", "Package", "ClipboardCheck", "Cpu", "LayoutDashboard", "Bell", "Search", "TrendingUp", "TrendingDown", "Thermometer", "Droplets", "Plus", "ChevronRight", "Truck", "Boxes", "GitBranch", "ArrowDownToLine", "ArrowUpFromLine", "CircleDot", "Beaker", "Filter", "Warehouse", "FileText", "Wind", "RotateCw", "BarChart3", "Repeat", "ShieldAlert", "Printer", "Users", "Wrench", "ClipboardList", "Lock", "Unlock", "ShieldCheck", "Tablet", "MessageSquareWarning", "PhoneCall"].map(__mkIcon);


/* ═══════════════════════════════════════════════════════════════
   나모케미칼㈜ QMES —  공정
   관리계획서 NMC-C-220-01 REV.0 (2026.03.12 개정 2차) 기준
   공정 흐름: 10 입고 → 20 IQC → 30 창고/드라이룸 → 40 바인더 제조(TK 501)
             → 50 절연 슬러리 제조(TK 501A↔B) → 60 OQC → 70 충진 → 80 보관 → 90 출하
   ═══════════════════════════════════════════════════════════════ */

/* ──────────────────────────── 공정 현황 ──────────────────────────── */

const PROCESSES = [
  { no: "20", name: "원재료 입고검사 (IQC)", icon: ArrowDownToLine },
  { no: "30", name: "원재료 창고 · 드라이룸", icon: Warehouse },
  { no: "40", name: "바인더 제조 (TK 501)", icon: Beaker },
  { no: "50", name: "절연 슬러리 제조 (TK 501A↔B)", icon: RotateCw },
  { no: "60", name: "검사 (OQC)", icon: ClipboardCheck },
  { no: "70", name: "충진", icon: Package },
  { no: "80", name: "제품보관", icon: Warehouse },
  { no: "90", name: "제품운송 · 출하", icon: Truck },
];

const BATCH_HOURLY = [];

/* 점도 SPC — 규격 1,500±300 cP (CTQ) */
const VISCO_SPC = [];

/* 고형분 SPC — 규격 20.0±1.0 wt% (CTQ) */
const SOLID_SPC = [];

const PARETO = [];

const ALARMS = [];

/* ──────────────────────────── 배치(작업지시) ──────────────────────────── */

const BATCHES = [];

/* ──────────────────────────── 품질 검사 (관리계획서 규격) ──────────────────────────── */

const QC_ITEMS = {
  "점도": { spec: "1,500±300 cP", method: "점도계", type: "CTQ", stage: "PQC/OQC", lo: 1200, hi: 1800 },
  "고형분": { spec: "20.0±1.0 wt%", method: "Drying Oven", type: "CTQ", stage: "PQC/OQC", lo: 19.0, hi: 21.0 },
  "입도(Dmax)": { spec: "<10 µm", method: "입도분석기", type: "CTQ", stage: "PQC/OQC", hi: 10 },
  "수분": { spec: "<2,000 ppm", method: "수분측정기", type: "OQC", stage: "OQC", hi: 2000 },
  "접착력": { spec: "≥400 gf/12.7mm", method: "인장시험기", type: "OQC", stage: "OQC", lo: 400 },
  "절연저항": { spec: "≥200 MΩ (Overflow)", method: "절연저항계", type: "OQC", stage: "OQC", lo: 200 },
  "외관": { spec: "이물질 혼입 없을 것", method: "육안", type: "-", stage: "PQC/OQC" },
  "전해액 안정성": { spec: "탈리 / 미탈리", method: "육안", type: "OQC", stage: "OQC" },
  "Gauss(필터)": { spec: "≥1만 gauss (1단)", method: "Gauss Meter", type: "CTP", stage: "필터링", lo: 10000 },
};

const INITIAL_QC = [];

/* ──────────────────────────── 원재료 재고 ──────────────────────────── */

const INVENTORY = [
  { code: "RM-NMP", name: "NMP", stock: 0, safety: 2400, unit: "kg", loc: "위험물창고 D-01", cond: "밀폐 · 25±5℃" },
  { code: "RM-BYK180", name: "BYK180 (분산제)", stock: 0, safety: 100, unit: "kg", loc: "원재료창고 A-05", cond: "25±5℃" },
  { code: "RM-AOH30", name: "AOH30 (Boehmite)", stock: 0, safety: 1800, unit: "kg", loc: "원재료창고 A-01", cond: "25±5℃ · 습도 50%↓" },
  { code: "RM-SBS", name: "SBS", stock: 0, safety: 300, unit: "kg", loc: "드라이룸 DR-02", cond: "RH 0.54%↓" },
  { code: "RM-PVDF", name: "PVdF", stock: 0, safety: 400, unit: "kg", loc: "드라이룸 DR-02", cond: "RH 0.54%↓" },
  { code: "RM-SBR", name: "SBR", stock: 0, safety: 300, unit: "kg", loc: "원재료창고 A-03", cond: "25±5℃ · 습도 50%↓" },
];

/* ──────────────────────────── 설비 ──────────────────────────── */

const EQUIPMENT = [
  { id: "TK-501", name: "TK 501", subtitle: "NMP/PVdF/SBS 바인더 용해", params: [
    { k: "temp", label: "온도", spec: "80±5 ℃", lo: 75, hi: 85, unit: "℃", src: "PLC 패널 · 1회/Lot" },
    { k: "rpm", label: "RPM", spec: "< 70 rpm", hi: 70, hiExclusive: true, unit: "rpm", src: "PLC 패널 · 1회/Lot" },
    { k: "time", label: "시간", spec: "≤ 300 min", hi: 300, unit: "min", src: "육안 · 1회/Lot" },
    { k: "appearance", label: "외관", spec: "색상 변형 및 이물 없음", visual: true, okLabel: "이상 없음", badLabel: "이상 발견", src: "육안 · 1회/Lot" },
  ] },
  { id: "TK-501A", name: "TK 501A", subtitle: "Mixer 용해", params: [
    { k: "time", label: "시간", spec: "< 80 min", hi: 80, hiExclusive: true, unit: "min", src: "PLC 패널 · 1회/Lot" },
    { k: "temp", label: "온도", spec: "< 70 ℃", hi: 70, hiExclusive: true, unit: "℃", src: "PLC 패널 · 1회/Lot" },
    { k: "rpm", label: "RPM", spec: "3,540±500 rpm", lo: 3040, hi: 4040, unit: "rpm", src: "PLC 패널 · 1회/Lot" },
    { k: "press", label: "최대압력", spec: "≤ 6 bar", hi: 6, unit: "bar", src: "PLC 패널 · 매일" },
    { k: "flow", label: "유량", spec: "< 20 L/min", hi: 20, hiExclusive: true, unit: "L/min", src: "PLC 패널 · 1회/Lot" },
    { k: "oring", label: "O-ring", spec: "균열·마모·경화·부식 없을 것", visual: true, okLabel: "이상 없음", badLabel: "이상 발견", src: "육안 · 설비점검표" },
  ] },
  { id: "TK-501B", name: "TK 501B", subtitle: "TK 501A ↔ TK 501B 순환", params: [
    { k: "time", label: "순환 시간", spec: "작업지시서 설정시간 준수", visual: true, okLabel: "설정시간 준수", badLabel: "미달/초과", src: "PLC 패널 · 1회/Lot" },
    { k: "temp", label: "순환 온도", spec: "< 70 ℃", hi: 70, hiExclusive: true, unit: "℃", src: "PLC 패널 · 1회/Lot" },
  ] },
  { id: "FLT-01", name: "필터 유닛", subtitle: "1단 Gauss · 2단 50/10 μm", params: [
    { k: "gauss", label: "Gauss", spec: "≥ 10,000 G (1단)", lo: 10000, unit: "G", src: "Gauss Meter · 1회/Lot" },
    { k: "condition", label: "필터 상태", spec: "부식 및 이물질 없을 것", visual: true, okLabel: "이상 없음", badLabel: "이상 발견", src: "육안 · 필터관리기록" },
    { k: "second", label: "2단 필터", spec: "50 μm, 10 μm 장착상태", visual: true, okLabel: "장착 정상", badLabel: "이상 발견", src: "육안 · 주1회" },
    { k: "press", label: "최대압력", spec: "≤ 6 bar", hi: 6, unit: "bar", src: "육안 · 1회/Lot" },
  ] },
  { id: "DR-HVAC", name: "드라이룸 항온제습", subtitle: "온·습도 및 이슬점", params: [
    { k: "rh", label: "RH", spec: "≤ 0.54 %", hi: 0.54, unit: "%", src: "온습도계 · 매일" },
    { k: "dp", label: "이슬점", spec: "≤ -40 ℃", hi: -40, unit: "℃", src: "온습도계 · 매일" },
  ] },
];

/* ──────────────────────────── Lot 추적 데이터 ──────────────────────────── */

const LOTS = {};

/* ──────────────────────────── SPC / 4M / 부적합(8D) / CoA 데이터 ──────────────────────────── */

const PARTICLE_SPC = [];

const SPC_CONFIG = {
  "점도": { data: VISCO_SPC, lsl: 1200, usl: 1800, target: 1500, unit: "cP", domain: [1100, 1900], spec: "1,500±300 cP", color: "#33C1E8" },
  "고형분": { data: SOLID_SPC, lsl: 19.0, usl: 21.0, target: 20.0, unit: "wt%", domain: [18.5, 21.5], spec: "20.0±1.0 wt%", color: "#a78bfa" },
  };

/* CTQ 관리도 점 표시: 규격 내 파랑, 규격 이탈 빨강 */
function SpecDot({ cx, cy, payload, lo = null, hi = null }) {
  const value = Number(payload?.v);
  const isOut = Number.isFinite(value) && ((lo != null && value < lo) || (hi != null && value > hi));
  return <circle cx={cx} cy={cy} r={4} fill={isOut ? "#ef4444" : "#38bdf8"} stroke={isOut ? "#fecaca" : "#bae6fd"} strokeWidth={1.5} />;
}

function spcStats(values, lsl, usl) {
  const n = values.length;
  const mean = values.reduce((a, b) => a + b, 0) / n;
  const sd = Math.sqrt(values.reduce((a, b) => a + (b - mean) ** 2, 0) / (n - 1));
  const cp = lsl != null && usl != null ? (usl - lsl) / (6 * sd) : null;
  const cpu = usl != null ? (usl - mean) / (3 * sd) : null;
  const cpl = lsl != null ? (mean - lsl) / (3 * sd) : null;
  const cpk = Math.min(...[cpu, cpl].filter((v) => v != null));
  return { mean, sd, cp, cpk, n };
}

const FOURM = [];

const NCRS = [];

const D_STEPS = ["D1 팀구성", "D2 문제기술", "D3 임시조치", "D4 원인분석", "D5 시정조치 선정", "D6 시정조치 실행", "D7 재발방지", "D8 완료/표준화"];

const COA = {};

/* ──────────────────────────── 수입검사 (IQC) 데이터 — 공정 20 ──────────────────────────── */
/* 검사 항목: 외관(파손유무·육안), 라벨(파손유무·육안), 중량(계약 허용오차·저울), CoA(1회/Lot) */

const IQC_LOTS = [];

const SUPPLIER_RATE = [];

const IQC_MATERIALS = ["NMP", "PVdF", "SBS", "SBR", "Boehmite", "분산제"];

/* ────────────────────────────  데이터 — 공정 40·50 ──────────────────────────── */

const WO_DOCS = {};

/* ──────────────────────────── Lot 채번 체계 (Grd 및 Lot 내역 설명자료 기준) ──────────────────────────── */
/* Lot No. [사이트][년도][월][일 2자리][일련번호 2자리]  예: CBG0701 = Pilot · 2026년 7월 01일 · 01호 */

const SITE_MAP = { A: "외부사이트(OEM)", B: "Lab", C: "Pilot", D: "양산" };

function decodeLot(lot) {
  const m = /^([A-D])([A-Z])([A-L])(\d{2})(\d{2})$/.exec(lot);
  if (!m) return null;
  return {
    site: SITE_MAP[m[1]],
    year: 2025 + (m[2].charCodeAt(0) - 65),
    month: m[3].charCodeAt(0) - 64,
    day: +m[4],
    seq: +m[5],
  };
}

/* ──────────────────────────── 품질 인터락 (차단) 데이터 ──────────────────────────── */
/* 부적합·미검사 상태에서 다음 단계 진행을 시스템이 차단하는 게이트 규칙 */

const GATES = [
  { name: "① IQC 게이트 (불출 차단)", target: "수입검사 미완료·불합격 원료", rule: "판정이 '합격' 또는 승인된 '조건부'가 아니면 창고 출고(불출) 자체가 불가", sys: "원료 Lot 바코드 스캔 시 차단", icon: ArrowDownToLine },
  { name: "② 투입 게이트 (오투입 차단)", target: "작업지시 외 원료·Lot", rule: " BOM에 등록된 원료 Lot만 계량·투입 가능, 기준량 허용공차 초과 시 투입 중지", sys: "전자저울·계량기 인터락", icon: Beaker },
  { name: "③ 공정 게이트 (진행 차단)", target: "PQC 미합격 배치", rule: "PQC(점도·고형분·입도) 합격 전 필터링·충진 공정 시작 불가, 부적합 시 배치 자동 홀드", sys: "PLC 인터락 · 배치 홀드", icon: Filter },
  { name: "④ 출하 게이트 (출하 차단)", target: "OQC 미합격·홀드 Lot", rule: "OQC 8항목 전부 합격 + 홀드 없음 조건 충족 시에만 CoA 발행·출하지시 가능", sys: "출하지시·성적서 발행 차단", icon: Truck },
];

const INITIAL_HOLDS = [];

/* 현장 부적합 격리 Rack — 시스템 홀드와 실물 격리 위치를 1:1 연결 (개선요청 6번) */
const RACKS = [
  { id: "R-01", zone: "원재료 격리", item: "-", hold: "-", state: "비어있음" },
  { id: "R-02", zone: "원재료 격리", item: "-", hold: "-", state: "비어있음" },
  { id: "R-03", zone: "제품 격리", item: "-", hold: "-", state: "비어있음" },
  { id: "R-04", zone: "제품 격리", item: "-", hold: "-", state: "비어있음" },
];

/* ──────────────────────────── 고객불만 (GQMS) 데이터 — 개선요청 8번 ──────────────────────────── */
/* 접수 → 초동조치(격리·사용중지) → 원인분석 → 시정조치 → 재발방지 → 고객승인·Close */

const COMPLAINT_FLOW_LABELS = ["① 접수·초동회신", "② 초동조치 (격리·사용중지)", "③ 원인분석", "④ 시정조치", "⑤ 재발방지·표준화", "⑥ 고객승인 · GQMS Close"];

const INITIAL_COMPLAINTS = [];

function qmesCloneWithScreenStyles(source) {
  const clone = source.cloneNode(true);
  const sourceNodes = [source, ...source.querySelectorAll("*")];
  const cloneNodes = [clone, ...clone.querySelectorAll("*")];

  sourceNodes.forEach((node, index) => {
    const clonedNode = cloneNodes[index];
    if (!clonedNode || node.nodeType !== 1) return;

    const computed = window.getComputedStyle(node);
    for (let i = 0; i < computed.length; i += 1) {
      const prop = computed[i];
      try {
        clonedNode.style.setProperty(prop, computed.getPropertyValue(prop), "important");
      } catch (_) {}
    }
  });

  clone.setAttribute("data-qmes-screen-print", "true");
  return clone;
}

function printDoc(sourceEl) {
  /* React onClick이 이벤트 객체를 첫 번째 인자로 넘기는 경우를 방지합니다. */
  const validSource = sourceEl instanceof Element ? sourceEl : null;
  const source = validSource || Array.from(document.querySelectorAll(".doc-paper"))
    .find((el) => el.offsetParent !== null && el.id !== "qmes-label-print-root");
  const printRoot = document.getElementById("qmes-print-root");

  if (!source || !printRoot) {
    alert("인쇄할 성적서 내용을 찾을 수 없습니다.");
    return;
  }

  /* ── 화면 미리보기 = 실제 인쇄 결과 ──
     화면에 렌더링된 계산 스타일(computed style)을 요소별 인라인으로 복제해
     인쇄 전용 CSS 재정의(글자/여백/표 높이 등)의 영향을 완전히 차단하고,
     화면 모습 그대로를 A4에 맞게 비율만 축소하여 출력합니다. */
  const clone = qmesCloneWithScreenStyles(source);
  clone.classList.add("qmes-a4-preview-clone");

  /* 캔버스(QR 등) 내용은 cloneNode로 복사되지 않으므로 비트맵을 직접 복사 */
  const srcCanvases = source.querySelectorAll("canvas");
  const cloneCanvases = clone.querySelectorAll("canvas");
  srcCanvases.forEach((c, i) => {
    const cc = cloneCanvases[i];
    if (!cc) return;
    try {
      cc.width = c.width;
      cc.height = c.height;
      cc.getContext("2d").drawImage(c, 0, 0);
    } catch (_) {}
  });

  /* 인쇄에 불필요한 요소 제거 */
  clone.querySelectorAll("button, .qmes-modal-close, .qmes-print-actions, [data-no-print='true']")
    .forEach((el) => el.remove());

  /* 배경색·표 음영이 미리보기와 동일하게 인쇄되도록 강제 */
  [clone, ...clone.querySelectorAll("*")].forEach((el) => {
    try {
      el.style.setProperty("-webkit-print-color-adjust", "exact", "important");
      el.style.setProperty("print-color-adjust", "exact", "important");
    } catch (_) {}
  });

  /* 화면 크기 측정 후 A4(210×297mm)에 맞는 축소 배율 계산 */
  const rect = source.getBoundingClientRect();
  const MM_TO_PX = 96 / 25.4;
  const PAGE_W = 210 * MM_TO_PX;
  const PAGE_H = 297 * MM_TO_PX;
  const MARGIN = 8 * MM_TO_PX; /* 상하좌우 8mm 여백 */
  const availW = PAGE_W - MARGIN * 2;
  const availH = PAGE_H - MARGIN * 2;
  const centerCoaOnA4 = source.classList.contains("qmes-coa-unified-doc");
  const scale = Math.min(availW / rect.width, centerCoaOnA4 ? availH / rect.height : 1, 1);

  /* 복제본을 화면 폭 그대로 두고 zoom으로 축소 → transform과 달리 실제 박스 크기가 줄어들어 인쇄 시 페이지 나눔이 정상 동작함 */
  clone.style.setProperty("width", rect.width + "px", "important");
  clone.style.setProperty("margin", "0", "important");
  clone.style.setProperty("box-shadow", "none", "important");
  clone.style.setProperty("transform", "none", "important");
  clone.style.setProperty("zoom", String(scale), "important");

  const stage = document.createElement("div");
  stage.className = "qmes-screen-print-stage";
  /* 수입·공정·출하검사 성적서 모두 용지 상단에서 1.2cm 추가 하향 배치 */
  const extraTop = (source.classList.contains("qmes-iqc-doc") ||
                    source.classList.contains("qmes-pqc-doc") ||
                    source.classList.contains("qmes-oqc-doc")) ? 12 * MM_TO_PX : 0;
  const stageTop = centerCoaOnA4
    ? Math.max(MARGIN, (PAGE_H - rect.height * scale) / 2)
    : MARGIN + extraTop;
  stage.style.setProperty("width", (rect.width * scale) + "px", "important");
  stage.style.setProperty("height", "auto", "important");
  stage.style.setProperty("margin", stageTop + "px auto 0", "important");
  stage.style.setProperty("overflow", "visible", "important");
  stage.appendChild(clone);

  printRoot.innerHTML = "";
  printRoot.appendChild(stage);

  document.body.classList.remove("print-label", "print-doc");
  document.body.classList.add("qmes-screen-exact-print");

  let cleaned = false;
  const cleanup = () => {
    if (cleaned) return;
    cleaned = true;
    document.body.classList.remove("qmes-screen-exact-print");
    printRoot.innerHTML = "";
    window.removeEventListener("afterprint", cleanup);
  };

  window.addEventListener("afterprint", cleanup);
  requestAnimationFrame(() => requestAnimationFrame(() => {
    window.print();
    setTimeout(cleanup, 1500);
  }));
}

/* ──────────────────────────── 통합 데이터 저장소 ──────────────────────────── */
/* 화면 간 연동 + 새로고침 후에도 기록 유지. 실제 서버 구축 시 이 계층만 API로 교체하면 됩니다. */

const DB_KEY = "qmes-local-shipment-dashboard-v8-clean";
const DB_RESET_MARKER = "qmes-shipment-dashboard-clean-reset-v8";

/* 수입검사 개선본 최초 실행 시 기존 테스트 DB를 한 번 초기화합니다.
   이후 입력한 데이터는 새 DB_KEY에 저장되어 새로고침 후에도 유지됩니다. */
try {
  if (!window.localStorage.getItem(DB_RESET_MARKER)) {
    ["qmes-local-v1", "qmes-db-v1", "qmes-local-iqc-v2", "qmes-local-iqc-v3", "qmes-local-iqc-v4-clean", "qmes-local-simple-summary-v5", "qmes-local-title-inline-v6", "qmes-local-title-inline-v7-clean", "qmes-local-shipment-dashboard-v8-clean", DB_KEY].forEach((key) => window.localStorage.removeItem(key));
    window.localStorage.setItem(DB_RESET_MARKER, "1");
  }
} catch (e) { /* 무시 */ }

/* 공용 서버 DB(Supabase) 연결 제거
   - 입력 데이터는 이 브라우저의 localStorage에만 저장됩니다.
   - 외부 서버 전송 및 장치 간 자동 동기화는 수행하지 않습니다. */
function dbDefault() {
  return {
    batches: [], woDocs: {}, iqc: [], iqcMaterials: [], insp: { PQC: [], OQC: [] },
    holds: [], gateEvents: [], intermediateLots: {}, intermediateContainers: {}, materialRemainders: {},
    eqReadings: {}, eqLogs: [], eqAlarms: [], complaints: [], lots: {}, coa: {},
    popEntries: [], auditLogs: [], seqs: {}
  };
}

/* ── 고객사 지적 대응: 실제 저장 단계 품질 게이트 ── */
function qmesText(v) { return String(v ?? "").trim(); }
function qmesActiveHold(lotNo) {
  const lot = qmesText(lotNo);
  return (DB.holds || []).find((h) => {
    const target = qmesText(h.target);
    const status = qmesText(h.status);
    const active = !(status.includes("해제 완료") || status.includes("종결") || status === "완료");
    return active && (target === lot || target.startsWith(lot + " ") || target.startsWith(lot + "("));
  }) || null;
}
function qmesLatestIqc(materialLot) {
  const lot = qmesText(materialLot);
  return (DB.iqc || [])
    .filter((r) => qmesText(r.lot) === lot)
    .sort((a, b) => qmesText(b.inspectedAt || b.recv).localeCompare(qmesText(a.inspectedAt || a.recv)))[0] || null;
}
function qmesMaterialGate(materialLot) {
  const lot = qmesText(materialLot);
  if (!lot) return { ok:false, reason:"원재료 LOT 미입력" };
  const hold = qmesActiveHold(lot);
  if (hold) return { ok:false, reason:`격리·홀드 LOT — ${hold.reason || hold.gate || "사용 차단"}` };
  const iqc = qmesLatestIqc(lot);
  if (!iqc) return { ok:false, reason:"IQC 검사 기록 없음" };
  if (qmesText(iqc.judge) !== "합격") return { ok:false, reason:`IQC ${iqc.judge || "미판정"} — 합격 전 투입 금지` };
  return { ok:true, reason:"IQC 합격" };
}
function qmesPqcPass(lotNo) {
  const rows = (DB.insp?.PQC || []).filter((r) => qmesText(r.lot) === qmesText(lotNo));
  if (!rows.length) return false;
  const latestGroup = qmesText(rows[0].groupId || rows[0].id).replace(/-\d+$/, "");
  const groupRows = rows.filter((r) => qmesText(r.groupId || r.id).replace(/-\d+$/, "") === latestGroup);
  return groupRows.length > 0 && groupRows.every((r) => qmesText(r.judge) === "합격");
}
function qmesProductionComplete(lotNo) {
  const doc = DB.woDocs?.[lotNo];
  return !!doc && (qmesText(doc.status) === "완료" || qmesText(doc.manualStatus) === "완료");
}
function qmesShipmentGate(lotNo) {
  const lot = qmesText(lotNo);
  const hold = qmesActiveHold(lot);
  if (hold) return { ok:false, reason:`홀드 미해제 — ${hold.reason || hold.gate || "출하 차단"}` };
  if (!qmesProductionComplete(lot)) return { ok:false, reason:"생산실적 미완료" };
  if (!qmesPqcPass(lot)) return { ok:false, reason:"PQC 합격 기록 없음" };
  return { ok:true, reason:"출하검사 진행 가능" };
}
function qmesRecordGateBlock(gate, lotNo, reason) {
  DB.gateEvents = [{
    id:`GATE-${Date.now()}`, gate, lot:qmesText(lotNo), reason:qmesText(reason),
    status:"차단", by:window.__QMES_USER__ || "현재 사용자", at:new Date().toISOString()
  }, ...(DB.gateEvents || [])].slice(0, 1000);
  auditLog("품질게이트", "차단", qmesText(lotNo), `${gate} / ${reason}`);
}
let DB = (() => {
  try {
    const current = window.localStorage.getItem(DB_KEY);
    if (current) return { ...dbDefault(), ...JSON.parse(current) };
  } catch (e) { /* 무시 */ }
  return dbDefault();
})();

function auditLog(module, action, target, reason = "") {
  const now = new Date();
  DB.auditLogs = [{
    id: `AUD-${Date.now()}`, module, action, target, reason,
    by: (window.__QMES_USER__ || "현재 사용자"),
    at: now.toLocaleString("ko-KR", { hour12: false })
  }, ...(DB.auditLogs || [])].slice(0, 500);
}

function askDeleteReason(label) {
  const reason = window.prompt(`${label} 삭제 사유를 입력하세요.\n※ 삭제 이력은 감사 로그에 남습니다.`);
  if (reason === null) return null;
  if (!reason.trim()) { window.alert("삭제 사유를 입력해야 합니다."); return null; }
  return reason.trim();
}

function dbSave() {
  try { window.localStorage.setItem(DB_KEY, JSON.stringify(DB)); } catch (e) { /* 무시 */ }
}
function dbReset() {
  DB = dbDefault();
  try { window.localStorage.setItem(DB_KEY, JSON.stringify(DB)); } catch (e) { /* 무시 */ }
}


function downloadQmesBackup() {
  const payload = {
    format: "QMES_BACKUP_V1",
    exportedAt: new Date().toISOString(),
    data: DB,
    users: (() => {
      try { return JSON.parse(localStorage.getItem("qmes-users-v3") || "[]"); }
      catch (e) { return []; }
    })(),
    ui: {
      currentTab: localStorage.getItem("qmes_current_tab") || "dash",
      openMenu: localStorage.getItem("qmes_open_menu") || "",
      selectedWo: localStorage.getItem("qmes_selected_wo") || "",
    },
  };
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  const d = new Date();
  const stamp = `${d.getFullYear()}${String(d.getMonth()+1).padStart(2,"0")}${String(d.getDate()).padStart(2,"0")}_${String(d.getHours()).padStart(2,"0")}${String(d.getMinutes()).padStart(2,"0")}`;
  a.href = url;
  a.download = `QMES_BACKUP_${stamp}.json`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

function restoreQmesBackup() {
  const input = document.createElement("input");
  input.type = "file";
  input.accept = ".json,application/json";
  input.onchange = () => {
    const file = input.files && input.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const payload = JSON.parse(String(reader.result || ""));
        if (payload.format !== "QMES_BACKUP_V1" || !payload.data) throw new Error("QMES 백업 파일이 아닙니다.");
        if (!confirm("현재 로컬 데이터를 백업 파일 내용으로 교체할까요?")) return;
        localStorage.setItem(DB_KEY, JSON.stringify({ ...dbDefault(), ...payload.data }));
        if (Array.isArray(payload.users)) localStorage.setItem("qmes-users-v3", JSON.stringify(payload.users));
        if (payload.ui?.currentTab) localStorage.setItem("qmes_current_tab", payload.ui.currentTab);
        if (payload.ui?.openMenu) localStorage.setItem("qmes_open_menu", payload.ui.openMenu);
        if (payload.ui?.selectedWo) localStorage.setItem("qmes_selected_wo", payload.ui.selectedWo);
        alert("복원이 완료되었습니다.");
        location.reload();
      } catch (e) {
        alert(`복원 실패: ${e.message || "파일을 확인하세요."}`);
      }
    };
    reader.readAsText(file, "utf-8");
  };
  input.click();
}

function qmesStart() {
  ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(QMESApp));
}

/* ──────────────────────────── 공통 UI ──────────────────────────── */

const statusMeta = {
  run: { label: "진행중", dot: "bg-sky-400", text: "text-sky-400", ring: "border-sky-500/40" },
  done: { label: "완료", dot: "bg-emerald-400", text: "text-emerald-400", ring: "border-emerald-500/40" },
  inspect: { label: "검사중", dot: "bg-violet-400", text: "text-violet-400", ring: "border-violet-500/40" },
  idle: { label: "대기", dot: "bg-slate-500", text: "text-slate-400", ring: "border-slate-600/40" },
  alarm: { label: "이상", dot: "bg-red-400", text: "text-red-400", ring: "border-red-500/50" },
};

function Badge({ tone, children }) {
  const tones = {
    green: "bg-emerald-500/10 text-emerald-400 border-emerald-500/30",
    red: "bg-red-500/10 text-red-400 border-red-500/30",
    amber: "bg-amber-500/10 text-amber-400 border-amber-500/30",
    blue: "bg-sky-500/10 text-sky-400 border-sky-500/30",
    violet: "bg-violet-500/10 text-violet-400 border-violet-500/30",
    gray: "bg-slate-500/10 text-slate-400 border-slate-500/30",
  };
  return (
    <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border ${tones[tone]}`}>
      {children}
    </span>
  );
}

function Panel({ title, right, children, className = "" }) {
  return (
    <div className={`bg-slate-900 border border-slate-800 rounded-lg ${className}`}>
      {title && (
        <div className="flex items-center justify-between px-4 py-3 border-b border-slate-800 gap-3">
          <h3 className="text-sm font-semibold text-slate-200 tracking-wide">{title}</h3>
          {right}
        </div>
      )}
      <div className="p-4">{children}</div>
    </div>
  );
}

function InspectionSummary({ items }) {
  return (
    <section className="qmes-simple-summary qmes-simple-summary-only">
      <div className="qmes-simple-summary-items" style={{"--summary-count": items.length}}>
        {items.map((item) => (
          <div className="qmes-simple-summary-item" key={item.label}>
            <span className={`qmes-simple-summary-light ${item.tone || "sky"}`} aria-hidden="true"></span>
            <span className="qmes-simple-summary-label">{item.label}</span>
            <div className="qmes-simple-summary-value">
              <strong>{item.value}</strong>
              {item.unit && <small>{item.unit}</small>}
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

function Kpi({ icon: Icon, label, value, unit, delta, deltaUp, tone = "text-sky-400", animated = false, progressValue, showProgress = false, textOnly = false, caption = "" }) {
  const numeric = Number(String(value).replace(/[^0-9.]/g,"")) || 0;
  const labelKey = String(label || "");
  const calculatedProgress = progressValue !== undefined && progressValue !== null
    ? Number(progressValue)
    : labelKey.includes("합격") ? numeric
    : labelKey.includes("LOT") ? (numeric > 0 ? 100 : 0)
    : labelKey.includes("부적합") || labelKey.includes("반품") ? numeric
    : 58;
  const progress = Math.max(0, Math.min(100, Number.isFinite(calculatedProgress) ? calculatedProgress : 0));
  const [displayProgress, setDisplayProgress] = useState(animated ? 0 : progress);

  useEffect(() => {
    if (!animated) {
      setDisplayProgress(progress);
      return;
    }
    setDisplayProgress(0);
    const timer = window.setTimeout(() => setDisplayProgress(progress), 80);
    return () => window.clearTimeout(timer);
  }, [progress, animated]);

  return (
    <div className={`bg-slate-900 border border-slate-800 rounded-lg ${textOnly ? "qmes-text-kpi-card" : "qmes-hybrid-kpi-card"} ${animated ? "qmes-iqc-kpi-card" : ""}`}>
      {!textOnly && Icon && (
        <div className="qmes-hybrid-donut" style={{"--donut-progress": `${displayProgress}%`}}>
          <div className="qmes-hybrid-ring"></div>
          <div className="qmes-hybrid-ring-inner"></div>
          <Icon className={`qmes-hybrid-icon ${tone}`} />
          {animated && <span className="qmes-hybrid-percent">{Math.round(displayProgress)}%</span>}
        </div>
      )}
      <div className={textOnly ? "qmes-text-kpi-copy" : "qmes-hybrid-copy"}>
        <div className="qmes-hybrid-label-row">
          {animated && <span className={`qmes-iqc-kpi-dot ${tone}`}></span>}
          <span className="qmes-hybrid-label">{label}</span>
        </div>
        <div className="qmes-hybrid-value-row">
          <span className="qmes-hybrid-value">{value}</span>
          {unit && <span className="qmes-hybrid-unit">{unit}</span>}
        </div>
        <div className={textOnly ? "qmes-text-kpi-caption" : "qmes-hybrid-caption"}>
          {caption || (labelKey.includes("합격") && "검사 완료 기준 합격 비율")}
          {!caption && labelKey.includes("LOT") && "이번 달 누적 입고 실적"}
          {!caption && labelKey.includes("부적합") && "즉시 확인이 필요한 항목"}
          {!caption && labelKey.includes("반품") && "공급사 반송 처리 건수"}
        </div>
        {showProgress && (
          <div className="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-slate-800" role="progressbar" aria-label={`${label} 진행률`} aria-valuemin="0" aria-valuemax="100" aria-valuenow={Math.round(progress)}>
            <div className="h-full rounded-full bg-emerald-400 transition-all duration-500" style={{ width: `${displayProgress}%` }} />
          </div>
        )}
        {delta && (
          <div className={`qmes-hybrid-delta ${deltaUp ? "is-up" : "is-down"}`}>
            {deltaUp ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
            <span>{delta} 전일 대비</span>
          </div>
        )}
      </div>
    </div>
  );
}

const chartTooltip = {
  contentStyle: { background: "#0F1E32", border: "1px solid #243D58", borderRadius: 8, fontSize: 12 },
  labelStyle: { color: "#C5D5E8" },
};

/* ──────────────────────────── 공정 상태 스트립 ──────────────────────────── */
