Subversion Repositories SmartDukaan

Rev

Rev 37335 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
37331 amit 1
<section class="wrapper">
2
<div class="oc-root">
3
 
4
  <style>
5
  /* Scoped to .oc-root so it cannot leak into the rest of the admin theme.
6
     Colours use rgb()/var() and selectors use classes, never id-hashes -
7
     Velocity would otherwise try to parse a leading "#" as a directive. */
8
  .oc-root {
9
    --oc-bg: rgb(248, 250, 252);
10
    --oc-surface: rgb(255, 255, 255);
11
    --oc-border: rgb(226, 232, 240);
12
    --oc-text: rgb(15, 23, 42);
13
    --oc-muted: rgb(100, 116, 139);
14
    --oc-accent: rgb(79, 70, 229);
15
    --oc-accent-soft: rgb(238, 242, 255);
16
    --oc-ok: rgb(22, 163, 74);
17
    --oc-ok-soft: rgb(240, 253, 244);
18
    --oc-warn: rgb(180, 83, 9);
19
    --oc-warn-soft: rgb(255, 251, 235);
20
    --oc-danger: rgb(190, 18, 60);
21
    --oc-danger-soft: rgb(255, 241, 242);
22
    --oc-radius: 10px;
23
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
24
    color: var(--oc-text);
25
    font-size: 13px;
26
    line-height: 1.5;
27
    /* own padding so the page is readable both inside admin.vm and standalone,
28
       where there is no sidebar offset and content would otherwise touch x=0 */
29
    padding: 18px 22px 32px;
30
    box-sizing: border-box;
31
  }
32
  .oc-root *, .oc-root *::before, .oc-root *::after { box-sizing: border-box; }
33
 
34
  .oc-head { display: flex; align-items: flex-start; justify-content: space-between;
35
             gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
36
  .oc-title { font-size: 20px; font-weight: 650; letter-spacing: -0.01em; margin: 0; }
37
  .oc-sub { color: var(--oc-muted); margin: 4px 0 0; font-size: 12.5px; }
38
 
39
  /* summary tiles */
40
  .oc-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
41
              gap: 10px; margin-bottom: 16px; }
42
  .oc-tile { background: var(--oc-surface); border: 1px solid var(--oc-border);
43
             border-radius: var(--oc-radius); padding: 12px 14px; }
44
  .oc-tile-label { color: var(--oc-muted); font-size: 11px; text-transform: uppercase;
45
                   letter-spacing: 0.05em; font-weight: 600; }
46
  .oc-tile-value { font-size: 22px; font-weight: 650; margin-top: 2px;
47
                   font-variant-numeric: tabular-nums; }
48
 
49
  /* filter bar */
