Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

<section class="wrapper">
<div class="ocs-root">

  <style>
  /* Scoped to .ocs-root so it cannot leak into the admin theme. Colours use rgb()
     and selectors use classes, never id-hashes - Velocity would try to parse a
     leading "#" as a directive. */
  .ocs-root {
    --ocs-surface: rgb(255, 255, 255);
    --ocs-border: rgb(226, 232, 240);
    --ocs-text: rgb(15, 23, 42);
    --ocs-muted: rgb(100, 116, 139);
    --ocs-accent: rgb(79, 70, 229);
    --ocs-accent-soft: rgb(238, 242, 255);
    --ocs-ok: rgb(22, 163, 74);
    --ocs-ok-soft: rgb(240, 253, 244);
    --ocs-warn: rgb(180, 83, 9);
    --ocs-warn-soft: rgb(255, 251, 235);
    --ocs-danger: rgb(190, 18, 60);
    --ocs-danger-soft: rgb(255, 241, 242);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    color: var(--ocs-text);
    font-size: 13px;
    line-height: 1.5;
    padding: 18px 22px 32px;
    box-sizing: border-box;
  }
  .ocs-root *, .ocs-root *::before, .ocs-root *::after { box-sizing: border-box; }

  .ocs-head { display: flex; align-items: flex-start; justify-content: space-between;
              gap: 16px; flex-wrap: wrap; margin-bottom: 8px; }
  .ocs-title { font-size: 20px; font-weight: 650; letter-spacing: -0.01em; margin: 0; }
  .ocs-sub { color: var(--ocs-muted); margin: 4px 0 0; font-size: 12.5px; max-width: 70ch; }

  .ocs-note { background: var(--ocs-warn-soft); border: 1px solid rgb(253, 230, 138);
              color: var(--ocs-warn); border-radius: 8px; padding: 9px 12px;
              font-size: 12.5px; margin: 14px 0 18px; }

  .ocs-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--ocs-border);
              margin-bottom: 16px; }
  .ocs-tab { border: 0; background: none; padding: 8px 14px; font-size: 13px;
             color: var(--ocs-muted); cursor: pointer; border-bottom: 2px solid transparent; }
  .ocs-tab.is-on { color: var(--ocs-accent); border-bottom-color: var(--ocs-accent);
                   font-weight: 600; }

  .ocs-card { background: var(--ocs-surface); border: 1px solid var(--ocs-border);
              border-radius: 10px; padding: 16px 18px; margin-bottom: 18px; }
  .ocs-card h3 { margin: 0 0 4px; font-size: 14px; font-weight: 650; }
  .ocs-card p.ocs-hint { margin: 0 0 14px; color: var(--ocs-muted); font-size: 12.5px;
                         max-width: 78ch; }

  .ocs-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
  .ocs-field { display: flex; flex-direction: column; gap: 4px; }
  .ocs-field label { font-size: 11.5px; color: var(--ocs-muted); text-transform: uppercase;
                     letter-spacing: 0.04em; }
  .ocs-field input, .ocs-field select {
      border: 1px solid var(--ocs-border); border-radius: 7px; padding: 6px 9px;
      font-size: 13px; min-width: 170px; background: var(--ocs-surface);
      color: var(--ocs-text); height: 32px; }
  .ocs-field-wide input { min-width: 260px; }

  .ocs-btn { border: 1px solid var(--ocs-border); background: var(--ocs-surface);
             color: var(--ocs-text); border-radius: 7px; padding: 6px 13px;
             font-size: 12.5px; cursor: pointer; height: 32px; }
  .ocs-btn:hover { border-color: var(--ocs-accent); color: var(--ocs-accent); }
  .ocs-btn-primary { background: var(--ocs-accent); border-color: var(--ocs-accent);
                     color: rgb(255, 255, 255); }
  .ocs-btn-primary:hover { color: rgb(255, 255, 255); opacity: 0.92; }
  .ocs-btn-danger { color: var(--ocs-danger); }
  .ocs-btn-danger:hover { border-color: var(--ocs-danger); color: var(--ocs-danger); }
  .ocs-btn-sm { height: 26px; padding: 2px 9px; font-size: 12px; }

  table.ocs-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
  table.ocs-table th { text-align: left; font-weight: 600; color: var(--ocs-muted);
                       font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.04em;
                       padding: 8px 10px; border-bottom: 1px solid var(--ocs-border);
                       white-space: nowrap; }
  table.ocs-table td { padding: 8px 10px; border-bottom: 1px solid var(--ocs-border);
                       vertical-align: middle; }
  table.ocs-table tr.ocs-off td { background: rgb(250, 250, 251); color: var(--ocs-muted); }
  .ocs-scroll { overflow-x: auto; }

  .ocs-pill { display: inline-block; border-radius: 999px; padding: 1px 8px;
              font-size: 11.5px; font-weight: 600; }
  .ocs-pill-on { background: var(--ocs-ok-soft); color: var(--ocs-ok); }
  .ocs-pill-off { background: var(--ocs-danger-soft); color: var(--ocs-danger); }
  .ocs-pill-alias { background: var(--ocs-accent-soft); color: var(--ocs-accent); }
  .ocs-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
              font-size: 12px; }
  .ocs-muted { color: var(--ocs-muted); }
  .ocs-empty { text-align: center; color: var(--ocs-muted); padding: 22px; }

  .ocs-msg { border-radius: 8px; padding: 9px 12px; font-size: 12.5px; margin-bottom: 14px;
             display: none; }
  .ocs-msg-ok { background: var(--ocs-ok-soft); color: var(--ocs-ok);
                border: 1px solid rgb(187, 247, 208); }
  .ocs-msg-bad { background: var(--ocs-danger-soft); color: var(--ocs-danger);
                 border: 1px solid rgb(254, 205, 211); }

  .ocs-export { width: 100%; min-height: 220px; font-family: ui-monospace, Menlo, monospace;
                font-size: 12px; border: 1px solid var(--ocs-border); border-radius: 8px;
                padding: 10px; display: none; }
  </style>

  <div class="ocs-head">
    <div>
      <h2 class="ocs-title">Offer circular &mdash; scope config</h2>
      <p class="ocs-sub">Which OEM divisions get ingested from the monthly circular, and
         which alternate labels mean a division that already exists. This is the only
         authority on scope &mdash; the parser has no built-in list.</p>
    </div>
    <div>
      <button class="ocs-btn" type="button" data-ocs-export>Export as SQL</button>
      <button class="ocs-btn" type="button" data-ocs-reload>Reload</button>
    </div>
  </div>

  <div class="ocs-note">
    Changes here take effect on the <strong>next ingest</strong>. Scope is read when a
    circular is parsed, so editing it does not alter a circular that is already published
    &mdash; re-ingest that circular from the review screen to apply it.
  </div>

  <div class="ocs-msg ocs-msg-ok" data-ocs-ok></div>
  <div class="ocs-msg ocs-msg-bad" data-ocs-bad></div>

  <div class="ocs-tabs">
    <button class="ocs-tab is-on" type="button" data-ocs-tab="divisions">Divisions</button>
    <button class="ocs-tab" type="button" data-ocs-tab="aliases">Label aliases</button>
  </div>

  <div data-ocs-panel="divisions">
    <div class="ocs-card">
      <h3>Add or edit a division</h3>
      <p class="ocs-hint">The label must match the circular's &ldquo;OEM Name&rdquo; cell
         exactly. Saving an existing label edits it. A division needs a category so the
         matcher knows which catalog bucket to search; a brand may be missing (Google has
         no catalog brand row), in which case its products stay unresolved.</p>
      <div class="ocs-form">
        <div class="ocs-field ocs-field-wide">
          <label>OEM label (verbatim)</label>
          <input type="text" data-ocs-d-label placeholder="Samsung Tabs" />
        </div>
        <div class="ocs-field">
          <label>Display name</label>
          <input type="text" data-ocs-d-display placeholder="Samsung Tablets" />
        </div>
        <div class="ocs-field">
          <label>Brand</label>
          <select data-ocs-d-brand><option value="">- none -</option></select>
        </div>
        <div class="ocs-field">
          <label>Category</label>
          <select data-ocs-d-category><option value="">- none -</option></select>
        </div>
        <div class="ocs-field">
          <label>In scope</label>
          <select data-ocs-d-inscope>
            <option value="true">Yes - ingest it</option>
            <option value="false">No - drop it</option>
          </select>
        </div>
        <div class="ocs-field ocs-field-wide">
          <label>Reason (required when out of scope)</label>
          <input type="text" data-ocs-d-reason placeholder="Laptops - out of scope" />
        </div>
        <button class="ocs-btn ocs-btn-primary" type="button" data-ocs-d-save>Save division</button>
        <button class="ocs-btn" type="button" data-ocs-d-clear>Clear</button>
      </div>
    </div>

    <div class="ocs-card">
      <h3>Divisions</h3>
      <p class="ocs-hint">Taking a division out of scope never deletes anything &mdash;
         offers already ingested keep their history. It changes what the next ingest keeps.</p>
      <div class="ocs-scroll">
        <table class="ocs-table">
          <thead>
            <tr>
              <th>OEM label</th><th>Display name</th><th>Brand</th><th>Category</th>
              <th>Scope</th><th>Reason</th><th>Offers</th><th>Aliases</th><th></th>
            </tr>
          </thead>
          <tbody data-ocs-divisions>
            <tr><td colspan="9" class="ocs-empty">Loading&hellip;</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>

  <div data-ocs-panel="aliases" style="display:none">
    <div class="ocs-card">
      <h3>Add a label alias</h3>
      <p class="ocs-hint">Use this when the circular prints a different spelling of a
         division that already exists &mdash; &ldquo;XIAOMI CE&rdquo; for
         &ldquo;Xiaomi CE&rdquo;, say. An alias only says two labels mean the same
         division; it carries no brand or category of its own, so the scope decision stays
         in one place. If the label needs a <em>different</em> category, it is a division,
         not an alias.</p>
      <div class="ocs-form">
        <div class="ocs-field ocs-field-wide">
          <label>Label as printed</label>
          <input type="text" data-ocs-a-label placeholder="XIAOMI CE" />
        </div>
        <div class="ocs-field">
          <label>Means this division</label>
          <select data-ocs-a-division><option value="">- pick one -</option></select>
        </div>
        <div class="ocs-field ocs-field-wide">
          <label>Note (optional)</label>
          <input type="text" data-ocs-a-note placeholder="Sep'26 circular prints it upper case" />
        </div>
        <button class="ocs-btn ocs-btn-primary" type="button" data-ocs-a-save>Save alias</button>
      </div>
    </div>

    <div class="ocs-card">
      <h3>Label aliases</h3>
      <div class="ocs-scroll">
        <table class="ocs-table">
          <thead>
            <tr><th>Label as printed</th><th>Means</th><th>Note</th><th>Added by</th><th></th></tr>
          </thead>
          <tbody data-ocs-aliases>
            <tr><td colspan="5" class="ocs-empty">Loading&hellip;</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>

  <textarea class="ocs-export" data-ocs-export-box readonly></textarea>

