Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37179 amit 1
#[[<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8">
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
<title>PJP Agenda Rule Config</title>
7
<style>
8
  *{box-sizing:border-box}
9
  body{margin:0;background:#f4f6fa;color:#1f2937;
10
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
11
  .wrap{max-width:1080px;margin:0 auto;padding:24px 20px 60px}
12
  h1{font-size:22px;margin:0 0 4px}
13
  .sub{color:#6b7280;font-size:13px;margin-bottom:22px}
14
  .card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:18px 20px;margin-bottom:22px}
15
  .card h2{font-size:15px;margin:0 0 12px}
16
  table{width:100%;border-collapse:collapse;font-size:13.5px}
17
  th{color:#6b7280;text-align:left;font-weight:600;padding:6px 10px;border-bottom:1px solid #e5e7eb}
18
  td{padding:8px 10px;border-bottom:1px solid #f1f5f9;vertical-align:middle}
19
  input[type=number],input[type=text]{width:90px;padding:5px 7px;border:1px solid #d1d5db;border-radius:6px;font-size:13px}
20
  input.wide{width:130px}
21
  button{background:#4f46e5;color:#fff;border:0;border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer}
22
  button.ghost{background:#fff;color:#4f46e5;border:1px solid #c7d2fe}
23
  button:disabled{opacity:.5;cursor:default}
24
  .muted{color:#9ca3af;font-size:12px}
25
  .msg{font-size:13px;margin-left:10px}
26
  .msg.ok{color:#059669}
27
  .msg.err{color:#dc2626}
28
  select{padding:5px 7px;border:1px solid #d1d5db;border-radius:6px;font-size:13px}
29
  .note{background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:10px 14px;font-size:12.5px;color:#92400e;margin-bottom:18px}
30
</style>
31
</head>
32
<body>
33
<div class="wrap">
34
  <h1>PJP Agenda Rule Config</h1>
35
  <div class="sub">Auto-agenda thresholds. Defaults apply to every partner; a per-partner override row wins key-by-key.</div>
36
  <div class="note">
37
    LOW_SALES <b>windowDays/baselineDays</b> apply globally only (the nightly sync batches one query for all partners);
38
    per-partner overrides of those two keys are ignored. flagPct / clearPct do resolve per partner.
39
    REVIVAL has no config here &mdash; its threshold belongs to the 23:30 activation-type cron.
40
  </div>
41
 
42
  <div class="card">
43
    <h2>Global defaults</h2>
44
    <table id="defaultsTable"><thead>
45
      <tr><th style="width:170px">Agenda</th><th>Params</th><th style="width:210px">Last update</th><th style="width:110px"></th></tr>
46
    </thead><tbody></tbody></table>
47
  </div>
48
 
49
  <div class="card">
50
    <h2>Per-partner overrides</h2>
51
    <table id="overridesTable"><thead>
52
      <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>
53
    </thead><tbody></tbody></table>
54
    <div style="margin-top:14px">
55
      <b style="font-size:13.5px">Add override:</b>
56
      fofoId <input type="number" id="newFofoId" min="1">
57
      <select id="newType"></select>
58
      <span id="newParams"></span>
59
      <button id="addOverrideBtn">Save override</button>
60
      <span class="msg" id="addMsg"></span>
61
    </div>
62
  </div>
63
</div>
64
 
65
<script>
66
var BASE = location.pathname.replace(/\/$/,'');
67
var state = {rows: [], types: []};
68
 
69
function paramInputs(prefix, paramsJson) {
70
  var p = {};
71
  try { p = JSON.parse(paramsJson || '{}'); } catch (e) {}
72
  var h = '';
73
  Object.keys(p).forEach(function (k) {
74
    h += '<label style="margin-right:12px">' + k +
75
         ' <input type="number" step="any" min="0.01" data-key="' + k + '" class="' + prefix + '-param" value="' + p[k] + '"></label>';
76
  });
77
  return h;
78
}
79
 
80
function collectParams($scope) {
81
  var params = {};
82
  $scope.querySelectorAll('input[data-key]').forEach(function (inp) {
83
    params[inp.getAttribute('data-key')] = parseFloat(inp.value);
84
  });
85
  return params;
86
}
87
 
88
function save(agendaType, fofoId, params, msgEl, after) {
89
  msgEl.textContent = 'Saving…'; msgEl.className = 'msg';
90
  fetch(BASE + '/save', {
91
    method: 'POST',
92
    headers: {'Content-Type': 'application/json'},
93
    body: JSON.stringify({agendaType: agendaType, fofoId: fofoId, params: params})
94
  }).then(function (r) { return r.json().then(function (j) { return {ok: r.ok, j: j}; }); })
95
    .then(function (res) {
96
      if (res.ok) { msgEl.textContent = 'Saved'; msgEl.className = 'msg ok'; if (after) after(); }
97
      else { msgEl.textContent = res.j.error || 'Failed'; msgEl.className = 'msg err'; }
98
    }).catch(function () { msgEl.textContent = 'Failed'; msgEl.className = 'msg err'; });
99
}
100
 
101
function render() {
102
  var dt = document.querySelector('#defaultsTable tbody');
103
  var ot = document.querySelector('#overridesTable tbody');
104
  dt.innerHTML = ''; ot.innerHTML = '';
105
  state.rows.forEach(function (row) {
106
    var tr = document.createElement('tr');
107
    var upd = (row.updatedBy || '') + (row.updatedOn ? ' · ' + row.updatedOn.replace('T', ' ').slice(0, 16) : '');
108
    if (row.fofoId === 0) {
109
      tr.innerHTML = '<td><b>' + row.agendaType + '</b></td>' +
110
        '<td>' + paramInputs('d' + row.id, row.params) + '</td>' +
111
        '<td class="muted">' + upd + '</td>' +
112
        '<td><button>Save</button><span class="msg"></span></td>';
113
      tr.querySelector('button').onclick = function () {
114
        save(row.agendaType, 0, collectParams(tr), tr.querySelector('.msg'));
115
      };
116
      dt.appendChild(tr);
117
    } else {
118
      tr.innerHTML = '<td><b>' + row.fofoId + '</b></td><td>' + row.agendaType + '</td>' +
119
        '<td>' + paramInputs('o' + row.id, row.params) + '</td>' +
120
        '<td class="muted">' + upd + '</td>' +
121
        '<td><button>Save</button> <button class="ghost">Remove</button><span class="msg"></span></td>';
122
      var btns = tr.querySelectorAll('button');
123
      btns[0].onclick = function () {
124
        save(row.agendaType, row.fofoId, collectParams(tr), tr.querySelector('.msg'));
125
      };
126
      btns[1].onclick = function () {
127
        if (!confirm('Remove override for fofo ' + row.fofoId + ' / ' + row.agendaType + '?')) return;
128
        save(row.agendaType, row.fofoId, {}, tr.querySelector('.msg'), load);
129
      };
130
      ot.appendChild(tr);
131
    }
132
  });
133
  if (!ot.children.length) {
134
    ot.innerHTML = '<tr><td colspan="5" class="muted">No per-partner overrides yet.</td></tr>';
135
  }
136
 
137
  var sel = document.getElementById('newType');
138
  sel.innerHTML = '';
139
  state.types.forEach(function (t) {
140
    var o = document.createElement('option'); o.value = t; o.textContent = t; sel.appendChild(o);
141
  });
142
  sel.onchange = renderNewParams;
143
  renderNewParams();
144
}
145
 
146
function renderNewParams() {
147
  var t = document.getElementById('newType').value;
148
  var def = state.rows.filter(function (r) { return r.fofoId === 0 && r.agendaType === t; })[0];
149
  document.getElementById('newParams').innerHTML = def ? paramInputs('new', def.params) : '';
150
}
151
 
152
document.getElementById('addOverrideBtn').onclick = function () {
153
  var fofoId = parseInt(document.getElementById('newFofoId').value, 10);
154
  var msgEl = document.getElementById('addMsg');
155
  if (!fofoId || fofoId < 1) { msgEl.textContent = 'Enter a fofoId'; msgEl.className = 'msg err'; return; }
156
  save(document.getElementById('newType').value, fofoId,
157
       collectParams(document.getElementById('newParams')), msgEl, load);
158
};
159
 
160
function load() {
161
  fetch(BASE + '/data', {headers: {'Accept': 'application/json'}})
162
    .then(function (r) { return r.json(); })
163
    .then(function (j) { state.rows = j.rows || []; state.types = j.configurableTypes || []; render(); })
164
    .catch(function () {
165
      document.querySelector('#defaultsTable tbody').innerHTML =
166
        '<tr><td colspan="4" class="msg err">Failed to load config</td></tr>';
167
    });
168
}
169
load();
170
</script>
171
</body>
172
</html>]]#