50
  .oc-filters { background: var(--oc-surface); border: 1px solid var(--oc-border);
51
                border-radius: var(--oc-radius); padding: 14px; margin-bottom: 16px;
52
                display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
53
  .oc-field { display: flex; flex-direction: column; gap: 5px; }
54
  .oc-field > label { font-size: 11px; font-weight: 600; color: var(--oc-muted);
55
                      text-transform: uppercase; letter-spacing: 0.04em; }
56
  .oc-root select.oc-input, .oc-root input.oc-input {
57
    height: 34px; padding: 0 10px; border: 1px solid var(--oc-border);
58
    border-radius: 7px; background: var(--oc-surface); color: var(--oc-text);
59
    font-size: 13px; font-family: inherit; min-width: 140px; box-shadow: none; }
60
  .oc-root input.oc-input:focus, .oc-root select.oc-input:focus {
61
    outline: 2px solid var(--oc-accent-soft); border-color: var(--oc-accent); }
62
  .oc-check { display: flex; align-items: center; gap: 7px; height: 34px;
63
              font-size: 12.5px; color: var(--oc-text); }
64
  .oc-root button.oc-btn {
65
    height: 34px; padding: 0 14px; border-radius: 7px; border: 1px solid var(--oc-accent);
66
    background: var(--oc-accent); color: rgb(255, 255, 255); font-size: 13px;
67
    font-weight: 550; font-family: inherit; cursor: pointer; }
68
  .oc-root button.oc-btn-ghost { background: var(--oc-surface); color: var(--oc-text);
69
                                 border-color: var(--oc-border); }
70
  .oc-root button.oc-btn:hover { filter: brightness(0.95); }
71
 
72
  /* table */
73
  .oc-card { background: var(--oc-surface); border: 1px solid var(--oc-border);
74
             border-radius: var(--oc-radius); overflow: hidden; }
75
  .oc-card-head { padding: 12px 14px; border-bottom: 1px solid var(--oc-border);
76
                  display: flex; align-items: center; justify-content: space-between;
77
                  gap: 12px; flex-wrap: wrap; font-weight: 600; }
78
  .oc-scroll { overflow-x: auto; max-height: 68vh; overflow-y: auto; }
79
  .oc-root table.oc-table { width: 100%; border-collapse: separate; border-spacing: 0;
80
                            font-size: 12.5px; }
81
  .oc-root .oc-table thead th {
82
    position: sticky; top: 0; z-index: 2; background: var(--oc-bg);
83
    text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
84
    color: var(--oc-muted); font-weight: 650; padding: 9px 10px;
85
    border-bottom: 1px solid var(--oc-border); white-space: nowrap; }
86
  .oc-root .oc-table tbody td { padding: 9px 10px; border-bottom: 1px solid var(--oc-border);
87
                                vertical-align: top; }
88
  .oc-root .oc-table tbody tr:hover { background: var(--oc-bg); }
89
  .oc-root .oc-table tbody tr.oc-detail-row:hover { background: transparent; }
90
  /* raw source text wraps to a few lines rather than being ellipsised away -
91
     reading the original cell is the entire purpose of this screen */
92
  .oc-raw { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
93
            max-width: 340px; white-space: normal; word-break: break-word; }
94
  .oc-clip { display: block; max-width: 320px; overflow: hidden; text-overflow: ellipsis;
95
             white-space: nowrap; }
96
  .oc-pill-info { background: var(--oc-accent-soft); color: var(--oc-accent);
97
                  border-color: var(--oc-accent); }
98
 
99
  /* raw-vs-parsed comparison inside an expanded row */
100
  .oc-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
101
                    gap: 24px; align-items: start; }
102
  .oc-parsed { font-size: 12.5px; }
103
  .oc-sub { margin-bottom: 14px; }
104
  .oc-sub-h { display: block; font-size: 11px; text-transform: uppercase;
105
              letter-spacing: 0.04em; color: var(--oc-muted); font-weight: 650;
106
              margin-bottom: 5px; }
107
  .oc-root table.oc-mini { width: 100%; border-collapse: collapse; font-size: 12px;
108
                           background: var(--oc-surface); border: 1px solid var(--oc-border);
109
                           border-radius: 7px; overflow: hidden; }
110
  .oc-root .oc-mini th { text-align: left; font-size: 10.5px; text-transform: uppercase;
111
                         letter-spacing: 0.04em; color: var(--oc-muted); font-weight: 650;
112
                         padding: 6px 9px; border-bottom: 1px solid var(--oc-border);
113
                         background: var(--oc-bg); }
114
  .oc-root .oc-mini td { padding: 6px 9px; border-bottom: 1px solid var(--oc-border);
115
                         vertical-align: top; word-break: break-word; }
116
  .oc-root .oc-mini tr:last-child td { border-bottom: none; }
117
  .oc-root .oc-mini code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
118
                           font-size: 11px; background: var(--oc-bg); padding: 1px 5px;
119
                           border-radius: 4px; border: 1px solid var(--oc-border); }
120
  .oc-num { font-variant-numeric: tabular-nums; color: var(--oc-muted); white-space: nowrap; }
37526 amit 121
 
122
  /* Ingest summary. It arrives as a Java Map.toString() - one long line that cannot
123
     wrap usefully - so it is parsed into chips rather than printed. The long drop-reason
124
     map goes behind a disclosure: it is the detail you want when a count looks wrong,
125
     and noise the rest of the time. */
126
  .oc-sum { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
127
            width: 100%; margin-top: 6px; }
128
  .oc-sum-chip { display: inline-flex; align-items: baseline; gap: 5px; padding: 2px 9px;
129
                 border: 1px solid var(--oc-border); border-radius: 999px;
130
                 background: var(--oc-surface); font-size: 11.5px; color: var(--oc-muted);
131
                 white-space: nowrap; }
132
  .oc-sum-chip b { font-weight: 650; color: var(--oc-text); font-variant-numeric: tabular-nums; }
133
  .oc-sum-chip-warn { background: var(--oc-warn-soft); border-color: rgb(253, 230, 138); }
134
  .oc-sum-chip-warn b { color: var(--oc-warn); }
135
  .oc-sum-toggle { border: 1px solid var(--oc-border); background: var(--oc-surface);
136
                   border-radius: 999px; padding: 2px 9px; font-size: 11.5px;
137
                   color: var(--oc-muted); cursor: pointer; }
