Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

#[[<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PJP Agenda Rule Config</title>
<style>
  *{box-sizing:border-box}
  body{margin:0;background:#f4f6fa;color:#1f2937;
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
  .wrap{max-width:1080px;margin:0 auto;padding:24px 20px 60px}
  h1{font-size:22px;margin:0 0 4px}
  .sub{color:#6b7280;font-size:13px;margin-bottom:22px}
  .card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:18px 20px;margin-bottom:22px}
  .card h2{font-size:15px;margin:0 0 12px}
  table{width:100%;border-collapse:collapse;font-size:13.5px}
  th{color:#6b7280;text-align:left;font-weight:600;padding:6px 10px;border-bottom:1px solid #e5e7eb}
  td{padding:8px 10px;border-bottom:1px solid #f1f5f9;vertical-align:middle}
  input[type=number],input[type=text]{width:90px;padding:5px 7px;border:1px solid #d1d5db;border-radius:6px;font-size:13px}
  input.wide{width:130px}
  button{background:#4f46e5;color:#fff;border:0;border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer}
  button.ghost{background:#fff;color:#4f46e5;border:1px solid #c7d2fe}
  button:disabled{opacity:.5;cursor:default}
  .muted{color:#9ca3af;font-size:12px}
  .msg{font-size:13px;margin-left:10px}
  .msg.ok{color:#059669}
  .msg.err{color:#dc2626}
  select{padding:5px 7px;border:1px solid #d1d5db;border-radius:6px;font-size:13px}
  .note{background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:10px 14px;font-size:12.5px;color:#92400e;margin-bottom:18px}
</style>
</head>
<body>
<div class="wrap">
  <h1>PJP Agenda Rule Config</h1>
  <div class="sub">Auto-agenda thresholds. Defaults apply to every partner; a per-partner override row wins key-by-key.</div>
  <div class="note">
    LOW_SALES <b>windowDays/baselineDays</b> apply globally only (the nightly sync batches one query for all partners);
    per-partner overrides of those two keys are ignored. flagPct / clearPct do resolve per partner.
    REVIVAL has no config here &mdash; its threshold belongs to the 23:30 activation-type cron.
  </div>

  <div class="card">
    <h2>Global defaults</h2>
    <table id="defaultsTable"><thead>
      <tr><th style="width:170px">Agenda</th><th>Params</th><th style="width:210px">Last update</th><th style="width:110px"></th></tr>
    </thead><tbody></tbody></table>
  </div>

  <div class="card">
    <h2>Per-partner overrides</h2>
    <table id="overridesTable"><thead>
      <tr><th style="width:120px">Fofo ID</th><th style="width:170px">Agenda</th><th>Params</th><th style="width:210px">Last update</th><th style="width:170px"></th></tr>
    </thead><tbody></tbody></table>
    <div style="margin-top:14px">
      <b style="font-size:13.5px">Add override:</b>
      fofoId <input type="number" id="newFofoId" min="1">
      <select id="newType"></select>
      <span id="newParams"></span>
      <button id="addOverrideBtn">Save override</button>
      <span class="msg" id="addMsg"></span>
    </div>
  </div>
</div>

<script>
var BASE = location.pathname.replace(/\/$/,'');
var state = {rows: [], types: []};

function paramInputs(prefix, paramsJson) {
  var p = {};
  try { p = JSON.parse(paramsJson || '{}'); } catch (e) {}
  var h = '';
  Object.keys(p).forEach(function (k) {
    h += '<label style="margin-right:12px">' + k +
         ' <input type="number" step="any" min="0.01" data-key="' + k + '" class="' + prefix + '-param" value="' + p[k] + '"></label>';
  });
  return h;
}

function collectParams($scope) {
  var params = {};
  $scope.querySelectorAll('input[data-key]').forEach(function (inp) {
    params[inp.getAttribute('data-key')] = parseFloat(inp.value);
  });
  return params;
}

function save(agendaType, fofoId, params, msgEl, after) {
  msgEl.textContent = 'Saving…'; msgEl.className = 'msg';
  fetch(BASE + '/save', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({agendaType: agendaType, fofoId: fofoId, params: params})
  }).then(function (r) { return r.json().then(function (j) { return {ok: r.ok, j: j}; }); })
    .then(function (res) {
      if (res.ok) { msgEl.textContent = 'Saved'; msgEl.className = 'msg ok'; if (after) after(); }
      else { msgEl.textContent = res.j.error || 'Failed'; msgEl.className = 'msg err'; }
    }).catch(function () { msgEl.textContent = 'Failed'; msgEl.className = 'msg err'; });
}

function render() {
  var dt = document.querySelector('#defaultsTable tbody');
  var ot = document.querySelector('#overridesTable tbody');
  dt.innerHTML = ''; ot.innerHTML = '';
  state.rows.forEach(function (row) {
    var tr = document.createElement('tr');
    var upd = (row.updatedBy || '') + (row.updatedOn ? ' · ' + row.updatedOn.replace('T', ' ').slice(0, 16) : '');
    if (row.fofoId === 0) {
      tr.innerHTML = '<td><b>' + row.agendaType + '</b></td>' +
        '<td>' + paramInputs('d' + row.id, row.params) + '</td>' +
        '<td class="muted">' + upd + '</td>' +
        '<td><button>Save</button><span class="msg"></span></td>';
      tr.querySelector('button').onclick = function () {
        save(row.agendaType, 0, collectParams(tr), tr.querySelector('.msg'));
      };
      dt.appendChild(tr);
    } else {
      tr.innerHTML = '<td><b>' + row.fofoId + '</b></td><td>' + row.agendaType + '</td>' +
        '<td>' + paramInputs('o' + row.id, row.params) + '</td>' +
        '<td class="muted">' + upd + '</td>' +
        '<td><button>Save</button> <button class="ghost">Remove</button><span class="msg"></span></td>';
      var btns = tr.querySelectorAll('button');
      btns[0].onclick = function () {
        save(row.agendaType, row.fofoId, collectParams(tr), tr.querySelector('.msg'));
      };
      btns[1].onclick = function () {
        if (!confirm('Remove override for fofo ' + row.fofoId + ' / ' + row.agendaType + '?')) return;
        save(row.agendaType, row.fofoId, {}, tr.querySelector('.msg'), load);
      };
      ot.appendChild(tr);
    }
  });
  if (!ot.children.length) {
    ot.innerHTML = '<tr><td colspan="5" class="muted">No per-partner overrides yet.</td></tr>';
  }

  var sel = document.getElementById('newType');
  sel.innerHTML = '';
  state.types.forEach(function (t) {
    var o = document.createElement('option'); o.value = t; o.textContent = t; sel.appendChild(o);
  });
  sel.onchange = renderNewParams;
  renderNewParams();
}

function renderNewParams() {
  var t = document.getElementById('newType').value;
  var def = state.rows.filter(function (r) { return r.fofoId === 0 && r.agendaType === t; })[0];
  document.getElementById('newParams').innerHTML = def ? paramInputs('new', def.params) : '';
}

document.getElementById('addOverrideBtn').onclick = function () {
  var fofoId = parseInt(document.getElementById('newFofoId').value, 10);
  var msgEl = document.getElementById('addMsg');
  if (!fofoId || fofoId < 1) { msgEl.textContent = 'Enter a fofoId'; msgEl.className = 'msg err'; return; }
  save(document.getElementById('newType').value, fofoId,
       collectParams(document.getElementById('newParams')), msgEl, load);
};

function load() {
  fetch(BASE + '/data', {headers: {'Accept': 'application/json'}})
    .then(function (r) { return r.json(); })
    .then(function (j) { state.rows = j.rows || []; state.types = j.configurableTypes || []; render(); })
    .catch(function () {
      document.querySelector('#defaultsTable tbody').innerHTML =
        '<tr><td colspan="4" class="msg err">Failed to load config</td></tr>';
    });
}
load();
</script>
</body>
</html>]]#