</div>

<script type="text/javascript">
    // This page can be opened directly, so it cannot rely on admin.vm having already
    // set the global "context".
    window.ocsContext = "${rc.contextPath}";

    // Standalone bootstrap - same reason as the review screen. Injected as a fragment
    // jQuery is already present and this does nothing; opened by URL the fragment
    // arrives alone with no include-scripts.vm, and the screen would never initialise.
    (function () {
        var base = window.ocsContext || '';
        function addScript(src, next) {
            var s = document.createElement('script');
            s.src = src;
            s.onload = next;
            document.head.appendChild(s);
        }
        // The script registers everything inside $(function(){...}), which runs on the
        // NEXT tick - so at this script's onload the init function does not exist yet.
        // Calling it here directly is a silent no-op that leaves the screen on "Loading".
        // Defer through jQuery's ready queue instead: our callback is registered after
        // the file's own, so it runs after it.
        function start() {
            if (!window.jQuery) { return; }
            window.jQuery(function () {
                if (window.initOfferCircularScope) { window.initOfferCircularScope(); }
            });
        }
        if (!window.jQuery) {
            addScript(base + '/resources/js/jquery-3.2.1.min.js', function () {
                addScript(base + '/resources/js/offer-circular-scope.js?v=${version}', start);
            });
        } else if (!window.initOfferCircularScope) {
            addScript(base + '/resources/js/offer-circular-scope.js?v=${version}', start);
        } else {
            start();
        }
    })();
</script>
</section>