138
  .oc-sum-toggle:hover { border-color: var(--oc-accent); color: var(--oc-accent); }
139
  .oc-sum-detail { width: 100%; margin-top: 6px; border: 1px solid var(--oc-border);
140
                   border-radius: 8px; background: var(--oc-bg); padding: 6px 10px;
141
                   max-height: 220px; overflow-y: auto; display: none; }
142
  .oc-sum-detail div { display: flex; gap: 14px; justify-content: space-between;
143
                       padding: 3px 0; font-size: 12px; border-bottom: 1px solid var(--oc-border); }
144
  .oc-sum-detail div:last-child { border-bottom: 0; }
145
  .oc-sum-detail span:first-child { color: var(--oc-text); word-break: break-word; }
146
  .oc-sum-detail span:last-child { color: var(--oc-muted); font-variant-numeric: tabular-nums;
147
                                   white-space: nowrap; }
148
  /* anything not parsed still has to wrap rather than run off the card */
149
  .oc-sum-raw { white-space: normal; word-break: break-word; }
37331 amit 150
  .oc-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
151
             font-size: 12px; }
152
 
153
  /* pills */
154
  .oc-pill { display: inline-block; padding: 2px 8px; border-radius: 999px;
155
             font-size: 11px; font-weight: 600; white-space: nowrap; border: 1px solid transparent; }
156
  .oc-pill-ok { background: var(--oc-ok-soft); color: var(--oc-ok); border-color: var(--oc-ok); }
157
  .oc-pill-warn { background: var(--oc-warn-soft); color: var(--oc-warn); border-color: var(--oc-warn); }
158
  .oc-pill-danger { background: var(--oc-danger-soft); color: var(--oc-danger); border-color: var(--oc-danger); }
159
  .oc-pill-muted { background: var(--oc-bg); color: var(--oc-muted); border-color: var(--oc-border); }
160
 
161
  .oc-root button.oc-expand { background: none; border: none; cursor: pointer; padding: 0;
162
                              color: var(--oc-accent); font-weight: 600; font-size: 12px;
163
                              font-family: inherit; }
164
 
165
  /* curation editor, opened under the decision it belongs to */
166
  .oc-edit { background: var(--oc-bg); padding: 14px 16px; }
167
  .oc-edit-actions { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 10px; }
168
  .oc-edit-actions label { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
169
  .oc-edit-row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap;
170
                 margin-bottom: 10px; }
171
  .oc-hits { max-height: 190px; overflow-y: auto; border: 1px solid var(--oc-border);
172
             border-radius: 7px; background: var(--oc-surface); }
173
  .oc-hit { display: block; width: 100%; text-align: left; background: none; border: none;
174
            border-bottom: 1px solid var(--oc-border); padding: 7px 10px; cursor: pointer;
175
            font-family: inherit; font-size: 12.5px; color: var(--oc-text); }
176
  .oc-hit:last-child { border-bottom: none; }
177
  .oc-hit:hover, .oc-hit[data-picked="1"] { background: var(--oc-accent-soft); }
178
  /* the closest miss is shown for context and is deliberately never pre-selected:
179
     on Aug'26 four new OnePlus phones each "nearly" matched a 2015 OnePlus 2 */
180
  .oc-miss { color: var(--oc-muted); font-size: 12px; }
181
 
182
  /* verbatim original-row detail */
183
  .oc-detail { background: var(--oc-bg); padding: 14px 16px; }
184
  .oc-detail-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
185
                     color: var(--oc-muted); font-weight: 650; margin-bottom: 10px; }
186
  .oc-dl { display: grid; grid-template-columns: 190px 1fr; gap: 6px 16px; margin: 0; }
187
  .oc-dl dt { color: var(--oc-muted); font-size: 11.5px; font-weight: 600; }
188
  .oc-dl dd { margin: 0; white-space: pre-wrap; word-break: break-word;
189
              font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
190
              font-size: 12px; }
191
 
192
  .oc-foot { padding: 11px 14px; border-top: 1px solid var(--oc-border);
193
             display: flex; align-items: center; justify-content: space-between;
194
             gap: 12px; flex-wrap: wrap; color: var(--oc-muted); font-size: 12.5px; }
195
  .oc-pager { display: flex; align-items: center; gap: 8px; }
196
  .oc-empty { padding: 44px 16px; text-align: center; color: var(--oc-muted); }
