// quote.jsx — 서비스 견적서 · 라이카 스타일
const { useState, useMemo, useEffect } = React;

const CAT = window.CATALOG;
const SVC = window.SERVICES;

const fmtKRW = (n) => "₩" + Math.round(n).toLocaleString("ko-KR");
const fmtNum = (n) => Math.round(n).toLocaleString("ko-KR");

// 견적서 번호: 날짜 + 6자리
const todayISO = () => {
  const d = new Date();
  const yy = d.getFullYear(), mm = String(d.getMonth()+1).padStart(2,"0"), dd = String(d.getDate()).padStart(2,"0");
  return `${yy}-${mm}-${dd}`;
};
const quoteNo = () => {
  const d = new Date();
  const yymmdd = d.getFullYear().toString().slice(2) + String(d.getMonth()+1).padStart(2,"0") + String(d.getDate()).padStart(2,"0");
  return `Q-${yymmdd}-${String(Math.floor(Math.random()*900)+100)}`;
};
const validUntil = () => {
  const d = new Date(); d.setDate(d.getDate() + 14);
  const yy = d.getFullYear(), mm = String(d.getMonth()+1).padStart(2,"0"), dd = String(d.getDate()).padStart(2,"0");
  return `${yy}-${mm}-${dd}`;
};

// v3.16: 견적서 자동 사이즈 추천 배지 (회의록 v3.16 §3.3)
function SizeRecommendBadge({ customer }) {
  const h = Number(customer?.heightCm);
  const bikeName = (customer?.bike || "").trim().toLowerCase();

  // 카탈로그에서 가장 잘 매칭되는 모델 찾기 (이름 포함 검색)
  const matchedBike = React.useMemo(() => {
    if (!bikeName) return null;
    const bikes = (window.CATALOG?.bikes) || [];
    return bikes.find(b =>
      b.name?.toLowerCase().includes(bikeName) ||
      bikeName.includes((b.name || "").toLowerCase().split(" ")[0])
    );
  }, [bikeName]);

  // 매칭된 모델의 신장 매핑
  const heightMap = React.useMemo(() => {
    if (!matchedBike) return [];
    if (Array.isArray(matchedBike.size_height_map)) return matchedBike.size_height_map;
    if (matchedBike.sizeHeightMap) {
      return Object.entries(matchedBike.sizeHeightMap).map(([size, hh]) => ({
        size, height_min_cm: hh.h1, height_max_cm: hh.h2,
      }));
    }
    return [];
  }, [matchedBike]);

  // 추천 사이즈
  const recommended = React.useMemo(() => {
    if (!h || !heightMap.length) return null;
    const matches = heightMap
      .filter(x => x.height_min_cm && x.height_max_cm && h >= x.height_min_cm && h <= x.height_max_cm)
      .map(x => ({ ...x, distance: Math.abs(((x.height_min_cm + x.height_max_cm)/2) - h) }))
      .sort((a, b) => a.distance - b.distance);
    return matches[0] || null;
  }, [h, heightMap]);

  if (!h) {
    return <div style={{padding:"10px 12px", background:"var(--neutral-50,#F9F9F9)", borderRadius:6, fontSize:12, color:"var(--fg-tertiary)"}}>신장 입력 시 자동 추천</div>;
  }
  if (!matchedBike) {
    return <div style={{padding:"10px 12px", background:"var(--neutral-50,#F9F9F9)", borderRadius:6, fontSize:12, color:"var(--fg-tertiary)"}}>차량 모델 입력 시 사이즈 추천</div>;
  }
  if (!heightMap.length || !heightMap.some(x => x.height_min_cm)) {
    return <div style={{padding:"10px 12px", background:"#FFF9E7", borderRadius:6, fontSize:12, color:"#7C5A00"}}>이 모델은 신장 정보 미등록 — 시승 추천</div>;
  }
  if (!recommended) {
    return <div style={{padding:"10px 12px", background:"var(--red-50,#FFE5E6)", borderRadius:6, fontSize:12, color:"#9A0E14"}}>{h}cm는 등록 범위 밖 — 시승 추천</div>;
  }
  return (
    <div style={{padding:"10px 12px", background:"var(--red-50,#FFE5E6)", border:"1px solid #FEAFB4", borderRadius:6, display:"flex", alignItems:"center", gap:6, flexWrap:"wrap"}}>
      <span style={{fontSize:13, fontWeight:700, color:"#9A0E14"}}>💡 {recommended.size}</span>
      <span style={{fontSize:11, color:"var(--fg-tertiary)"}}>({recommended.height_min_cm}~{recommended.height_max_cm}cm · {h}cm 기준)</span>
    </div>
  );
}

// ─── HEADER ──────────────────────────────────────────────
function Header() {
  return (
    <header className="q-header">
      <div className="q-header-inner">
        <a className="q-brand" href="#">
          <span className="q-brand-dot" />
          <div>
            <div className="q-brand-name">김성호바이크 · 세종</div>
            <div className="q-brand-sub">Authorized Specialized Dealer</div>
          </div>
        </a>
        <div className="q-header-title">Service · Estimate</div>
        <div className="q-header-tools">
          <span>발행일 · <b>{todayISO()}</b></span>
          <span>발행자 · <b>김성호</b></span>
        </div>
      </div>
    </header>
  );
}

// ─── PAGE NAV ────────────────────────────────────────────
function PageNav() {
  return (
    <nav className="cat-pagenav q-pagenav">
      <div className="cat-pagenav-inner">
        <a href="관리자.html">관리자 <small>Admin</small></a>
        <a href="스케줄러.html">스케줄러 <small>Scheduler</small></a>
        <a href="2026 카탈로그.html">카탈로그 <small>Catalog</small></a>
        <a href="재고관리.html">재고관리 <small>Inventory</small></a>
        <a className="on" href="서비스 견적서.html">서비스 견적서 <small>Estimate</small></a>
      </div>
    </nav>
  );
}

