<?php /* views/template/builder.php — updated with store_header element & Indomaret preset */ ?>
<style>
/* ════════════════════════════════════════════════════════
   BUILDER LAYOUT
════════════════════════════════════════════════════════ */
.builder-topbar {
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  padding:10px 0 14px; margin-bottom:0;
}
.builder-topbar .form-control { margin-bottom:0; }
.topbar-sep { width:1px; height:28px; background:#E0E0E0; flex-shrink:0; }
.topbar-spacer { flex:1; }

/* Paper size toggle */
.paper-toggle {
  display:inline-flex; background:#F5F5F5; border:1.5px solid #DDD;
  border-radius:7px; overflow:hidden; flex-shrink:0;
}
.paper-btn {
  padding:5px 13px; font-size:12px; font-weight:700;
  font-family:'Courier New',monospace; cursor:pointer;
  border:none; background:transparent; color:#888;
  transition:all .15s; white-space:nowrap; line-height:1.4;
}
.paper-btn.active { background:#1976D2; color:#fff; }

/* Three-column layout */
.builder-wrap {
  display:grid;
  grid-template-columns: 230px 1fr 285px;
  gap:14px;
  align-items:start;
}

/* ── LEFT SIDEBAR ── */
.bld-sidebar { display:flex; flex-direction:column; gap:8px; }
.tool-section { background:#fff; border:1px solid #E0E0E0; border-radius:10px; overflow:hidden; box-shadow:0 1px 4px rgba(0,0,0,.05); }
.tool-section-title {
  padding:7px 12px; font-size:10px; font-weight:700;
  letter-spacing:.7px; text-transform:uppercase; color:#888;
  background:#FAFAFA; border-bottom:1px solid #EEEEEE;
}
.tool-list { padding:5px; }
.tool-btn {
  display:flex; align-items:center; gap:9px;
  width:100%; padding:7px 9px; margin-bottom:2px;
  border:1.5px solid transparent; border-radius:7px;
  background:transparent; cursor:pointer; font-family:inherit;
  text-align:left; transition:all .12s;
}
.tool-btn:last-child { margin-bottom:0; }
.tool-btn:hover { background:#E3F2FD; border-color:#B3D4F5; }
.tool-btn .tb-icon {
  width:28px; height:28px; border-radius:6px;
  display:flex; align-items:center; justify-content:center;
  font-size:13px; flex-shrink:0; background:#F5F5F5;
}
.tool-btn .tb-icon.c-blue   { background:#E3F2FD; }
.tool-btn .tb-icon.c-green  { background:#E8F5E9; }
.tool-btn .tb-icon.c-orange { background:#FFF3E0; }
.tool-btn .tb-icon.c-purple { background:#F3E5F5; }
.tool-btn .tb-label { font-size:12px; font-weight:600; color:#333; }
.tool-btn .tb-desc  { font-size:10px; color:#999; margin-top:1px; }
.tool-btn.preset      { background:#E3F2FD; border-color:#90CAF9; }
.tool-btn.preset:hover{ background:#BBDEFB; border-color:#42A5F5; }
.tool-btn.preset-indo      { background:#FFF3E0; border-color:#FFCC80; }
.tool-btn.preset-indo:hover{ background:#FFE0B2; border-color:#FFA726; }

/* ── CANVAS AREA ── */
.canvas-card { background:#fff; border:1px solid #E0E0E0; border-radius:10px; padding:14px; box-shadow:0 1px 4px rgba(0,0,0,.05); }
.canvas-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
.canvas-title  { font-weight:700; font-size:13px; color:#333; }
.canvas-hints  { display:flex; gap:6px; flex-wrap:wrap; }
.canvas-pill {
  display:inline-flex; align-items:center; gap:3px;
  padding:2px 8px; border-radius:20px; font-size:10px;
  font-weight:600; background:#F5F5F5; color:#888;
  border:1px solid #E0E0E0;
}
.canvas-outer { display:flex; justify-content:center; }

/* ── CANVAS (receipt paper) ── */
#builder-canvas {
  min-height:460px; background:#FFFEF9;
  border:1.5px dashed #CCC; border-radius:4px;
  padding:10px 7px; font-family:'Courier New',monospace;
  font-size:11px; transition:width .2s ease;
}
#builder-canvas.paper-58 { width:218px; }
#builder-canvas.paper-80 { width:302px; }
#builder-canvas.empty::after {
  content:'Klik elemen di kiri\nuntuk menambahkan';
  display:block; text-align:center; color:#ccc; font-size:12px;
  font-family:sans-serif; padding:60px 16px; white-space:pre; line-height:2.2;
}

/* ── CANVAS ELEMENTS ── */
.canvas-el {
  display:flex; align-items:flex-start; gap:0;
  margin-bottom:2px; border:1.5px solid transparent;
  border-radius:4px; cursor:pointer; font-size:11px;
  line-height:1.45; transition:background .1s,border-color .1s,box-shadow .1s;
  position:relative; user-select:none;
}
.canvas-el:hover   { border-color:#90CAF9 !important; background:#F5F9FF; }
.canvas-el.selected {
  border-color:#1976D2 !important; background:#E3F2FD !important;
  box-shadow:0 0 0 2px rgba(25,118,210,.12);
}
.canvas-el.sortable-ghost  { opacity:.3; background:#BBDEFB !important; border:2px dashed #1976D2 !important; }
.canvas-el.sortable-chosen { box-shadow:0 4px 14px rgba(25,118,210,.22) !important; z-index:50; background:#fff !important; }

.drag-handle {
  width:14px; flex-shrink:0; cursor:grab;
  display:flex; align-items:center; justify-content:center;
  align-self:stretch; font-size:10px; color:#D0D0D0;
  border-radius:3px 0 0 3px; transition:color .1s,background .1s;
}
.drag-handle:hover   { color:#1976D2; background:rgba(25,118,210,.07); }
.drag-handle:active  { cursor:grabbing; }
.el-content { flex:1; padding:4px 6px 4px 4px; word-break:break-word; white-space:pre-wrap; min-width:0; }

.canvas-el.el-text         { border-left:3px solid #2196F3 !important; }
.canvas-el.el-input        { border-left:3px solid #4CAF50 !important; }
.canvas-el.el-row2         { border-left:3px solid #FF9800 !important; }
.canvas-el.el-multiply     { border-left:3px solid #9C27B0 !important; }
.canvas-el.el-spacer       { border-left:3px solid #BDBDBD !important; min-height:14px; }
.canvas-el.el-line         { border-left:3px solid #BDBDBD !important; }
.canvas-el.el-store_header { border-left:3px solid #E53935 !important; }

/* Store header preview */
.canvas-header-block { width:100%; font-family:'Courier New',monospace; font-size:10px; line-height:1.5; }
.canvas-header-logo {
  text-align:center; font-weight:bold; font-size:11.5px;
  border:1px solid #ccc; border-radius:3px; padding:4px 6px;
  background:#fff8f0; margin-bottom:3px; letter-spacing:1px;
  font-family:'Courier New',monospace;
}
.canvas-header-meta { font-size:9.5px; color:#444; text-align:center; line-height:1.55; font-family:'Courier New',monospace; }
.canvas-header-divider { border-top:1px dashed #ccc; margin:3px 0 0; }

.row2-chips { display:flex; gap:3px; width:100%; }
.row2-chip {
  flex:1; background:#FFF8E1; border:1px dashed #FFB74D;
  border-radius:3px; padding:3px 5px; font-size:9.5px;
  color:#E65100; text-align:center; font-family:sans-serif;
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

.skip-badge {
  position:absolute; right:3px; top:2px;
  font-size:8px; background:#FF9800; color:#fff;
  padding:1px 5px; border-radius:10px;
  font-family:sans-serif; font-weight:700; pointer-events:none;
}
.canvas-el.is-skip { opacity:.55; }

/* ── RIGHT PROPS PANEL ── */
.bld-props { display:flex; flex-direction:column; gap:8px; }
.props-card { background:#fff; border:1px solid #E0E0E0; border-radius:10px; box-shadow:0 1px 4px rgba(0,0,0,.05); overflow:hidden; }
.props-card-header {
  display:flex; align-items:center; justify-content:space-between;
  padding:8px 13px; font-size:10px; font-weight:700;
  letter-spacing:.7px; text-transform:uppercase; color:#888;
  background:#FAFAFA; border-bottom:1px solid #EEEEEE;
}
.props-type-badge { font-size:10px; font-weight:700; padding:2px 8px; border-radius:20px; text-transform:none; letter-spacing:0; }
.props-body { padding:12px 13px; }
.props-empty { padding:28px 16px; text-align:center; color:#bbb; font-size:12px; line-height:1.9; font-family:sans-serif; }
.props-empty-icon { font-size:26px; display:block; margin-bottom:6px; }

.fg { margin-bottom:9px; }
.fg:last-child { margin-bottom:0; }
.frow { display:flex; gap:7px; flex-wrap:wrap; }
.frow .fg { flex:1; min-width:65px; }
.fl { display:block; font-size:10.5px; font-weight:700; color:#666; text-transform:uppercase; letter-spacing:.3px; margin-bottom:3px; }
.fc {
  display:block; width:100%; border:1.5px solid #DDD; border-radius:6px;
  padding:5px 9px; font-size:12px; font-family:inherit;
  background:#FAFAFA; color:#333; outline:none; transition:border-color .15s;
}
.fc:focus { border-color:#1976D2; background:#fff; }
textarea.fc { resize:vertical; min-height:52px; }

.pdivider { height:1px; background:#EEEEEE; margin:10px 0; }
.pstitle  { font-size:10.5px; font-weight:700; color:#1976D2; margin-bottom:7px; text-transform:uppercase; letter-spacing:.3px; }

.col-block { border:1.5px solid #E0E0E0; border-radius:7px; padding:9px 10px; margin-bottom:9px; background:#FAFAFA; }
.col-block-title { font-size:10.5px; font-weight:700; color:#666; margin-bottom:7px; text-transform:uppercase; letter-spacing:.3px; }

.skip-row {
  display:flex; align-items:flex-start; gap:8px;
  background:#FFF8E1; border:1.5px solid #FFE082;
  border-radius:7px; padding:8px 10px; margin-top:2px;
}
.skip-row input[type=checkbox] { margin-top:2px; flex-shrink:0; }
.skip-row .sl { font-size:12px; font-weight:700; color:#E65100; }
.skip-row .sd { font-size:10px; color:#888; margin-top:2px; line-height:1.5; }

/* Store header props */
.header-block-section {
  background:#FFF8F8; border:1.5px solid #FFCDD2;
  border-radius:8px; padding:10px 11px; margin-bottom:9px;
}
.header-block-section-title {
  font-size:10.5px; font-weight:700; color:#C62828;
  margin-bottom:8px; text-transform:uppercase; letter-spacing:.3px;
  display:flex; align-items:center; gap:5px;
}

.el-actions { display:flex; gap:5px; margin-top:10px; }
.el-actions .btn { flex:1; justify-content:center; font-size:11px; padding:5px 6px; }

#saveStatus { display:none; margin-top:10px; border-radius:8px; font-size:13px; padding:9px 14px; font-weight:500; }
.alert-ok    { background:#E8F5E9; border:1px solid #A5D6A7; color:#2E7D32; }
.alert-info  { background:#E3F2FD; border:1px solid #90CAF9; color:#1565C0; }
.alert-error { background:#FFEBEE; border:1px solid #FFCDD2; color:#C62828; }

@media (max-width:960px) {
  .builder-wrap { grid-template-columns: 220px 1fr; }
  .bld-props { display:none; }
}
@media (max-width:640px) {
  .builder-wrap { grid-template-columns:1fr; }
}
</style>

<!-- ── TOP BAR ── -->
<div class="builder-topbar card" style="padding:10px 14px; margin-bottom:14px;">
  <strong style="font-size:14px; white-space:nowrap;">🧾 Template Builder</strong>
  <div class="topbar-sep"></div>

  <input type="text" id="tplName" class="form-control" style="max-width:200px; margin:0;"
    value="<?= htmlspecialchars($template->name ?? 'Template Baru') ?>"
    placeholder="Nama template...">

  <select id="tplStore" class="form-control" style="width:170px; margin:0;">
    <?php foreach ($stores as $s): ?>
      <option value="<?= $s->id ?>"
        <?= (isset($template) && $template && $template->store_id == $s->id) ? 'selected' : '' ?>>
        <?= htmlspecialchars($s->name) ?>
      </option>
    <?php endforeach; ?>
  </select>

  <div style="display:flex;align-items:center;gap:6px;flex-shrink:0;">
    <span style="font-size:11px;color:#888;font-weight:600;">Kertas:</span>
    <div class="paper-toggle">
      <button class="paper-btn active" data-mm="58" onclick="setPaper(58)">58 mm</button>
      <button class="paper-btn"        data-mm="80" onclick="setPaper(80)">80 mm</button>
    </div>
  </div>

  <div class="topbar-spacer"></div>
  <a href="<?= site_url('template') ?>" class="btn btn-light">← Kembali</a>
  <button class="btn btn-primary" onclick="saveTemplate()">💾 Simpan</button>
</div>

<!-- ── THREE-COLUMN WRAP ── -->
<div class="builder-wrap">

  <!-- ══ LEFT SIDEBAR ══ -->
  <div class="bld-sidebar">

    <!-- Preset -->
    <div class="tool-section">
      <div class="tool-section-title">⚡ Preset Cepat</div>
      <div class="tool-list">
        <button class="tool-btn preset-indo" onclick="loadPreset('minimarket')">
          <div class="tb-icon" style="background:#FFF3E0;font-size:11px;">🏪</div>
          <div>
            <div class="tb-label" style="color:#E65100;">Struk Minimarket</div>
            <div class="tb-desc">Gaya Indomaret / Alfamart</div>
          </div>
        </button>
        <button class="tool-btn preset" onclick="loadPreset('parkir')">
          <div class="tb-icon c-blue">🅿️</div>
          <div>
            <div class="tb-label" style="color:#1565C0">Struk Parkir</div>
            <div class="tb-desc">Template South Quarter</div>
          </div>
        </button>
      </div>
    </div>

    <!-- Header Toko -->
    <div class="tool-section">
      <div class="tool-section-title">🏢 Header Toko</div>
      <div class="tool-list">
        <button class="tool-btn" onclick="addEl('store_header')">
          <div class="tb-icon" style="background:#FFEBEE;">🏪</div>
          <div>
            <div class="tb-label">Header Toko</div>
            <div class="tb-desc">Logo + nama + alamat</div>
          </div>
        </button>
      </div>
    </div>

    <!-- Teks & Layout -->
    <div class="tool-section">
      <div class="tool-section-title">📋 Teks & Layout</div>
      <div class="tool-list">
        <button class="tool-btn" onclick="addEl('text')">
          <div class="tb-icon c-blue">📝</div>
          <div><div class="tb-label">Teks Statis</div><div class="tb-desc">Teks tetap / placeholder</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('date_field')">
          <div class="tb-icon c-blue">📅</div>
          <div><div class="tb-label">Tanggal Otomatis</div><div class="tb-desc">Gunakan {{date}}</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('line')">
          <div class="tb-icon">──</div>
          <div><div class="tb-label">Garis Pemisah</div><div class="tb-desc">Garis horizontal</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('spacer')">
          <div class="tb-icon">⬜</div>
          <div><div class="tb-label">Baris Kosong</div><div class="tb-desc">Jeda vertikal</div></div>
        </button>
      </div>
    </div>

    <!-- Input -->
    <div class="tool-section">
      <div class="tool-section-title">📥 Input Field</div>
      <div class="tool-list">
        <button class="tool-btn" onclick="addEl('input')">
          <div class="tb-icon c-green">▣</div>
          <div><div class="tb-label">Input Tunggal</div><div class="tb-desc">Satu field penuh</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('row2')">
          <div class="tb-icon c-orange">▣▣</div>
          <div><div class="tb-label">2 Input Sejajar</div><div class="tb-desc">Dua kolom 1 baris</div></div>
        </button>
      </div>
    </div>

    <!-- Kalkulasi -->
    <div class="tool-section">
      <div class="tool-section-title">🧮 Kalkulasi</div>
      <div class="tool-list">
        <button class="tool-btn" onclick="addEl('multiply')">
          <div class="tb-icon c-purple">✖️</div>
          <div><div class="tb-label">Perkalian</div><div class="tb-desc">Field A × Field B</div></div>
        </button>
      </div>
    </div>

    <!-- Sistem -->
    <div class="tool-section">
      <div class="tool-section-title">🏪 Sistem</div>
      <div class="tool-list">
        <button class="tool-btn" onclick="addEl('text','{{store_name}}','store_name')">
          <div class="tb-icon c-blue">🏪</div>
          <div><div class="tb-label">Nama Toko</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('text','{{store_address}}','store_address')">
          <div class="tb-icon c-blue">📍</div>
          <div><div class="tb-label">Alamat</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('text','No: {{invoice_no}}','invoice_no')">
          <div class="tb-icon c-blue">🔢</div>
          <div><div class="tb-label">No. Invoice</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('text','Kasir: {{cashier}}','cashier')">
          <div class="tb-icon c-blue">👤</div>
          <div><div class="tb-label">Kasir</div></div>
        </button>
        <button class="tool-btn" onclick="addEl('text','{{footer}}','footer')">
          <div class="tb-icon c-blue">📝</div>
          <div><div class="tb-label">Footer</div></div>
        </button>
      </div>
    </div>

  </div><!-- /bld-sidebar -->

  <!-- ══ CANVAS ══ -->
  <div class="bld-canvas-wrap">
    <div class="canvas-card">
      <div class="canvas-header">
        <div class="canvas-title">👁 Preview Struk</div>
        <div class="canvas-hints">
          <span class="canvas-pill" id="paperPill">📄 58 mm</span>
          <span class="canvas-pill">☰ Drag urutan</span>
          <span class="canvas-pill">Klik = Edit</span>
        </div>
      </div>
      <div class="canvas-outer">
        <div id="builder-canvas" class="paper-58 empty"></div>
      </div>
    </div>
  </div>

  <!-- ══ RIGHT PROPS PANEL ══ -->
  <div class="bld-props" id="bldProps">
    <div class="props-card">
      <div class="props-card-header">
        <span>⚙️ Properti</span>
        <span id="propsBadge" class="props-type-badge"></span>
      </div>
      <div id="propsBody">
        <div class="props-empty">
          <span class="props-empty-icon">🖱️</span>
          Klik elemen di canvas<br>untuk mengedit propertinya
        </div>
      </div>
    </div>
  </div>

</div><!-- /builder-wrap -->

<div id="saveStatus"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.3/Sortable.min.js"></script>
<script>
/* ═══════════════════════════════════════════════════════
   CONFIG
═══════════════════════════════════════════════════════ */
const COLS    = 32;
const APP_URL = '<?= site_url() ?>';
const TPL_ID  = <?= $template_id ?? 0 ?>;

let paperMm   = <?= isset($template) && $template && !empty($template->paper_width) ? (int)$template->paper_width : 58 ?>;
if (paperMm !== 58 && paperMm !== 80) paperMm = 58;
let elements  = <?= isset($template) && $template ? json_encode($template->layout_json) : '[]' ?>;
let idCounter = elements.length ? Math.max(...elements.map(e => parseInt(e.id)||0)) : 0;
let selectedId = null;

const INPUT_TYPES = [
  { val:'text',         label:'Teks' },
  { val:'number',       label:'Angka' },
  { val:'datetime',     label:'Tgl + Waktu (dd/mm/yyyy hh:mm:ss)' },
  { val:'datetime_min', label:'Tgl + Waktu (dd/mm/yyyy hh:mm)' },
  { val:'date',         label:'Tanggal saja' },
];

const FORMAT_OPTS = [
  { val:'plain',      label:'Biasa' },
  { val:'number_sep', label:'1.000 (titik)' },
  { val:'number',     label:'1000 (tanpa sep)' },
  { val:'rupiah',     label:'Rp 1.000' },
];

/* ═══════════════════════════════════════════════════════
   PAPER SIZE
═══════════════════════════════════════════════════════ */
function setPaper(mm) {
  paperMm = mm;
  const c = document.getElementById('builder-canvas');
  c.classList.remove('paper-58','paper-80');
  c.classList.add('paper-' + mm);
  document.querySelectorAll('.paper-btn').forEach(b =>
    b.classList.toggle('active', parseInt(b.dataset.mm) === mm)
  );
  document.getElementById('paperPill').textContent = '📄 ' + mm + ' mm';
}

/* ═══════════════════════════════════════════════════════
   SORTABLE
═══════════════════════════════════════════════════════ */
new Sortable(document.getElementById('builder-canvas'), {
  animation: 200, delay: 80, delayOnTouchOnly: true,
  handle: '.drag-handle',
  ghostClass: 'sortable-ghost',
  chosenClass: 'sortable-chosen',
  onEnd() { syncOrder(); renderCanvas(true); },
});

function syncOrder() {
  const ids = [...document.querySelectorAll('#builder-canvas .canvas-el')]
    .map(e => parseInt(e.dataset.id));
  const map = {};
  elements.forEach(e => map[e.id] = e);
  elements = ids.map((id, i) => { if (map[id]) { map[id].y = i; return map[id]; } }).filter(Boolean);
}

/* ═══════════════════════════════════════════════════════
   RENDER CANVAS
═══════════════════════════════════════════════════════ */
function renderCanvas(keepScroll) {
  const canvas = document.getElementById('builder-canvas');
  const sy = keepScroll ? canvas.scrollTop : 0;
  canvas.innerHTML = '';

  const sorted = [...elements].sort((a,b)=>(a.y||0)-(b.y||0));
  if (!sorted.length) { canvas.classList.add('empty'); return; }
  canvas.classList.remove('empty');

  sorted.forEach(el => {
    const div = document.createElement('div');
    div.className = 'canvas-el el-' + el.element_type;
    div.dataset.id = el.id;
    if (el.skip_print) div.classList.add('is-skip');

    const dh = document.createElement('div');
    dh.className = 'drag-handle'; dh.innerHTML = '⠿'; dh.title='Drag untuk pindah';
    div.appendChild(dh);

    const cnt = document.createElement('div');
    cnt.className = 'el-content';

    switch (el.element_type) {

      /* ── STORE HEADER ── */
      case 'store_header': {
        const logoTxt = el.logo_text    || el.company_name || 'TOKO';
        const compNm  = el.company_name || '';
        const addr1   = el.address_line1 || '';
        const addr2   = el.address_line2 || '';
        const npwp    = el.npwp  || '';
        const phone   = el.phone || '';
        const scode   = el.store_code || '';
        cnt.innerHTML =
          `<div class="canvas-header-block">` +
            `<div class="canvas-header-logo">${esc(logoTxt)}</div>` +
            (compNm ? `<div class="canvas-header-meta"><b>${esc(compNm)}</b></div>` : '') +
            (addr1  ? `<div class="canvas-header-meta">${esc(addr1)}</div>` : '') +
            (addr2  ? `<div class="canvas-header-meta">${esc(addr2)}</div>` : '') +
            (npwp   ? `<div class="canvas-header-meta">NPWP: ${esc(npwp)}</div>` : '') +
            (phone  ? `<div class="canvas-header-meta">Telp: ${esc(phone)}</div>` : '') +
            (scode  ? `<div class="canvas-header-meta" style="color:#888;">${esc(scode)}</div>` : '') +
            (el.show_divider ? `<div class="canvas-header-divider"></div>` : '') +
          `</div>`;
        break;
      }

      case 'text': {
        const fs = Math.min(3, Math.max(1, parseInt(el.font_size)||1));
        div.style.fontSize   = (9 + fs*2) + 'px';
        div.style.fontWeight = el.bold ? 'bold' : 'normal';
        cnt.style.textAlign  = el.align || 'left';
        cnt.style.whiteSpace = 'pre-wrap';
        cnt.textContent = el.value || '(kosong)';
        break;
      }
      case 'line':
        cnt.style.color = '#bbb'; cnt.style.letterSpacing = '1px';
        cnt.textContent = '- '.repeat(Math.floor(COLS/2));
        break;
      case 'spacer':
        cnt.innerHTML = '<span style="color:#CCC;font-size:9px;font-family:sans-serif;">▸ spasi</span>';
        div.style.minHeight = '14px';
        break;
      case 'input': {
        const tm = itypeMeta(el.input_type||'text');
        cnt.innerHTML =
          tag('▣ INPUT','#4CAF50') +
          `<b style="font-family:sans-serif;font-size:11px;">${esc(el.input_label||el.input_name||'?')}</b>` +
          sub('[' + tm + ']');
        if (el.skip_print) {
          const b = document.createElement('span'); b.className='skip-badge'; b.textContent='Form Only';
          div.appendChild(b);
        }
        break;
      }
      case 'row2': {
        const c1=el.col1||{}, c2=el.col2||{};
        cnt.innerHTML =
          `<div class="row2-chips">` +
          `<div class="row2-chip" style="flex:${c1.width||50}">${esc(c1.input_label||c1.input_name||'Kol 1')}<br><span style="opacity:.6;font-size:8.5px;">${itypeMeta(c1.input_type||'text')}</span></div>` +
          `<div class="row2-chip" style="flex:${c2.width||50}">${esc(c2.input_label||c2.input_name||'Kol 2')}<br><span style="opacity:.6;font-size:8.5px;">${itypeMeta(c2.input_type||'text')}</span></div>` +
          `</div>`;
        break;
      }
      case 'multiply':
        cnt.innerHTML =
          tag('✖ HITUNG','#9C27B0') +
          `<b style="font-family:sans-serif;font-size:11px;">${esc(el.display_label||'Total')}</b>` +
          sub(esc(el.input_a||'?') + ' × ' + esc(el.input_b||'?'));
        break;
    }

    div.appendChild(cnt);
    if (el.id === selectedId) div.classList.add('selected');
    div.addEventListener('click', e => {
      if (e.target.closest('.drag-handle')) return;
      e.stopPropagation(); selectEl(el.id);
    });
    canvas.appendChild(div);
  });
  canvas.scrollTop = sy;
}

function tag(t, color) {
  return `<span style="font-size:9px;font-family:sans-serif;font-weight:700;color:${color};margin-right:3px;">${t}</span>`;
}
function sub(t) {
  return `<span style="color:#999;font-size:9.5px;font-family:sans-serif;margin-left:2px;">${t}</span>`;
}
function esc(s) {
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function itypeMeta(v) {
  const f = INPUT_TYPES.find(t=>t.val===v);
  return f ? f.label : v;
}

/* ═══════════════════════════════════════════════════════
   SELECT
═══════════════════════════════════════════════════════ */
function selectEl(id) {
  selectedId = id;
  document.querySelectorAll('.canvas-el').forEach(d=>d.classList.remove('selected'));
  const dom = document.querySelector('.canvas-el[data-id="'+id+'"]');
  if (dom) { dom.classList.add('selected'); dom.scrollIntoView({behavior:'smooth',block:'nearest'}); }
  const el = elements.find(e=>e.id===id);
  if (el) renderProps(el);
}

document.getElementById('builder-canvas').addEventListener('click', function(e) {
  if (e.target === this) {
    selectedId = null;
    document.querySelectorAll('.canvas-el').forEach(d=>d.classList.remove('selected'));
    showPropsEmpty();
  }
});

function showPropsEmpty() {
  document.getElementById('propsBadge').innerHTML = '';
  document.getElementById('propsBody').innerHTML =
    '<div class="props-empty"><span class="props-empty-icon">🖱️</span>Klik elemen di canvas<br>untuk mengedit propertinya</div>';
}

/* ═══════════════════════════════════════════════════════
   PROPS PANEL
═══════════════════════════════════════════════════════ */
const TYPE_BADGE_STYLES = {
  text:         'background:#E3F2FD;color:#1565C0',
  input:        'background:#E8F5E9;color:#1B5E20',
  row2:         'background:#FFF3E0;color:#E65100',
  multiply:     'background:#F3E5F5;color:#4A148C',
  line:         'background:#F5F5F5;color:#555',
  spacer:       'background:#F5F5F5;color:#555',
  store_header: 'background:#FFEBEE;color:#B71C1C',
};
const TYPE_LABELS = {
  text:'Teks', input:'Input', row2:'2 Kolom', multiply:'Hitung',
  line:'Garis', spacer:'Spasi', store_header:'Header Toko',
};

function renderProps(el) {
  const type = el.element_type;
  document.getElementById('propsBadge').innerHTML =
    `<span class="props-type-badge" style="${TYPE_BADGE_STYLES[type]||''}">${TYPE_LABELS[type]||type}</span>`;

  let h = '<div class="props-body">';

  if (type==='spacer'||type==='line') {
    h += '<p style="font-size:12px;color:#999;line-height:1.7;">Tidak ada pengaturan untuk elemen ini.</p>';
  }

  /* ─ STORE HEADER ─ */
  if (type==='store_header') {
    h += `<div class="header-block-section">
      <div class="header-block-section-title">🏪 Identitas / Logo Toko</div>
      ${fg('Teks Logo / Nama Brand',
        `<input class="fc" type="text" value="${esc(el.logo_text||'')}" placeholder="cth: Indomaret — bisa pakai {{store_name}}" oninput="upd('logo_text',this.value)">`
      )}
      <div style="font-size:9.5px;color:#aaa;margin-top:-5px;margin-bottom:8px;line-height:1.5;">Tampil menonjol sebagai "logo" tercetak di bagian atas struk. Bisa pakai <code>{{store_name}}</code>.</div>
      ${fg('Nama Perusahaan',
        `<input class="fc" type="text" value="${esc(el.company_name||'')}" placeholder="cth: PT INDOMARCO PRISMATAMA (opsional)" oninput="upd('company_name',this.value)">`
      )}
    </div>`;

    h += pdiv();
    h += `<div class="pstitle">📍 Alamat</div>`;
    h += fg('Alamat Baris 1',
      `<input class="fc" type="text" value="${esc(el.address_line1||'')}" placeholder="cth: JL. BRIGJEN SLAMET RIADI NO.186 — atau {{store_address}}" oninput="upd('address_line1',this.value)">`
    );
    h += fg('Alamat Baris 2',
      `<input class="fc" type="text" value="${esc(el.address_line2||'')}" placeholder="cth: RT.003 RW.006, MALANG, 65119" oninput="upd('address_line2',this.value)">`
    );

    h += pdiv();
    h += `<div class="pstitle">📋 Info Tambahan</div>`;
    h += frow([
      fg('No. NPWP', `<input class="fc" type="text" value="${esc(el.npwp||'')}" placeholder="xx.xxx.xxx.x-xxx.xxx" oninput="upd('npwp',this.value)">`),
      fg('No. Telpon', `<input class="fc" type="text" value="${esc(el.phone||'')}" placeholder="021-XXXXXXXX" oninput="upd('phone',this.value)">`),
    ]);
    h += fg('Kode Toko / ID Outlet',
      `<input class="fc" type="text" value="${esc(el.store_code||'')}" placeholder="cth: {{store_code}} atau kosongkan" oninput="upd('store_code',this.value)">`
    );

    h += pdiv();
    h += `<div class="pstitle">🎨 Tampilan</div>`;
    h += fg('Rata Teks Header', selAlign('header_align', el.header_align||'center'));
    h += fg('&nbsp;',
      `<label style="display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;cursor:pointer;margin-top:2px;">
        <input type="checkbox" ${el.show_divider?'checked':''} onchange="upd('show_divider',this.checked?1:0)">
        Garis pemisah setelah header
      </label>`
    );
  }

  /* ─ TEXT ─ */
  if (type==='text') {
    h += frow([
        fg('Rata Teks', selAlign('align', el.align)),
        fg('Ukuran',    selFontSize('font_size', el.font_size)),
        fg('&nbsp;',   `<label style="display:flex;align-items:center;gap:6px;margin-top:14px;font-size:12px;font-weight:600;cursor:pointer;"><input type="checkbox" ${el.bold?'checked':''} onchange="upd('bold',this.checked?1:0)"> Bold</label>`),
      ]);
    h += fg('Teks / Placeholder',
      `<input class="fc" type="text" value="${esc(el.value||'')}" oninput="upd('value',this.value)">
       <div style="font-size:9.5px;color:#aaa;margin-top:3px;line-height:1.6;">
         Variabel: <code>{{store_name}}</code> <code>{{store_address}}</code> <code>{{invoice_no}}</code> <code>{{date}}</code> <code>{{cashier}}</code> <code>{{footer}}</code>
       </div>`);
  }

  /* ─ INPUT ─ */
  if (type==='input') {
    h += fg('Nama Field (key unik)', `<input class="fc" type="text" value="${esc(el.input_name||'')}" placeholder="cth: parking_fee" oninput="upd('input_name',this.value)">`)
      + fg('Label Tampilan', `<input class="fc" type="text" value="${esc(el.input_label||'')}" placeholder="cth: Biaya Parkir" oninput="upd('input_label',this.value)">`)
      + pdiv()
      + frow([
          fg('Tipe Input', selItype('input_type', el.input_type||'text')),
          fg('Format',     selFormat('format', el.format||'plain')),
        ])
      + frow([
          fg('Prefix', `<input class="fc" type="text" value="${esc(el.prefix||'')}" placeholder="Rp" oninput="upd('prefix',this.value)">`),
          fg('Suffix', `<input class="fc" type="text" value="${esc(el.suffix||'')}" placeholder="kg"  oninput="upd('suffix',this.value)">`),
        ])
      + pdiv()
      + frow([
          fg('Rata',    selAlign('align', el.align)),
          fg('Ukuran',  selFontSize('font_size', el.font_size)),
        ])
      + fg('&nbsp;', `<label style="display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;cursor:pointer;"><input type="checkbox" ${el.bold?'checked':''} onchange="upd('bold',this.checked?1:0)"> Bold</label>`)
      + pdiv()
      + `<div class="skip-row">
           <input type="checkbox" ${el.skip_print?'checked':''} onchange="upd('skip_print',this.checked?1:0)">
           <div><div class="sl">Hanya di Form (tidak dicetak)</div>
           <div class="sd">Centang jika field ini hanya untuk input data. Tampilan di struk pakai elemen Teks dengan <code>{{${esc(el.input_name||'field')}}}</code>.</div>
           </div></div>`;
  }

  /* ─ ROW2 ─ */
  if (type==='row2') {
    ['col1','col2'].forEach((col, ci) => {
      const cd = el[col]||{};
      h += `<div class="col-block"><div class="col-block-title">${ci===0?'◀ Kolom Kiri':'▶ Kolom Kanan'}</div>`
        + frow([
            fg('Nama Field', `<input class="fc" type="text" value="${esc(cd.input_name||'')}" placeholder="key unik" oninput="updCol('${col}','input_name',this.value)">`),
            fg('Label',      `<input class="fc" type="text" value="${esc(cd.input_label||'')}" placeholder="tampilan" oninput="updCol('${col}','input_label',this.value)">`),
          ])
        + fg('Tipe Input', selItypeCol(col, 'input_type', cd.input_type||'text'))
        + frow([
            fg('Format',  selFormatCol(col, 'format', cd.format||'plain')),
            fg('Lebar %', `<input class="fc" type="number" value="${cd.width||50}" min="10" max="90" oninput="updCol('${col}','width',parseInt(this.value)||50)">`),
            fg('Rata',    `<select class="fc" onchange="updCol('${col}','align',this.value)"><option value="left" ${(cd.align||'left')==='left'?'selected':''}>Kiri</option><option value="right" ${(cd.align||'')==='right'?'selected':''}>Kanan</option><option value="center" ${(cd.align||'')==='center'?'selected':''}>Tengah</option></select>`),
          ])
        + `</div>`;
    });
  }

  /* ─ MULTIPLY ─ */
  if (type==='multiply') {
    const names = getInputFieldNames();
    const mkOpts = sel => (names.length
      ? names.map(n=>`<option value="${esc(n)}" ${n===sel?'selected':''}>${n}</option>`).join('')
      : '<option value="">-- belum ada input --</option>');
    h += fg('Label Tampilan', `<input class="fc" type="text" value="${esc(el.display_label||'Total')}" oninput="upd('display_label',this.value)">`)
      + pdiv()
      + frow([
          fg('Field A (pengali)', `<select class="fc" onchange="upd('input_a',this.value)"><option value="">-- pilih --</option>${mkOpts(el.input_a||'')}</select>`),
          fg('Field B (pengali)', `<select class="fc" onchange="upd('input_b',this.value)"><option value="">-- pilih --</option>${mkOpts(el.input_b||'')}</select>`),
        ])
      + frow([
          fg('Format', selFormat('format', el.format||'rupiah')),
          fg('Prefix', `<input class="fc" type="text" value="${esc(el.prefix||'')}" placeholder="Total: " oninput="upd('prefix',this.value)">`),
        ])
      + frow([
          fg('Rata',   selAlign('align', el.align)),
          fg('Ukuran', selFontSize('font_size', el.font_size)),
          fg('&nbsp;', `<label style="display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;cursor:pointer;margin-top:14px;"><input type="checkbox" ${el.bold?'checked':''} onchange="upd('bold',this.checked?1:0)"> Bold</label>`),
        ]);
  }

  h += `<div class="pdivider"></div>
    <div class="el-actions">
      <button class="btn btn-light btn-sm" onclick="moveUp()">▲ Atas</button>
      <button class="btn btn-light btn-sm" onclick="moveDown()">▼ Bawah</button>
      <button class="btn btn-danger btn-sm" onclick="deleteSelected()">🗑 Hapus</button>
    </div>`;
  h += '</div>';

  document.getElementById('propsBody').innerHTML = h;
}

function fg(lbl, ctrl) { return `<div class="fg"><label class="fl">${lbl}</label>${ctrl}</div>`; }
function frow(items)    { return `<div class="frow">${items.join('')}</div>`; }
function pdiv()         { return '<div class="pdivider"></div>'; }

function selAlign(key, cur) {
  const o = (v,l) => `<option value="${v}" ${(cur||'left')===v?'selected':''}>${l}</option>`;
  return `<select class="fc" onchange="upd('${key}',this.value)">${o('left','Kiri')}${o('center','Tengah')}${o('right','Kanan')}</select>`;
}
function selFontSize(key, cur) {
  const v = parseInt(cur)||1;
  return `<select class="fc" onchange="upd('${key}',parseInt(this.value))">
    <option value="1" ${v===1?'selected':''}>Normal</option>
    <option value="2" ${v===2?'selected':''}>Besar</option>
    <option value="3" ${v===3?'selected':''}>Sangat Besar</option></select>`;
}
function selItype(key, cur) {
  return `<select class="fc" onchange="upd('${key}',this.value)">`
    + INPUT_TYPES.map(t=>`<option value="${t.val}" ${cur===t.val?'selected':''}>${t.label}</option>`).join('')
    + `</select>`;
}
function selItypeCol(col, key, cur) {
  return `<select class="fc" onchange="updCol('${col}','${key}',this.value)">`
    + INPUT_TYPES.map(t=>`<option value="${t.val}" ${cur===t.val?'selected':''}>${t.label}</option>`).join('')
    + `</select>`;
}
function selFormat(key, cur) {
  return `<select class="fc" onchange="upd('${key}',this.value)">`
    + FORMAT_OPTS.map(f=>`<option value="${f.val}" ${cur===f.val?'selected':''}>${f.label}</option>`).join('')
    + `</select>`;
}
function selFormatCol(col, key, cur) {
  return `<select class="fc" onchange="updCol('${col}','${key}',this.value)">`
    + FORMAT_OPTS.map(f=>`<option value="${f.val}" ${cur===f.val?'selected':''}>${f.label}</option>`).join('')
    + `</select>`;
}

/* ═══════════════════════════════════════════════════════
   UPDATE HELPERS
═══════════════════════════════════════════════════════ */
function getInputFieldNames() {
  const names = [];
  elements.forEach(el => {
    if (el.element_type==='input' && el.input_name) names.push(el.input_name);
    if (el.element_type==='row2') {
      if (el.col1?.input_name) names.push(el.col1.input_name);
      if (el.col2?.input_name) names.push(el.col2.input_name);
    }
  });
  return [...new Set(names)];
}

function upd(key, val) {
  const el = elements.find(e=>e.id===selectedId); if (!el) return;
  el[key] = val;
  renderCanvas(true);
  document.querySelectorAll('.canvas-el').forEach(d=>d.classList.remove('selected'));
  const dom = document.querySelector('.canvas-el[data-id="'+selectedId+'"]');
  if (dom) dom.classList.add('selected');
}

function updCol(col, key, val) {
  const el = elements.find(e=>e.id===selectedId); if (!el) return;
  if (!el[col]) el[col]={};
  el[col][key] = val;
  renderCanvas(true);
  document.querySelectorAll('.canvas-el').forEach(d=>d.classList.remove('selected'));
  const dom = document.querySelector('.canvas-el[data-id="'+selectedId+'"]');
  if (dom) dom.classList.add('selected');
}

/* ═══════════════════════════════════════════════════════
   ADD ELEMENT
═══════════════════════════════════════════════════════ */
function addEl(type, value, label) {
  syncOrder();
  const maxY = elements.length ? Math.max(...elements.map(e=>e.y||0))+1 : 0;
  const e = { id: ++idCounter, element_type: type, y: maxY };

  switch (type) {
    case 'store_header':
      Object.assign(e, {
        label:         'store_header',
        logo_text:     '{{store_name}}',
        company_name:  '',
        address_line1: '{{store_address}}',
        address_line2: '',
        npwp:          '',
        phone:         '',
        store_code:    '',
        header_align:  'center',
        show_divider:  1,
      }); break;
    case 'text':
      Object.assign(e, {
        value: value||'{{store_name}}', label: label||'custom',
        align:'left', font_size: label==='store_name'?2:1, bold: label==='store_name'?1:0, date_format:'',
      }); break;
    case 'date_field':
      Object.assign(e, {
        value:'Tgl: {{date}}', label:'date',
        align:'left', font_size:1, bold:0, date_format:'d/m/Y H:i',
        element_type:'text',
      }); break;
    case 'line':   Object.assign(e, {label:'separator', value:''}); break;
    case 'spacer': Object.assign(e, {label:'spacer_'+idCounter}); break;
    case 'input':
      Object.assign(e, {
        input_name:'field_'+idCounter, input_label:'Field '+idCounter,
        input_type:'text', align:'left', font_size:1, bold:0,
        format:'plain', prefix:'', suffix:'', label:'input_'+idCounter, skip_print:0,
      }); break;
    case 'row2':
      Object.assign(e, {
        label:'row2_'+idCounter,
        col1:{input_name:'field_a'+idCounter, input_label:'Kol Kiri',  input_type:'text',   format:'plain',  width:50, align:'left'},
        col2:{input_name:'field_b'+idCounter, input_label:'Kol Kanan', input_type:'number', format:'rupiah', width:50, align:'right'},
      }); break;
    case 'multiply':
      Object.assign(e, {
        display_label:'Total', input_a:'', input_b:'',
        format:'rupiah', prefix:'', align:'left', font_size:1, bold:1,
        label:'multiply_'+idCounter,
      }); break;
  }
  elements.push(e);
  renderCanvas();
  selectEl(e.id);
}

/* ═══════════════════════════════════════════════════════
   DELETE / MOVE
═══════════════════════════════════════════════════════ */
function deleteSelected() {
  if (!selectedId) return;
  if (!confirm('Hapus elemen ini?')) return;
  elements = elements.filter(e=>e.id!==selectedId);
  selectedId = null;
  showPropsEmpty();
  renderCanvas();
}

function moveUp() {
  syncOrder();
  const s=[...elements].sort((a,b)=>a.y-b.y), i=s.findIndex(e=>e.id===selectedId);
  if (i<=0) return;
  [s[i].y,s[i-1].y]=[s[i-1].y,s[i].y];
  elements=s; renderCanvas(true); selectEl(selectedId);
}
function moveDown() {
  syncOrder();
  const s=[...elements].sort((a,b)=>a.y-b.y), i=s.findIndex(e=>e.id===selectedId);
  if (i<0||i>=s.length-1) return;
  [s[i].y,s[i+1].y]=[s[i+1].y,s[i].y];
  elements=s; renderCanvas(true); selectEl(selectedId);
}

/* ═══════════════════════════════════════════════════════
   SAVE
═══════════════════════════════════════════════════════ */
async function saveTemplate() {
  syncOrder();
  const name = document.getElementById('tplName').value.trim();
  if (!name)            { alert('Nama template kosong!');       return; }
  if (!elements.length) { alert('Tambahkan minimal 1 elemen!'); return; }

  showSave('info','⏳ Menyimpan...');
  try {
    const res = await fetch(APP_URL+'template/save', {
      method:'POST',
      headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest'},
      body: JSON.stringify({
        template_id: TPL_ID,
        store_id:    parseInt(document.getElementById('tplStore').value)||1,
        paper_width: paperMm,
        name,
        layout_json: elements,
      }),
    });

    const ct = res.headers.get('content-type')||'';
    if (!ct.includes('application/json')) {
      const txt = await res.text();
      console.error('Non-JSON response:', txt.substring(0,400));
      showSave('error','❌ Server tidak mengembalikan JSON (HTTP '+res.status+'). Cek endpoint template/save.');
      return;
    }
    const json = await res.json();
    if (json.status==='ok') {
      showSave('ok','✅ '+json.message);
      if (!TPL_ID && json.id) setTimeout(()=>{ window.location.href=APP_URL+'template/builder/'+json.id; },900);
    } else {
      showSave('error','❌ '+(json.message||'Terjadi kesalahan.'));
    }
  } catch(err) {
    showSave('error','❌ Gagal menghubungi server: '+err.message);
    console.error(err);
  }
}

function showSave(type, msg) {
  const el = document.getElementById('saveStatus');
  el.className = 'alert-'+type; el.style.display='block'; el.textContent=msg;
  el.scrollIntoView({behavior:'smooth',block:'nearest'});
  if (type!=='info') setTimeout(()=>{ el.style.display='none'; },5000);
}

/* ═══════════════════════════════════════════════════════
   PRESETS
═══════════════════════════════════════════════════════ */
const PRESETS = {

  /* ── MINIMARKET (Indomaret / Alfamart style) ── */
  minimarket: {
    name: 'Struk Minimarket',
    elements: [
      /* ── HEADER BLOK ── */
      {id:201,element_type:'store_header',label:'store_header',
        logo_text:'{{store_name}}',
        company_name:'',
        address_line1:'{{store_address}}',
        address_line2:'',
        npwp:'',phone:'',store_code:'',
        header_align:'center',show_divider:1,skip_print:0,y:0
      },
      /* ── TANGGAL + NO TRX ── */
      {id:202,element_type:'row2',label:'row2_header',
        col1:{input_name:'trx_date_str', input_label:'Tanggal', input_type:'text',format:'plain',width:55,align:'left'},
        col2:{input_name:'trx_no',       input_label:'No. Trx', input_type:'text',format:'plain',width:45,align:'right'},
        skip_print:0,y:1
      },
      /* ── JUDUL STRUK ── */
      {id:203,element_type:'text',value:'SLIP PEMBAYARAN',label:'title_slip',
        align:'center',font_size:2,bold:1,skip_print:0,y:2},
      {id:204,element_type:'text',value:'Kode Pembayaran: {{kode_pembayaran}}',label:'kode_bayar',
        align:'left',font_size:1,bold:0,skip_print:0,y:3},
      {id:205,element_type:'line',label:'sep1',skip_print:0,y:4},

      /* ── INFO PELANGGAN ── */
      {id:206,element_type:'text',value:'Nama         : {{nama_pelanggan}}',label:'nama_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:5},
      {id:207,element_type:'text',value:'Expired      : {{expired}}',label:'expired_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:6},
      {id:208,element_type:'text',value:'Toko         : {{toko_tujuan}}',label:'toko_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:7},
      {id:209,element_type:'text',value:'Deskripsi    : {{deskripsi}}',label:'deskripsi_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:8},
      {id:210,element_type:'text',value:'No.Referensi : {{no_referensi}}',label:'noref_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:9},
      {id:211,element_type:'line',label:'sep2',skip_print:0,y:10},

      /* ── DETAIL TRANSAKSI ── */
      {id:212,element_type:'text',value:'No. Referensi : {{no_referensi}}',label:'noref_d2',
        align:'left',font_size:1,bold:0,skip_print:0,y:11},
      {id:213,element_type:'text',value:'Rp. Bayar     : {{rp_bayar}}',label:'rpbayar_d',
        align:'left',font_size:1,bold:1,skip_print:0,y:12},
      {id:214,element_type:'text',value:'Pembelian dari {{toko_tujuan}}\ntelah berhasil',label:'status_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:13},
      {id:215,element_type:'line',label:'sep3',skip_print:0,y:14},

      /* ── DETAIL PRODUK ── */
      {id:216,element_type:'row2',label:'row2_produk',
        col1:{input_name:'nama_produk',  input_label:'Produk', input_type:'text',   format:'plain',      width:60,align:'left'},
        col2:{input_name:'harga_produk', input_label:'Harga',  input_type:'number', format:'number_sep', width:40,align:'right'},
        skip_print:0,y:15},
      {id:217,element_type:'text',value:'BIAYA ADM.    :   {{biaya_adm}}',label:'admfee_d',
        align:'right',font_size:1,bold:0,skip_print:0,y:16},
      {id:218,element_type:'line',label:'sep4',skip_print:0,y:17},

      /* ── TOTAL ── */
      {id:219,element_type:'text',value:'HARGA JUAL : {{harga_jual}}',label:'hjual_d',
        align:'right',font_size:1,bold:1,skip_print:0,y:18},
      {id:220,element_type:'line',label:'sep5',skip_print:0,y:19},
      {id:221,element_type:'text',value:'TOTAL    : {{total}}',label:'total_d',
        align:'right',font_size:1,bold:1,skip_print:0,y:20},
      {id:222,element_type:'text',value:'TUNAI    : {{tunai}}',label:'tunai_d',
        align:'right',font_size:1,bold:0,skip_print:0,y:21},
      {id:223,element_type:'text',value:'KEMBALI  : {{kembali}}',label:'kembali_d',
        align:'right',font_size:1,bold:0,skip_print:0,y:22},
      /* ── PAJAK ── */
      {id:224,element_type:'text',value:'PPN : DPP= {{ppn_dpp}}  PPN= {{ppn_nilai}}',label:'ppn_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:23},
      {id:225,element_type:'text',value:'NON PPN : DPP= {{non_ppn_dpp}}',label:'nonppn_d',
        align:'left',font_size:1,bold:0,skip_print:0,y:24},
      {id:226,element_type:'line',label:'sep6',skip_print:0,y:25},
      /* ── FOOTER ── */
      {id:227,element_type:'text',value:'LAYANAN KONSUMEN SMS {{cs_sms}}',label:'footer1_d',
        align:'center',font_size:1,bold:0,skip_print:0,y:26},
      {id:228,element_type:'text',value:'CALL {{cs_call}} - {{cs_email}}',label:'footer2_d',
        align:'center',font_size:1,bold:0,skip_print:0,y:27},
      {id:229,element_type:'spacer',label:'sp_end',skip_print:0,y:28},

      /* ══ INPUT FIELDS (form only, skip_print=1) ══ */
      {id:230,element_type:'input',input_name:'kode_pembayaran',input_label:'Kode Pembayaran',    input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_kode',  skip_print:1,y:29},
      {id:231,element_type:'input',input_name:'nama_pelanggan', input_label:'Nama Pelanggan',      input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_nama',  skip_print:1,y:30},
      {id:232,element_type:'input',input_name:'expired',        input_label:'Expired (tgl/waktu)', input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_exp',   skip_print:1,y:31},
      {id:233,element_type:'input',input_name:'toko_tujuan',    input_label:'Toko Tujuan',         input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_toko',  skip_print:1,y:32},
      {id:234,element_type:'input',input_name:'deskripsi',      input_label:'Deskripsi',           input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_desk',  skip_print:1,y:33},
      {id:235,element_type:'input',input_name:'no_referensi',   input_label:'No. Referensi',       input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_ref',   skip_print:1,y:34},
      {id:236,element_type:'input',input_name:'trx_date_str',   input_label:'Tanggal (teks)',       input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_tgl',   skip_print:1,y:35},
      {id:237,element_type:'input',input_name:'trx_no',         input_label:'No. Transaksi',        input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_trxno', skip_print:1,y:36},
      {id:238,element_type:'input',input_name:'nama_produk',    input_label:'Nama Produk',          input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_prod',  skip_print:1,y:37},
      {id:239,element_type:'input',input_name:'harga_produk',   input_label:'Harga Produk (Rp)',    input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_hrg',   skip_print:1,y:38},
      {id:240,element_type:'input',input_name:'biaya_adm',      input_label:'Biaya Admin (Rp)',     input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_adm',   skip_print:1,y:39},
      {id:241,element_type:'input',input_name:'harga_jual',     input_label:'Harga Jual (Rp)',      input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_hjual', skip_print:1,y:40},
      {id:242,element_type:'input',input_name:'total',          input_label:'Total (Rp)',            input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_total', skip_print:1,y:41},
      {id:243,element_type:'input',input_name:'tunai',          input_label:'Tunai / Bayar (Rp)',    input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_tunai', skip_print:1,y:42},
      {id:244,element_type:'input',input_name:'kembali',        input_label:'Kembalian (Rp)',        input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_kmbl',  skip_print:1,y:43},
      {id:245,element_type:'input',input_name:'ppn_dpp',        input_label:'PPN DPP',               input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_dpp',   skip_print:1,y:44},
      {id:246,element_type:'input',input_name:'ppn_nilai',      input_label:'PPN Nilai',             input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_ppn',   skip_print:1,y:45},
      {id:247,element_type:'input',input_name:'non_ppn_dpp',    input_label:'Non PPN DPP',           input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_nppn',  skip_print:1,y:46},
      {id:248,element_type:'input',input_name:'rp_bayar',       input_label:'Rp. Bayar (Rp)',        input_type:'number',format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_rpbyr', skip_print:1,y:47},
      {id:249,element_type:'input',input_name:'cs_sms',         input_label:'No. SMS CS',            input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_cssms', skip_print:1,y:48},
      {id:250,element_type:'input',input_name:'cs_call',        input_label:'No. Call CS',           input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_cscll', skip_print:1,y:49},
      {id:251,element_type:'input',input_name:'cs_email',       input_label:'Email CS',              input_type:'text',  format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_cseml', skip_print:1,y:50},
    ]
  },

  /* ── PARKIR ── */
  parkir: {
    name:'Template Parkir',
    elements:[
      {id:101,element_type:'store_header',label:'store_header',
        logo_text:'{{store_name}}',company_name:'',
        address_line1:'{{store_address}}',address_line2:'',
        npwp:'',phone:'',store_code:'',
        header_align:'center',show_divider:1,skip_print:0,y:0},
      {id:102,element_type:'spacer',label:'sp1',skip_print:0,y:1},
      {id:103,element_type:'text',value:'{{vehicle_type}}',label:'vt_d',align:'left',font_size:1,bold:1,skip_print:0,y:2},
      {id:104,element_type:'text',value:'{{slot_info}}',label:'sl_d',align:'left',font_size:1,bold:0,skip_print:0,y:3},
      {id:105,element_type:'text',value:'In  : {{in_datetime}} - {{in_gate}}',label:'in_d',align:'left',font_size:1,bold:0,skip_print:0,y:4},
      {id:106,element_type:'text',value:'    : {{out_datetime}} - {{out_gate}}',label:'out_d',align:'left',font_size:1,bold:0,skip_print:0,y:5},
      {id:107,element_type:'text',value:'Duration: {{duration}}',label:'dur_d',align:'left',font_size:1,bold:0,skip_print:0,y:6},
      {id:108,element_type:'spacer',label:'sp2',skip_print:0,y:7},
      {id:109,element_type:'text',value:'Biaya Parkir: Rp   {{parking_fee}}',label:'fee_d',align:'left',font_size:1,bold:1,skip_print:0,y:8},
      {id:110,element_type:'spacer',label:'sp3',skip_print:0,y:9},
      {id:111,element_type:'text',value:'Flazz Balance: Rp {{flazz_balance}}',label:'bal_d',align:'left',font_size:1,bold:0,skip_print:0,y:10},
      {id:112,element_type:'text',value:'Flazz CardNo : {{flazz_cardno}}',label:'card_d',align:'left',font_size:1,bold:0,skip_print:0,y:11},
      {id:113,element_type:'spacer',label:'sp4',skip_print:0,y:12},
      {id:114,element_type:'text',value:'TERIMA KASIH',label:'f1',align:'center',font_size:1,bold:0,skip_print:0,y:13},
      {id:115,element_type:'text',value:'Biaya Parkir',label:'f2',align:'center',font_size:1,bold:0,skip_print:0,y:14},
      {id:116,element_type:'text',value:'sudah termasuk PBJT Parkir',label:'f3',align:'center',font_size:1,bold:0,skip_print:0,y:15},
      {id:117,element_type:'input',input_name:'vehicle_type', input_label:'Jenis Kendaraan',       input_type:'text',   format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_vt',  skip_print:1,y:16},
      {id:118,element_type:'input',input_name:'slot_info',    input_label:'No. Slot / Shift',      input_type:'text',   format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_sl',  skip_print:1,y:17},
      {id:119,element_type:'input',input_name:'in_datetime',  input_label:'Waktu Masuk',           input_type:'datetime',format:'plain',     align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_ind', skip_print:1,y:18},
      {id:120,element_type:'input',input_name:'in_gate',      input_label:'Gate Masuk (cth: M06)', input_type:'text',   format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_ing', skip_print:1,y:19},
      {id:121,element_type:'input',input_name:'out_datetime', input_label:'Waktu Keluar',          input_type:'datetime',format:'plain',     align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_outd',skip_print:1,y:20},
      {id:122,element_type:'input',input_name:'out_gate',     input_label:'Gate Keluar (cth: F05)',input_type:'text',   format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_outg',skip_print:1,y:21},
      {id:123,element_type:'input',input_name:'duration',     input_label:'Durasi (cth: 1h 24m)',  input_type:'text',   format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_dur', skip_print:1,y:22},
      {id:124,element_type:'input',input_name:'parking_fee',  input_label:'Biaya Parkir (Rp)',     input_type:'number', format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_fee', skip_print:1,y:23},
      {id:125,element_type:'input',input_name:'flazz_balance',input_label:'Saldo Flazz (Rp)',      input_type:'number', format:'number_sep', align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_bal', skip_print:1,y:24},
      {id:126,element_type:'input',input_name:'flazz_cardno', input_label:'No. Kartu Flazz',       input_type:'text',   format:'plain',      align:'left',font_size:1,bold:0,prefix:'',suffix:'',label:'inp_card',skip_print:1,y:25},
    ]
  }
};

function loadPreset(key) {
  const p = PRESETS[key]; if (!p) return;
  if (elements.length && !confirm('Load preset "'+p.name+'"?\nCanvas saat ini akan diganti. Lanjutkan?')) return;
  const nm = document.getElementById('tplName');
  if (!nm.value || nm.value==='Template Baru') nm.value = p.name;
  elements   = JSON.parse(JSON.stringify(p.elements));
  idCounter  = Math.max(...elements.map(e=>e.id))+1;
  selectedId = null;
  showPropsEmpty();
  renderCanvas();
  showSave('ok','✅ Preset "'+p.name+'" berhasil dimuat! Sesuaikan lalu klik Simpan.');
}

/* ═══════════════════════════════════════════════════════
   INIT
═══════════════════════════════════════════════════════ */
setPaper(paperMm);
renderCanvas();
</script>