197
  .oc-spin { padding: 30px; text-align: center; color: var(--oc-muted); }
198
 
199
  @media (prefers-color-scheme: dark) {
200
    .oc-root {
201
      --oc-bg: rgb(15, 23, 42);
202
      --oc-surface: rgb(24, 33, 54);
203
      --oc-border: rgb(51, 65, 85);
204
      --oc-text: rgb(226, 232, 240);
205
      --oc-muted: rgb(148, 163, 184);
206
      --oc-accent: rgb(129, 140, 248);
207
      --oc-accent-soft: rgb(35, 45, 75);
208
      --oc-ok-soft: rgb(20, 45, 32);
209
      --oc-warn-soft: rgb(52, 39, 12);
210
      --oc-danger-soft: rgb(60, 20, 30);
211
    }
212
  }
213
  </style>
214
 
215
  <div class="oc-head">
216
    <div>
217
      <h1 class="oc-title">Offer Circular Review</h1>
218
      <p class="oc-sub">Verbatim OEM circular rows as printed in the source PDF, alongside how
219
         each row was interpreted. Mobiles, tablets, smart watches and TVs are in scope.</p>
220
    </div>
221
    <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
222
      <button class="oc-btn oc-btn-ghost" type="button" data-oc-download>Download source PDF</button>
223
      <button class="oc-btn oc-btn-ghost" type="button" data-oc-export>Export CSV</button>
224
      <button class="oc-btn" type="button" data-oc-reload>Refresh</button>
225
    </div>
226
  </div>
227
 
228
  <div class="oc-filters" style="margin-bottom:16px;">
229
    <div class="oc-field" style="flex:1;min-width:260px;">
230
      <label for="oc-doc">Circular</label>
231
      <select class="oc-input" id="oc-doc" data-oc-doc style="width:100%;">
232
        <option value="">Loading circulars...</option>
233
      </select>
234
    </div>
235
    <div class="oc-field">
236
      <label for="oc-upload-month">Upload for month</label>
237
      <input class="oc-input" id="oc-upload-month" type="date" data-oc-upload-month />
238
    </div>
239
    <div class="oc-field">
240
      <label for="oc-upload-file">New circular PDF</label>
241
      <input class="oc-input" id="oc-upload-file" type="file" accept="application/pdf"
242
             data-oc-upload-file style="padding-top:5px;" />
243
    </div>
244
    <button class="oc-btn" type="button" data-oc-upload>Upload</button>
245
    <span data-oc-upload-msg style="font-size:12px;color:var(--oc-muted);"></span>
37335 amit 246
    <!-- Upload and parsing are different processes: the portal stores the PDF, the
247
         cron job parses it minutes later. Without this the screen looks the same
248
         before and after, so "not parsed yet" is indistinguishable from
249
         "parsed, found nothing". -->
250
    <span data-oc-ingest-state style="flex-basis:100%;"></span>
37331 amit 251
  </div>
252
 
253
  <div class="oc-tiles">
254
    <div class="oc-tile"><div class="oc-tile-label">Rows in circular</div>
255
      <div class="oc-tile-value" data-oc-stat="total">&ndash;</div></div>
256
    <div class="oc-tile"><div class="oc-tile-label">Parsed</div>
257
      <div class="oc-tile-value" data-oc-stat="parsed">&ndash;</div></div>
258
    <div class="oc-tile"><div class="oc-tile-label">Needs review</div>
259
      <div class="oc-tile-value" data-oc-stat="pending">&ndash;</div></div>
260
    <div class="oc-tile"><div class="oc-tile-label">Brands</div>
261
      <div class="oc-tile-value" data-oc-stat="brands">&ndash;</div></div>
262
  </div>
263
 
37526 amit 264
  <!-- Product resolution moved to its own screen (Offer Circular -> Resolve Products):
265
       it is a working queue with a form, and a form laid out inside a six-column table
266
       never aligned. Keeping a copy here would also double-bind the delegated handlers. -->
37331 amit 267
  <div class="oc-card" style="margin-bottom:16px;">
268
    <div class="oc-card-head">
37526 amit 269
      <span>Resolve products</span>
270
      <button class="oc-btn" type="button" onclick="if(window.openOfferCircularResolve){window.openOfferCircularResolve('main-content');}else{window.location='/offerCircularResolve';}">Open the queue</button>
37331 amit 271
    </div>
272
  </div>
273
 
274
  <div class="oc-filters">
275
    <div class="oc-field">
276
      <label for="oc-brand">Brand</label>