// ─── BUILDER (left pane) ─────────────────────────────────
function Builder({ q, setQ, onSaveImage, onCopyImage, onShareKakao, onSaveSupabase }) {
  const [tab, setTab] = useState("fit");
  const [pickSku, setPickSku] = useState("");
  const [name, setName] = useState("");
  const [price, setPrice] = useState("");
  const [desc, setDesc] = useState("");
  const [discType, setDiscType] = useState("none"); // none | percent | amount | service
  const [discValue, setDiscValue] = useState("");
  const [draggingId, setDraggingId] = useState(null);

  // 순서 변경 — 드래그 OR 위/아래 버튼
  const reorderLine = (fromId, toId) => {
    if (!fromId || fromId === toId) return;
    const items = [...q.items];
    const fi = items.findIndex(x => x.id === fromId);
    const ti = items.findIndex(x => x.id === toId);
    if (fi < 0 || ti < 0) return;
    const [moved] = items.splice(fi, 1);
    items.splice(ti, 0, moved);
    setQ({ ...q, items });
  };
  const moveLine = (id, delta) => {
    const items = [...q.items];
    const idx = items.findIndex(x => x.id === id);
    const newIdx = idx + delta;
    if (idx < 0 || newIdx < 0 || newIdx >= items.length) return;
    [items[idx], items[newIdx]] = [items[newIdx], items[idx]];
    setQ({ ...q, items });
  };

  // 현재 탭이 공임 탭인지
  const isLaborTab = tab === "fit";

  // 선택 가능한 프리셋 by tab
  const presets = useMemo(() => {
    if (tab === "bike") return CAT.bikes.map(b => ({ sku: b.sku, name: `${b.name} · ${b.tier}`, price: b.priceShop, desc: b.group }));
    if (tab === "fit")  return SVC.fit.items;
    if (tab === "parts")return SVC.parts.items;
    if (tab === "acc")  return SVC.acc.items;
    return [];
  }, [tab]);

  const applyPreset = (sku) => {
    setPickSku(sku);
    if (!sku) return;
    const p = presets.find(x => x.sku === sku);
    if (p) { setName(p.name); setPrice(String(p.price)); setDesc(p.desc || ""); }
  };

  useEffect(() => { setPickSku(""); setName(""); setPrice(""); setDesc(""); setDiscType("none"); setDiscValue(""); }, [tab]);

  const addItem = () => {
    const nm = name.trim();
    const pr = Number(price) || 0;
    if (!nm || !pr) return;
    const baseSku = pickSku || `${tab.toUpperCase()}-${String(Date.now()).slice(-5)}`;
    const ts = Date.now();
    // 할인 / 서비스 처리
    let discount = { type: "none", value: 0 };
    if (discType === "service") {
      discount = { type: "percent", value: 100, service: true };
    } else if (discType === "percent" && Number(discValue) > 0) {
      discount = { type: "percent", value: Number(discValue) };
    } else if (discType === "amount" && Number(discValue) > 0) {
      discount = { type: "amount", value: Number(discValue) };
    }
    setQ({ ...q, items: [...q.items, {
      id: `${baseSku}-${ts}`,
      sku: baseSku,
      name: nm,
      desc: desc.trim(),
      price: pr,
      qty: 1,
      discount,
      kind: tab,
    }]});
    setPickSku(""); setName(""); setPrice(""); setDesc(""); setDiscType("none"); setDiscValue("");
  };

  const updateLine = (id, patch) => {
    setQ({ ...q, items: q.items.map(x => x.id === id ? { ...x, ...patch } : x) });
  };
  const updateLineDisc = (id, patch) => {
    setQ({ ...q, items: q.items.map(x => x.id === id ? { ...x, discount: { ...x.discount, ...patch } } : x) });
  };
  const updateQty = (id, delta) => {
    setQ({ ...q, items: q.items.map(x => x.id === id ? { ...x, qty: Math.max(1, x.qty + delta) } : x) });
  };
  const removeItem = (id) => {
    setQ({ ...q, items: q.items.filter(x => x.id !== id) });
  };

  // A4: 빠른 템플릿 (정비 풀패키지 / 신차 구매 / 부품 교체)
  const applyTemplate = (kind) => {
    const SRV = window.SERVICES || {};
    let items = [];
    let memo = "";
    if (kind === "tune-full") {
      memo = "정비 풀패키지 — 변속 + 브레이크 + 휠 트루닝";
      items = [
        { id: Math.random().toString(36).slice(2), kind:"labor", name:"Overhaul · 풀 분해 정비",     price: 380000, qty:1, discount:{type:"none",value:0} },
        { id: Math.random().toString(36).slice(2), kind:"labor", name:"Wheel Truing · 휠 트루닝",     price: 60000,  qty:1, discount:{type:"none",value:0} },
        { id: Math.random().toString(36).slice(2), kind:"part",  name:"Shimano CN-M8100 체인 (12s)", price: 78000,  qty:1, discount:{type:"none",value:0} },
      ];
    } else if (kind === "new-bike") {
      memo = "신차 구매 — Body Geometry Fit 첫구매 50% 할인 포함";
      items = [
        { id: Math.random().toString(36).slice(2), kind:"part",  name:"신차 본체 (모델 선택)",     price: 0, qty:1, discount:{type:"none",value:0} },
        { id: Math.random().toString(36).slice(2), kind:"labor", name:"Body Geometry Fit · 첫구매", price: 120000, qty:1, discount:{type:"none",value:0} },
      ];
    } else if (kind === "parts-swap") {
      memo = "부품 교체 — 체인 + 카세트 + 브레이크 패드";
      items = [
        { id: Math.random().toString(36).slice(2), kind:"part",  name:"Shimano CN-M8100 체인 (12s)",   price: 78000, qty:1, discount:{type:"none",value:0} },
        { id: Math.random().toString(36).slice(2), kind:"part",  name:"Shimano R8100 카세트 11-34T",   price: 168000, qty:1, discount:{type:"none",value:0} },
        { id: Math.random().toString(36).slice(2), kind:"part",  name:"Resin 브레이크 패드 · 한 쌍",   price: 32000, qty:1, discount:{type:"none",value:0} },
        { id: Math.random().toString(36).slice(2), kind:"labor", name:"부품 교체 공임",                price: 80000, qty:1, discount:{type:"none",value:0} },
      ];
    }
    setQ({ ...q, items, customer: { ...q.customer, note: memo } });
  };

  // A3: 자전거 자동완성 — 카탈로그 16종 datalist 데이터
  const bikeOptions = (window.CATALOG?.bikes || []).map(b => `${b.name} (${b.sku || b.base_sku || ''})`);

  return (
    <aside className="q-builder">
      <div className="q-builder-head">
        <div className="q-builder-eyebrow">견적 빌더</div>
        <div className="q-builder-h">새 견적서 <em>·</em> {q.no}</div>
      </div>

      {/* A4: 빠른 템플릿 */}
      <div style={{padding:"12px 14px",background:"#FFF9E7",border:"1px dashed #F5C97A",
                   borderRadius:8,marginBottom:14}}>
        <div style={{fontSize:12,fontWeight:600,color:"#7C5A00",marginBottom:8}}>
          ⚡ 빠른 템플릿 — 클릭 시 현재 항목 덮어쓰기
        </div>
        <div style={{display:"flex",gap:6,flexWrap:"wrap"}}>
          <button onClick={()=>applyTemplate("tune-full")}
            style={{padding:"6px 10px",fontSize:12,background:"#fff",border:"1px solid var(--grey-200)",
                    borderRadius:6,cursor:"pointer"}}>🛠 정비 풀패키지</button>
          <button onClick={()=>applyTemplate("new-bike")}
            style={{padding:"6px 10px",fontSize:12,background:"#fff",border:"1px solid var(--grey-200)",
                    borderRadius:6,cursor:"pointer"}}>🚲 신차 구매</button>
          <button onClick={()=>applyTemplate("parts-swap")}
            style={{padding:"6px 10px",fontSize:12,background:"#fff",border:"1px solid var(--grey-200)",
                    borderRadius:6,cursor:"pointer"}}>🔩 부품 교체</button>
        </div>
      </div>

      {/* A3: 자전거 자동완성 datalist (전체 페이지에서 참조) */}
      <datalist id="bike-options">
        {bikeOptions.map((b,i) => <option key={i} value={b} />)}
      </datalist>

      {/* CUSTOMER */}
      <div className="q-block">
        <div className="q-block-h">고객 정보</div>
        <div className="q-grid-1" style={{gap:14}}>
          <div className="q-field">
            <label>성명</label>
            <input value={q.customer.name} onChange={(e) => setQ({...q, customer:{...q.customer, name:e.target.value}})} placeholder="홍길동" />
          </div>
          <div className="q-grid-2">
            <div className="q-field">
              <label>연락처</label>
              <input value={q.customer.phone} onChange={(e) => setQ({...q, customer:{...q.customer, phone:e.target.value}})} placeholder="010-0000-0000" />
            </div>
            <div className="q-field">
              <label>📏 신장 (cm, 옵션)</label>
              <input type="number" min="80" max="230" value={q.customer.heightCm || ""}
                onChange={(e) => setQ({...q, customer:{...q.customer, heightCm: e.target.value ? Number(e.target.value) : null}})}
                placeholder="예: 173" />
            </div>
          </div>
          <div className="q-grid-2">
            <div className="q-field">
              <label>차량 모델 (옵션)</label>
              <input list="bike-options" value={q.customer.bike} onChange={(e) => setQ({...q, customer:{...q.customer, bike:e.target.value}})} placeholder="입력 시 카탈로그 자동완성 ↓" />
            </div>
            <div className="q-field">
              <label>💡 추천 사이즈 <small style={{color:"var(--fg-tertiary)",fontWeight:400}}>(자동)</small></label>
              <SizeRecommendBadge customer={q.customer} />
            </div>
          </div>
          <div className="q-field">
            <label>주소 / 메모</label>
            <textarea value={q.customer.note} onChange={(e) => setQ({...q, customer:{...q.customer, note:e.target.value}})} placeholder="세종특별자치시 ..." />
          </div>
        </div>
      </div>

      {/* PICKER — 컴팩트 */}
      <div className="q-block">
        <div className="q-block-h">항목 추가</div>

        {/* 2-tab segmented */}
        <div className="q-picker-tabs">
          <button className={"q-picker-tab " + (!isLaborTab ? "on":"")} onClick={()=>setTab("parts")}>
            <i style={{background:"var(--q-ink)"}} />파트 · 부품
          </button>
          <button className={"q-picker-tab " + (isLaborTab ? "on labor":"labor")} onClick={()=>setTab("fit")}>
            <i style={{background:"var(--q-dot)"}} />공임 · 서비스
          </button>
        </div>

        {/* 빠른 입력 */}
        <div className="q-quickadd">
          <div className="q-quickadd-name">
            <input
              value={name}
              onChange={(e)=>setName(e.target.value)}
              placeholder={isLaborTab ? "공임 항목명을 입력하세요" : "부품 · 장비명을 입력하세요"}
            />
          </div>
          <input
            className="q-quickadd-price"
            type="number" min="0" step="1000"
            value={price}
            onChange={(e)=>setPrice(e.target.value)}
            placeholder="단가 ₩"
          />
          <button
            className="q-quickadd-btn"
            onClick={addItem}
            disabled={!name.trim() || !Number(price)}
          >＋ 추가</button>
        </div>

        {/* 비고 — 항상 표시 */}
        <div className="q-field" style={{marginTop:8}}>
          <label>비고 (선택)</label>
          <input
            value={desc}
            onChange={(e)=>setDesc(e.target.value)}
            placeholder={isLaborTab ? "예) 1.5시간 / 우중 라이딩 후 점검" : "예) 사이즈 56 / 12s 호환"}
          />
        </div>

        {/* 할인 / 서비스 — 추가 시 미리 설정 */}
        <div className="q-quickadd-disc" style={{marginTop:10}}>
          <label className="q-quickadd-disc-l">할인 · 서비스</label>
          <div className="q-quickadd-disc-tabs">
            <button
              type="button"
              className={discType === "none" ? "on" : ""}
              onClick={()=>{setDiscType("none"); setDiscValue("");}}
            >없음</button>
            <button
              type="button"
              className={discType === "percent" ? "on" : ""}
              onClick={()=>setDiscType("percent")}
            >% 할인</button>
            <button
              type="button"
              className={discType === "amount" ? "on" : ""}
              onClick={()=>setDiscType("amount")}
            >₩ 할인</button>
            <button
              type="button"
              className={"service " + (discType === "service" ? "on" : "")}
              onClick={()=>{setDiscType("service"); setDiscValue("");}}
            >서비스 (무료)</button>
          </div>
          {(discType === "percent" || discType === "amount") && (
            <input
              className="q-quickadd-disc-input"
              type="number" min="0"
              value={discValue}
              onChange={(e)=>setDiscValue(e.target.value)}
              placeholder={discType === "percent" ? "할인율 (예: 10)" : "할인 금액 (₩)"}
            />
          )}
          {discType === "service" && (
            <div className="q-quickadd-disc-hint">
              ※ 무상 서비스 항목으로 추가됩니다. 금액 0원 표시.
            </div>
          )}
        </div>

        {/* 프리셋 — 보조 옵션으로 분리 */}
        {presets.length > 0 && (
          <details className="q-quickadd-more" style={{marginTop:8}}>
            <summary>＋ 프리셋에서 가져오기 ({presets.length}개)</summary>
            <select
              value={pickSku}
              onChange={(e)=>applyPreset(e.target.value)}
              style={{marginTop:6, height:36, padding:"0 12px", borderRadius:8, border:"1px solid var(--q-line)", background:"var(--q-paper)", font:"400 13px var(--font-sf)", color:"var(--q-ink)", width:"100%", outline:"none"}}
            >
              <option value="">── 선택 ──</option>
              {presets.map(p => (
                <option key={p.sku} value={p.sku}>{p.name} — {fmtKRW(p.price)}</option>
              ))}
            </select>
          </details>
        )}

        {/* LINE ITEMS */}
        <div className="q-lines" style={{marginTop:18}}>
          {q.items.length === 0 && (
            <div style={{padding:"40px 24px", textAlign:"center",
                         background:"var(--grey-50,#F9FAFB)", border:"1px dashed var(--grey-200,#E5E8EB)",
                         borderRadius:12, color:"var(--q-ink-soft)"}}>
              <div style={{fontSize:36,marginBottom:8}}>🧾</div>
              <div style={{fontSize:14,fontWeight:600,color:"var(--grey-900,#191F28)",marginBottom:4}}>
                항목이 없어요
              </div>
              <div style={{fontSize:12,color:"var(--grey-600,#6B7684)",lineHeight:1.5}}>
                위 "항목 추가" 폼에서 파트/공임을 입력하거나<br/>
                상단의 <b>빠른 템플릿</b>을 클릭하면 자동으로 채워집니다.
              </div>
            </div>
          )}
          {q.items.map((it, idx) => {
            const gross = it.price * it.qty;
            const disc =
              it.discount.type === "percent" ? Math.round(gross * it.discount.value / 100) :
              it.discount.type === "amount"  ? (it.discount.value || 0) : 0;
            const net = Math.max(0, gross - disc);
            const isLabor = it.kind === "fit";
            const isDragging = draggingId === it.id;
            return (
              <div
                key={it.id}
                className={"q-line-row " + (isLabor ? "labor " : "part ") + (isDragging ? "dragging" : "")}
                draggable
                onDragStart={(e) => { setDraggingId(it.id); e.dataTransfer.effectAllowed = "move"; }}
                onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; }}
                onDrop={(e) => { e.preventDefault(); reorderLine(draggingId, it.id); setDraggingId(null); }}
                onDragEnd={() => setDraggingId(null)}
              >
                <div className="q-line-top">
                  <div className="q-line-drag">
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/></svg>
                  </div>
                  <span className={"q-line-tag " + (isLabor ? "labor" : "part")}>
                    {isLabor ? "공임" : "파트"}
                  </span>
                  <div className="q-line-meta">
                    <input
                      className="q-line-name-edit"
                      value={it.name}
                      onChange={(e)=>updateLine(it.id, { name: e.target.value })}
                      placeholder="품목명"
                    />
                    <input
                      className="q-line-desc-edit"
                      value={it.desc || ""}
                      onChange={(e)=>updateLine(it.id, { desc: e.target.value })}
                      placeholder="비고 (선택)"
                    />
                  </div>
                  <div className="q-line-net">
                    {disc > 0 && <s>{fmtKRW(gross)}</s>}
                    <b>{fmtKRW(net)}</b>
                  </div>
                  <div className="q-line-controls">
                    <button onClick={()=>moveLine(it.id, -1)} disabled={idx === 0} title="위로">
                      <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6"><polyline points="6 15 12 9 18 15"/></svg>
                    </button>
                    <button onClick={()=>moveLine(it.id, 1)} disabled={idx === q.items.length - 1} title="아래로">
                      <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6"><polyline points="6 9 12 15 18 9"/></svg>
                    </button>
                    <button className="del" onClick={()=>removeItem(it.id)} title="삭제">
                      <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M18 6 6 18M6 6l12 12"/></svg>
                    </button>
                  </div>
                </div>

                <div className="q-line-bottom">
                  <div className="q-line-field">
                    <span className="q-line-field-l">단가 (₩)</span>
                    <input
                      type="number" min="0" step="1000"
                      value={it.price}
                      onChange={(e)=>updateLine(it.id, { price: Number(e.target.value) || 0 })}
                    />
                  </div>
                  <div className="q-line-field qty">
                    <span className="q-line-field-l">수량</span>
                    <div className="qty-group">
                      <button onClick={()=>updateQty(it.id,-1)}>−</button>
                      <input type="number" min="1" value={it.qty}
                        onChange={(e)=>updateLine(it.id, { qty: Math.max(1, Number(e.target.value)||1) })} />
                      <button onClick={()=>updateQty(it.id, 1)}>＋</button>
                    </div>
                  </div>
                  <div className="q-line-field disc">
                    <span className="q-line-field-l">할인</span>
                    <div className="disc-group">
                      <select
                        value={it.discount.type}
                        onChange={(e)=>updateLineDisc(it.id, { type: e.target.value, value: 0 })}
                      >
                        <option value="none">—</option>
                        <option value="percent">%</option>
                        <option value="amount">₩</option>
                      </select>
                      <input
                        type="number" min="0"
                        disabled={it.discount.type === "none"}
                        value={it.discount.value || ""}
                        onChange={(e)=>updateLineDisc(it.id, { value: Number(e.target.value) || 0 })}
                        placeholder="0"
                      />
                    </div>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* OPTIONS (할인은 라인별로 이동, 여기는 유효기간/VAT만) */}
      <div className="q-block">
        <div className="q-block-h">발행 옵션</div>
        <div className="q-grid-2">
          <div className="q-field">
            <label>유효기간</label>
            <input type="date" value={q.validUntil} onChange={(e)=>setQ({...q, validUntil: e.target.value})} />
          </div>
          <div className="q-field">
            <label>VAT</label>
            <select value={q.vatIncluded ? "y" : "n"} onChange={(e)=>setQ({...q, vatIncluded: e.target.value==="y"})}>
              <option value="y">표시가에 포함</option>
              <option value="n">별도 부과 (10%)</option>
            </select>
          </div>
        </div>
        <div className="q-field" style={{marginTop:14}}>
          <label>견적 메모 (옵션)</label>
          <textarea
            value={q.note || ""}
            onChange={(e)=>setQ({...q, note: e.target.value})}
            placeholder="예) 동호회 회원 패키지 · 풀 정비 + 부품 동시 작업"
          />
        </div>
      </div>

      <div className="q-actions">
        <button className="q-btn q-btn-ghost" onClick={() => setQ(makeBlank())}>
          새로 시작
        </button>
        {/* v3.12 Phase B2: Supabase 저장 (운영 모드에서만 활성) */}
        {window.DAL?.isOnline && (
          <button className="q-btn" onClick={onSaveSupabase}
            style={{background:"var(--q-ink,#1B1B1F)",color:"#fff"}}>
            💾 발행 (저장)
          </button>
        )}
        <button className="q-btn q-btn-ghost" onClick={onCopyImage}>
          클립보드 복사
        </button>
        <button className="q-btn q-btn-ghost" onClick={onSaveImage}>
          이미지 저장
        </button>
        <button className="q-btn q-btn-kakao" onClick={onShareKakao}>
          카카오톡 공유
        </button>
      </div>
    </aside>
  );
}

// ─── DOCUMENT (right pane — printable) ───────────────────
function Document({ q }) {
  // 라인별 합계 — 단가 × 수량 − 할인
  const lineCalc = (it) => {
    const gross = it.price * it.qty;
    const disc =
      it.discount?.type === "percent" ? Math.round(gross * it.discount.value / 100) :
      it.discount?.type === "amount"  ? (it.discount.value || 0) : 0;
    return { gross, disc, net: Math.max(0, gross - disc) };
  };

  // 파트 / 공임 분리 — kind === "fit" 라인은 공임으로 집계
  const partsSum    = q.items.filter(b => b.kind !== "fit").reduce((a, b) => a + lineCalc(b).gross, 0);
  const laborSum    = q.items.filter(b => b.kind === "fit").reduce((a, b) => a + lineCalc(b).gross, 0);
  const subtotal    = q.items.reduce((a, b) => a + lineCalc(b).gross, 0);
  const discountSum = q.items.reduce((a, b) => a + lineCalc(b).disc, 0);
  const afterDisc   = Math.max(0, subtotal - discountSum);
  const vat         = q.vatIncluded ? Math.round(afterDisc * 10 / 110) : Math.round(afterDisc * 0.10);
  const grand       = q.vatIncluded ? afterDisc : afterDisc + vat;

  // 분류별 그룹화 (인쇄용)
  const kindLabel = { bike: "Bicycle", fit: "Service · Fit / Tune", parts: "Parts · Consumables", acc: "Accessories" };
  const byKind = q.items.reduce((acc, it) => {
    (acc[it.kind] = acc[it.kind] || []).push(it);
    return acc;
  }, {});

  return (
    <section className="q-paper">
      <span className="q-crop-tl" /><span className="q-crop-tr" />

      {/* TITLE */}
      <div className="q-doc-title">
        <em>견적서 · Estimate</em>
        <h1>서비스 견적서</h1>
        <div className="q-doc-no">No. {q.no}</div>
      </div>

      {/* META */}
      <div className="q-doc-meta">
        <div className="q-meta-cell">
          <div className="q-meta-l">수신</div>
          <div className="q-meta-name">{q.customer.name || "── 고객명 ──"} 귀하</div>
          <dl>
            <div className="q-meta-row"><dt>연락처</dt><dd>{q.customer.phone || "—"}</dd></div>
            <div className="q-meta-row"><dt>차량</dt><dd>{q.customer.bike || "—"}</dd></div>
            {q.customer.heightCm && (
              <div className="q-meta-row"><dt>신장</dt><dd>{q.customer.heightCm}cm</dd></div>
            )}
            <div className="q-meta-row"><dt>메모</dt><dd style={{whiteSpace:"pre-wrap"}}>{q.customer.note || "—"}</dd></div>
          </dl>
        </div>
        <div className="q-meta-cell">
          <div className="q-meta-l">발행</div>
          <div className="q-meta-name">{q.issuedAt}</div>
          <dl>
            <div className="q-meta-row"><dt>유효기간</dt><dd>{q.validUntil}</dd></div>
            <div className="q-meta-row"><dt>발행자</dt><dd>스페셜라이즈드 세종점</dd></div>
            <div className="q-meta-row"><dt>통화</dt><dd>KRW (₩) · {q.vatIncluded ? "VAT 포함" : "VAT 별도 (10%)"}</dd></div>
          </dl>
        </div>
      </div>

      {/* ITEMS */}
      <div className="q-doc-section">
        <div className="q-doc-section-h">
          <h2>견적 내역</h2>
          <em>{q.items.length}개 항목</em>
        </div>
        {q.items.length === 0 ? (
          <div className="q-items-empty">왼쪽 패널에서 항목을 추가하면 견적이 채워집니다.</div>
        ) : (
          <table className="q-items">
            <thead>
              <tr>
                <th style={{width:30}}>No.</th>
                <th>품목</th>
                <th className="center" style={{width:42}}>수량</th>
                <th className="num" style={{width:100}}>단가</th>
                <th className="num" style={{width:80}}>할인</th>
                <th className="num" style={{width:120}}>금액</th>
              </tr>
            </thead>
            <tbody>
              {q.items.map((it, idx) => {
                const { gross, disc, net } = lineCalc(it);
                const isService = it.discount?.service || (it.discount?.type === "percent" && it.discount.value === 100);
                const discLbl =
                  isService ? <span style={{color:"#15803D",fontWeight:700}}>서비스</span> :
                  it.discount?.type === "percent" && it.discount.value > 0 ? `−${it.discount.value}%` :
                  it.discount?.type === "amount"  && it.discount.value > 0 ? `−${fmtKRW(it.discount.value)}` : "—";
                const isLabor = it.kind === "fit";
                return (
                  <tr key={it.id}>
                    <td className="idx">{String(idx+1).padStart(2,"0")}</td>
                    <td className="q-items-name">
                      <div className="q-items-name-row">
                        <span className={"q-items-kind " + (isLabor ? "labor" : "part")}>
                          {isLabor ? "공임" : "파트"}
                        </span>
                        <b>{it.name}</b>
                      </div>
                      {it.desc && <small>{it.desc}</small>}
                    </td>
                    <td className="center">{it.qty}</td>
                    <td className="num">{fmtKRW(it.price)}</td>
                    <td className="num" style={{color: disc > 0 ? "var(--q-dot)" : "var(--q-ink-soft)"}}>
                      {discLbl}
                    </td>
                    <td className="num">
                      {disc > 0 && <s style={{color:"var(--q-ink-soft)", fontWeight:400, fontSize:11, marginRight:6}}>{fmtKRW(gross)}</s>}
                      <b>{fmtKRW(net)}</b>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        )}
      </div>

      {/* TOTALS */}
      <div className="q-totals">
        <div className="q-totals-l">
          <div className="q-terms-h">서비스 이용 안내 및 보증 규정</div>
          <div className="q-totals-fineprint">
            모든 금액은 부가세(VAT)를 포함한 원화(KRW) 기준이며, 상세 내역은 상단을 참조해 주시기 바랍니다.<br/>
            본 견적서 및 정비 내역의 유효기간은 발행일로부터 14일간입니다.
          </div>
        </div>

        <div className="q-totals-r">
          {(partsSum > 0 || laborSum > 0) && (
            <div className="q-totals-breakdown">
              {partsSum > 0 && (
                <div className="q-bd-row">
                  <span className="q-bd-dot" style={{background:"var(--q-ink)"}} />
                  <span>파트 · 부품</span>
                  <b>{fmtKRW(partsSum)}</b>
                </div>
              )}
              {laborSum > 0 && (
                <div className="q-bd-row">
                  <span className="q-bd-dot" style={{background:"var(--q-dot)"}} />
                  <span>공임 · 서비스</span>
                  <b>{fmtKRW(laborSum)}</b>
                </div>
              )}
            </div>
          )}

          <div className="q-total-row">
            <div className="q-total-l">공급가</div>
            <div className="q-total-v">{fmtKRW(subtotal)}</div>
          </div>
          {discountSum > 0 && (
            <div className="q-total-row">
              <div className="q-total-l">총할인</div>
              <div className="q-total-v disc">− {fmtKRW(discountSum)}</div>
            </div>
          )}
          <div className="q-total-row">
            <div className="q-total-l">과세표준</div>
            <div className="q-total-v" style={{color:"var(--q-ink-mid)"}}>{fmtKRW(afterDisc)}</div>
          </div>
          <div className="q-total-row">
            <div className="q-total-l">부가세 (10%)</div>
            <div className="q-total-v">{q.vatIncluded ? <span style={{color:"var(--q-ink-soft)"}}>위 금액에 포함</span> : fmtKRW(vat)}</div>
          </div>
          <div className="q-total-row grand">
            <div>
              <div className="q-total-grand-l">최종 결제 금액</div>
              <div className="q-total-grand-sub">{q.vatIncluded ? "VAT 포함" : "VAT 별도 합산"}</div>
            </div>
            <div className="q-total-v"><small>₩</small>{fmtNum(grand)}</div>
          </div>
        </div>
      </div>

      {/* TERMS + SIGN */}
      <div className="q-doc-terms" style={{display:"none"}}>
      </div>

      <div className="q-doc-footer">
        <div className="q-doc-footer-c">
          <span>스페셜라이즈드 세종점</span>
          <span>{q.no}</span>
          <span>발행일 {q.issuedAt}</span>
        </div>
        <div>
          <img src="assets/wordmark-specialized.svg" alt="Specialized" />
        </div>
      </div>
    </section>
  );
}

// ─── INITIAL STATE ───────────────────────────────────────
function makeBlank() {
  return {
    no: quoteNo(),
    issuedAt: todayISO(),
    validUntil: validUntil(),
    customer: { name: "", phone: "", bike: "", note: "", heightCm: null },
    items: [],
    note: "",
    vatIncluded: true,
  };
}

function seedExample() {
  const q = makeBlank();
  q.customer = { name: "이도현", phone: "010-2345-6789", bike: "Tarmac SL7 (2022)", note: "동호회 그란폰도 출전 전 풀 점검 요청" };
  q.note = "동호회 회원 패키지 · 풀 정비 + 부품 교체 동시 작업";
  // 파트 라인과 공임 라인을 각각 분리. 공임은 모두 kind: "fit"
  q.items = [
    // 정비 (공임만)
    { id:"L1", sku:"TUNE-OH",     name:"Tarmac SL7 · Overhaul 풀 정비", desc:"BB · 헤드셋 · 휠 베어링 분해 + 그리스업",
      price: 380000, qty: 1, discount:{type:"percent", value:15}, kind:"fit" },
    { id:"L2", sku:"BGFIT-CHECK", name:"Fit Check (1h)",                 desc:"포지션 점검 · 클릿 재조정",
      price: 90000,  qty: 1, discount:{type:"percent", value:50}, kind:"fit" },

    // 부품 + 장착 공임 (별도 라인)
    { id:"L3a", sku:"PT-CHN-12",    name:"Shimano CN-M8100 체인 (12s)",   desc:"Ultegra/XT 호환 · 116 링크",
      price: 78000,  qty: 1, discount:{type:"none", value:0},    kind:"parts" },
    { id:"L3b", sku:"PT-CHN-12-LBR",name:"체인 교체 · 장착공임",          desc:"공임",
      price: 15000,  qty: 1, discount:{type:"none", value:0},    kind:"fit" },

    { id:"L4a", sku:"PT-CST-1134",  name:"Shimano R8100 카세트 11-34T",   desc:"Ultegra 12s 카세트",
      price: 168000, qty: 1, discount:{type:"percent", value:10}, kind:"parts" },
    { id:"L4b", sku:"PT-CST-1134-LBR", name:"카세트 교체 · 장착공임",     desc:"공임",
      price: 20000,  qty: 1, discount:{type:"none", value:0},    kind:"fit" },

    { id:"L5a", sku:"PT-BRP-MET",   name:"Metal 브레이크 패드",            desc:"디스크 · 우중/장거리",
      price: 38000,  qty: 2, discount:{type:"none", value:0},    kind:"parts" },
    { id:"L5b", sku:"PT-BRP-MET-LBR", name:"브레이크 패드 교체 · 장착공임", desc:"앞/뒤 일괄 공임",
      price: 25000,  qty: 1, discount:{type:"none", value:0},    kind:"fit" },

    // 액세서리 (파트만 — 부착 무상)
    { id:"L6", sku:"ACC-SADDLE",  name:"Power Pro 안장 · 143mm",          desc:"MIMIC 컴포트 + Ti 레일",
      price: 320000, qty: 1, discount:{type:"amount", value:50000}, kind:"acc" },
  ];
  return q;
}

// 간단 토스트 (오른쪽 하단)
function showToast(msg) {
  const t = document.createElement("div");
  t.textContent = msg;
  t.style.cssText = `
    position: fixed; bottom: 24px; right: 24px; z-index: 9999;
    background: #1B1B1F; color: #fff; padding: 12px 18px; border-radius: 8px;
    font: 600 13px/1 -apple-system, sans-serif;
    box-shadow: 0 20px 40px rgba(0,0,0,0.2);
    transform: translateY(20px); opacity: 0;
    transition: all 0.2s ease;
  `;
  document.body.appendChild(t);
  requestAnimationFrame(() => { t.style.transform = "translateY(0)"; t.style.opacity = "1"; });
  setTimeout(() => {
    t.style.transform = "translateY(20px)"; t.style.opacity = "0";
    setTimeout(() => t.remove(), 200);
  }, 2400);
}

// ─── APP ─────────────────────────────────────────────────
function App() {
  // A1: 자동 임시 저장 — 탭 열 때 자동 복원, 5초마다 저장
  const DRAFT_KEY = "sejong-quote-draft";
  const [q, setQ] = useState(() => {
    try {
      const saved = localStorage.getItem(DRAFT_KEY);
      if (saved) {
        const parsed = JSON.parse(saved);
        // 24시간 이내 draft만 복원
        if (parsed._ts && Date.now() - parsed._ts < 24*60*60*1000) {
          console.log("[quote] 임시 저장본 복원 (저장 시각:", new Date(parsed._ts).toLocaleString(), ")");
          return parsed;
        }
      }
    } catch (e) { console.warn("[quote] draft 복원 실패:", e); }
    return seedExample();
  });

  // 5초마다 자동 임시 저장
  useEffect(() => {
    const t = setTimeout(() => {
      try {
        localStorage.setItem(DRAFT_KEY, JSON.stringify({ ...q, _ts: Date.now() }));
      } catch (e) { /* quota exceed 등 무시 */ }
    }, 5000);
    return () => clearTimeout(t);
  }, [q]);

  // A5: 권한 체크 — manager는 견적 발행 불가
  const role = window.AUTH?.role || "owner";
  const canIssue = ["owner","mechanic"].includes(role);

  // 카카오 SDK 초기화 (앱 키는 매장 운영자가 발급 후 교체)
  useEffect(() => {
    if (window.Kakao && !window.Kakao.isInitialized()) {
      try { window.Kakao.init("YOUR_KAKAO_JAVASCRIPT_KEY"); } catch(e) {}
    }
  }, []);

  // 권한 없으면 즉시 차단
  if (!canIssue) {
    return (
      <div style={{padding:"80px 24px",textAlign:"center",minHeight:"100vh",
                   display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"}}>
        <div style={{fontSize:48,marginBottom:16}}>🔒</div>
        <h2 style={{margin:"0 0 8px"}}>견적 발행 권한 없음</h2>
        <p style={{color:"var(--grey-600)",marginBottom:24,maxWidth:480}}>
          견적서 발행은 <b>정비 매니저(mechanic)</b> 또는 <b>매장 대표(owner)</b> 권한이 필요합니다.<br/>
          현재 역할: <b>{role}</b>
        </p>
        <a href="관리자.html" className="cat-btn cat-btn-primary">← 관리자 대시보드로</a>
      </div>
    );
  }

  // 합계 계산 (간단 버전, 공유 메시지용)
  const calcTotal = () => {
    let subtotal = 0, disc = 0;
    q.items.forEach(it => {
      const g = it.price * it.qty;
      subtotal += g;
      if (it.discount?.type === "percent") disc += Math.round(g * it.discount.value / 100);
      else if (it.discount?.type === "amount") disc += it.discount.value || 0;
    });
    const after = Math.max(0, subtotal - disc);
    const vat = q.vatIncluded ? 0 : Math.round(after * 0.10);
    return q.vatIncluded ? after : after + vat;
  };

  const onSaveImage = async () => {
    const paper = document.querySelector(".q-paper");
    if (!paper || !window.htmlToImage) {
      alert("이미지 저장 라이브러리를 불러올 수 없습니다.");
      return;
    }
    try {
      const dataUrl = await window.htmlToImage.toPng(paper, {
        pixelRatio: 2,
        backgroundColor: "#FFFFFF",
        cacheBust: true,
      });
      const a = document.createElement("a");
      a.href = dataUrl;
      a.download = `견적서_${q.no}_${(q.customer.name || "고객").replace(/\s+/g,"_")}.png`;
      a.click();
    } catch (err) {
      console.error(err);
      alert("이미지 저장에 실패했습니다.");
    }
  };

  const onCopyImage = async () => {
    const paper = document.querySelector(".q-paper");
    if (!paper || !window.htmlToImage) {
      alert("이미지 라이브러리를 불러올 수 없습니다.");
      return;
    }
    try {
      const blob = await window.htmlToImage.toBlob(paper, {
        pixelRatio: 2,
        backgroundColor: "#FFFFFF",
        cacheBust: true,
      });
      if (!blob) throw new Error("blob 생성 실패");
      // 모던 브라우저: 이미지 클립보드 복사
      if (window.ClipboardItem && navigator.clipboard?.write) {
        await navigator.clipboard.write([
          new ClipboardItem({ "image/png": blob })
        ]);
        // 확인 토스트
        showToast("견적서 이미지가 클립보드에 복사되었습니다");
        return;
      }
      // 폴백: 텍스트만 복사
      const fallback = `[견적서 ${q.no}] ${q.customer.name || "고객"} 귀하 · 합계 ₩${calcTotal().toLocaleString("ko-KR")}`;
      await navigator.clipboard.writeText(fallback);
      alert("이미지 복사가 지원되지 않는 환경입니다.\n견적 요약 텍스트를 복사했습니다.");
    } catch (err) {
      console.error(err);
      alert("클립보드 복사에 실패했습니다. 브라우저 권한을 확인해 주세요.");
    }
  };

  const onShareKakao = async () => {
    const total = calcTotal();
    const fmt = "₩" + total.toLocaleString("ko-KR");
    const summary = `${q.customer.name || "고객"}님 견적서 (No. ${q.no})\n총 ${q.items.length}개 항목 · 합계 ${fmt}\n유효: ${q.validUntil}`;

    // 1) 카카오톡 SDK가 초기화된 경우 — sendDefault
    if (window.Kakao?.isInitialized() && window.Kakao.Share) {
      try {
        window.Kakao.Share.sendDefault({
          objectType: "text",
          text: `[김성호바이크 세종점 · 견적서]\n\n${summary}`,
          link: { mobileWebUrl: location.href, webUrl: location.href },
        });
        return;
      } catch (e) { /* fallthrough */ }
    }

    // 2) Web Share API (모바일에서 카카오톡으로 공유 가능)
    if (navigator.share) {
      try {
        // 이미지도 같이 보내기
        let files = [];
        if (window.htmlToImage) {
          const paper = document.querySelector(".q-paper");
          const blob = await window.htmlToImage.toBlob(paper, { pixelRatio: 2, backgroundColor: "#FFFFFF" });
          if (blob) files = [new File([blob], `견적서_${q.no}.png`, { type: "image/png" })];
        }
        await navigator.share({
          title: "김성호바이크 세종점 · 견적서",
          text: summary,
          files: files.length && navigator.canShare?.({ files }) ? files : undefined,
        });
        return;
      } catch (e) { /* fallthrough */ }
    }

    // 3) Fallback — 클립보드 복사
    try {
      await navigator.clipboard.writeText(summary + "\n\n매장: 044-865-0000");
      alert("공유할 수 없는 환경입니다.\n견적 요약을 클립보드에 복사했습니다.\n카카오톡에 붙여넣어 전송하세요.");
    } catch (e) {
      prompt("아래 내용을 복사해서 카카오톡에 전송하세요:", summary);
    }
  };

  // v3.12 Phase B2: Supabase 저장 (견적 + 라인 + PDF 업로드)
  const onSaveSupabase = async () => {
    if (!window.DAL?.isOnline) {
      alert("운영 환경에서만 저장 가능합니다 (Supabase 미연동).");
      return;
    }
    if (!q.customer?.name) {
      alert("고객 성명을 입력해 주세요.");
      return;
    }
    if (q.items.length === 0) {
      alert("항목을 1개 이상 추가해 주세요.");
      return;
    }
    try {
      // 1. 견적 + 라인 계산
      let subtotal_parts = 0, subtotal_labor = 0, total_discount = 0;
      const lines = q.items.map((it, i) => {
        const gross = (it.price || 0) * (it.qty || 1);
        let disc = 0;
        if (it.discount?.type === "percent") disc = Math.round(gross * it.discount.value / 100);
        else if (it.discount?.type === "amount") disc = it.discount.value || 0;
        else if (it.discount?.type === "service") disc = gross;
        const line_total = Math.max(0, gross - disc);
        if (it.kind === "part") subtotal_parts += line_total;
        else subtotal_labor += line_total;
        total_discount += disc;
        return {
          kind: it.kind, name: it.name, desc_text: it.desc || "",
          qty: it.qty, unit_price: it.price,
          discount_type: it.discount?.type || "none",
          discount_value: it.discount?.value || 0,
          line_total,
        };
      });
      const subtotal = subtotal_parts + subtotal_labor;
      const vat = q.vatIncluded ? 0 : Math.round(subtotal * 0.10);
      const grand_total = subtotal + vat;

      // 2. createQuote RPC
      const saved = await window.DAL.createQuote({
        quote_no:        q.no,
        customer_name:   q.customer.name,
        customer_phone:  q.customer.phone || null,
        customer_bike:   q.customer.bike  || null,
        customer_memo:   q.customer.note  || null,
        // v3.16: 고객 신장 (선택)
        customer_height_cm: q.customer.heightCm || null,
        valid_until:     q.validUntil     || null,
        vat_mode:        q.vatIncluded ? "included" : "excluded",
        subtotal_parts, subtotal_labor, total_discount, vat, grand_total,
        status: "issued",
      }, lines);

      // 3. (선택) PDF 업로드 — html-to-image + STORAGE
      if (window.STORAGE?.uploadQuotePdf) {
        const paper = document.querySelector(".q-paper");
        if (paper && window.htmlToImage) {
          try {
            const blob = await window.htmlToImage.toBlob(paper, { pixelRatio: 2, backgroundColor: "#FFFFFF" });
            if (blob) {
              await window.STORAGE.uploadQuotePdf(blob, q.no);
            }
          } catch (e) { console.warn("[B2] PDF 업로드 실패:", e); }
        }
      }

      // 4. localStorage draft 삭제 (저장됐으니)
      try { localStorage.removeItem("sejong-quote-draft"); } catch {}

      if (window.SECURITY) window.SECURITY.showToast(`✓ 견적 발행 완료 — Supabase 저장됨 (${saved.quote_no || q.no})`, "success", 3000);
      else alert(`견적 발행 완료\n번호: ${saved.quote_no || q.no}`);
    } catch (e) {
      console.error("[B2] 견적 저장 실패:", e);
      if (window.SECURITY) window.SECURITY.showToast(`견적 저장 실패: ${e.message}`, "error", 4000);
      else alert(`견적 저장 실패: ${e.message}`);
    }
  };

  return (
    <div className="q-app">
      <Header />
      <PageNav />
      <main className="q-main">
        <Builder q={q} setQ={setQ} onSaveImage={onSaveImage} onCopyImage={onCopyImage} onShareKakao={onShareKakao} onSaveSupabase={onSaveSupabase} />
        <Document q={q} />
      </main>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