277
      <select class="oc-input" id="oc-brand" data-oc-filter="brand">
278
        <option value="">All brands</option>
279
      </select>
280
    </div>
281
    <div class="oc-field">
282
      <label for="oc-month">Circular month</label>
283
      <!-- deliberately blank: a hardcoded month silently returns nothing the moment
284
           the loaded circular is for a different month -->
285
      <input class="oc-input" id="oc-month" type="date" data-oc-filter="periodMonth" />
286
    </div>
287
    <div class="oc-field">
288
      <label for="oc-from">Live from</label>
289
      <input class="oc-input" id="oc-from" type="date" data-oc-filter="fromDate" />
290
    </div>
291
    <div class="oc-field">
292
      <label for="oc-to">Live to</label>
293
      <input class="oc-input" id="oc-to" type="date" data-oc-filter="toDate" />
294
    </div>
295
    <div class="oc-field">
296
      <label for="oc-size">Per page</label>
297
      <select class="oc-input" id="oc-size" data-oc-filter="pageSize" style="min-width:90px;">
298
        <option>25</option><option selected>50</option><option>100</option><option>200</option>
299
      </select>
300
    </div>
301
    <label class="oc-check">
302
      <input type="checkbox" id="oc-unmapped" data-oc-filter="onlyUnmapped" /> Only rows needing review
303
    </label>
304
    <label class="oc-check" title="Mobiles, tablets, smart watches and TVs only. Unchecking also shows laptop, printer and audio rows.">
305
      <input type="checkbox" id="oc-inscope" data-oc-filter="inScopeOnly" checked /> Relevant categories only
306
    </label>
307
    <button class="oc-btn oc-btn-ghost" type="button" data-oc-clear>Clear</button>
308
  </div>
309
 
310
  <div class="oc-card">
311
    <div class="oc-card-head">
312
      <span data-oc-caption>Circular rows</span>
313
      <span class="oc-num" data-oc-range></span>
314
    </div>
315
    <div class="oc-scroll">
316
      <table class="oc-table">
317
        <thead>
318
          <tr>
319
            <th>Page/Row</th>
320
            <th>OEM (as printed)</th>
321
            <th>Brand</th>
322
            <th>Category</th>
323
            <th>Cashback</th>
324
            <th>Offer description</th>
325
            <th>EMI tenure</th>
326
            <th>Eligible products</th>
327
            <th>Validity</th>
328
            <th>Status</th>
329
            <th></th>
330
          </tr>
331
        </thead>
332
        <tbody data-oc-body>
333
          <tr><td colspan="11" class="oc-spin">Loading circular rows&hellip;</td></tr>
334
        </tbody>
335
      </table>
336
    </div>
337
    <div class="oc-foot">
338
      <span data-oc-summary></span>
339
      <span class="oc-pager">
340
        <button class="oc-btn oc-btn-ghost" type="button" data-oc-prev>Previous</button>
341
        <span data-oc-page></span>
342
        <button class="oc-btn oc-btn-ghost" type="button" data-oc-next>Next</button>
343
      </span>
344
    </div>
345
  </div>
346
 
347
</div>
348
 
349
<script type="text/javascript">
350
    // This page can be opened directly, so it cannot rely on admin.vm having
351
    // already set the global "context".
352
    window.ocContext = "${rc.contextPath}";
353
 
354
    // Standalone bootstrap. When this view is injected into admin.vm as a fragment,
355
    // jQuery and offer-circular-review.js are already on the page and this does
356
    // nothing. When the URL is opened directly the fragment arrives alone, with no
357
    // include-scripts.vm, so the screen would sit on "Loading..." forever - load
358
    // what is missing ourselves.
359
    (function () {
360
        var base = window.ocContext || '';
361
        function addScript(src, next) {
362
            var s = document.createElement('script');
363
            s.src = src;
364
            s.onload = next;
365
            s.onerror = function () {
366
                var body = document.querySelector('[data-oc-body]');
367
                if (body) {
368
                    body.innerHTML = '<tr><td colspan="11" class="oc-empty">'
369
                        + 'Could not load ' + src + '</td></tr>';
370
                }
371
            };
372
            document.head.appendChild(s);
373
        }
374
        if (!window.jQuery) {
375
            document.documentElement.setAttribute('data-oc-standalone', '1');
376
            addScript(base + '/resources/js/jquery-3.2.1.min.js', function () {
377
                addScript(base + '/resources/js/offer-circular-review.js?v=${version}');
378
            });
379
        }
380
    })();
381
</script>
382
</section>