Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37526 amit 1
/**
2
 * Offer circular - scope config screen.
3
 *
4
 * Which OEM divisions are ingested, and which alternate labels mean a division that
5
 * already exists. Everything here is CONFIG read at ingest time, so nothing on this
6
 * screen changes an already-published circular; the copy says so in several places
7
 * because that was the single most confusing thing about the product-alias screen.
8
 *
9
 * Like the review screen this is an AJAX fragment, so every handler is delegated from
10
 * document and state is rebuilt on each load. It must also work when the URL is opened
11
 * directly, which is why it uses $.ajax rather than common.js's helpers.
12
 */
13
$(function () {
14
 
15
    var state = { divisions: [], aliases: [], brands: [], categories: [], loaded: false };
16
 
17
    function ctx() {
18
        return (typeof context !== 'undefined' && context) ? context : (window.ocsContext || '');
19
    }
20
 
21
    function root() {
22
        return $('.ocs-root');
23
    }
24
 
25
    function esc(value) {
26
        if (value === null || value === undefined) {
27
            return '';
28
        }
29
        return $('<span>').text(String(value)).html();
30
    }
31
 
32
    function dash(value) {
33
        return (value === null || value === undefined || String(value).trim() === '')
34
            ? '<span class="ocs-muted">-</span>' : esc(value);
35
    }
36
 
37
    function ocsGet(url, onDone, onFail) {
38
        $.ajax({ url: url, type: 'GET', cache: false }).done(onDone).fail(onFail || noop);
39
    }
40
 
41
    function ocsPost(url, data, onDone, onFail) {
42
        $.ajax({ url: url, type: 'POST', data: data }).done(onDone).fail(onFail || noop);
43
    }
44
 
45
    function noop() {}
46
 
47
    function failMessage(xhr) {
48
        if (xhr.status === 0) { return 'could not reach the server'; }
49
        if (xhr.status === 404) { return 'endpoint not found (404) - is this build deployed?'; }
50
        if (xhr.status === 403 || xhr.status === 401) { return 'not authorised (' + xhr.status + ')'; }
51
        return 'server returned status ' + xhr.status;
52
    }
53
 
54
    function say(ok, message) {
55
        var okBox = root().find('[data-ocs-ok]');
56
        var badBox = root().find('[data-ocs-bad]');
57
        okBox.hide();
58
        badBox.hide();
59
        var box = ok ? okBox : badBox;
60
        box.text(message).show();
61
        if (ok) {
62
            window.setTimeout(function () { box.fadeOut(200); }, 6000);
63
        }
64
    }
65
 
66
    /**
67
     * The endpoints answer with the "response" view, which may arrive as a JSON string
68
     * or as an already-parsed object depending on how jQuery read the content type.
69
     */
70
    function payloadOf(response) {
71
        if (typeof response === 'string') {
72
            try { return JSON.parse(response); } catch (e) { return null; }
73
        }
74
        return response;
75
    }
76
 
77
    // ------------------------------------------------------------------ rendering
78
 
79
    function optionList(items, selected, noneLabel) {
80
        var html = '<option value="">' + esc(noneLabel) + '</option>';
81
        items.forEach(function (item) {
82
            html += '<option value="' + esc(item.id) + '"'
83
                 + (String(item.id) === String(selected) ? ' selected' : '') + '>'
84
                 + esc(item.name) + '</option>';
85
        });
86
        return html;
87
    }
88
 
89
    function renderPickers() {
90
        root().find('[data-ocs-d-brand]').html(optionList(state.brands, '', '- none -'));
91
        root().find('[data-ocs-d-category]').html(optionList(state.categories, '', '- none -'));
92
 
93
        var divisionOptions = state.divisions.map(function (d) {
94
            return { id: d.pdfLabel, name: d.pdfLabel + ' (' + (d.category || 'no category') + ')' };
95
        });
96
        root().find('[data-ocs-a-division]')
97
              .html(optionList(divisionOptions, '', '- pick one -'));
98
    }
99
 
100
    function renderDivisions() {
101
        var body = root().find('[data-ocs-divisions]');
102
        if (!state.divisions.length) {
103
            body.html('<tr><td colspan="9" class="ocs-empty">No divisions configured.</td></tr>');
104
            return;
105
        }
106
        var html = '';
107
        state.divisions.forEach(function (d, index) {
108
            html += '<tr class="' + (d.inScope ? '' : 'ocs-off') + '">'
109
                 + '<td class="ocs-mono">' + esc(d.pdfLabel) + '</td>'
110
                 + '<td>' + dash(d.displayName) + '</td>'
111
                 + '<td>' + dash(d.brand) + '</td>'
112
                 + '<td>' + dash(d.category) + '</td>'
113
                 + '<td><span class="ocs-pill ' + (d.inScope ? 'ocs-pill-on">in scope'
114
                                                             : 'ocs-pill-off">dropped')
115
                 + '</span></td>'
116
                 + '<td>' + dash(d.dropReason) + '</td>'
117
                 + '<td>' + esc(d.offerCount) + '</td>'
118
                 + '<td>' + esc(d.labelAliasCount) + '</td>'
119
                 + '<td style="white-space:nowrap">'
120
                 + '<button class="ocs-btn ocs-btn-sm" type="button" data-ocs-edit="'
121
                 + index + '">Edit</button> '
122
                 + '<button class="ocs-btn ocs-btn-sm ' + (d.inScope ? 'ocs-btn-danger' : '')
123
                 + '" type="button" data-ocs-toggle="' + index + '">'
124
                 + (d.inScope ? 'Remove' : 'Add back') + '</button>'
125
                 + '</td></tr>';
126
        });
127
        body.html(html);
128
    }
129
 
130
    function renderAliases() {
131
        var body = root().find('[data-ocs-aliases]');
132
        if (!state.aliases.length) {
133
            body.html('<tr><td colspan="5" class="ocs-empty">'
134
                + 'No label aliases yet. Add one when a circular prints a spelling of a '
135
                + 'division that is already configured.</td></tr>');
136
            return;
137
        }
138
        var html = '';
139
        state.aliases.forEach(function (a) {
140
            html += '<tr>'
141
                 + '<td class="ocs-mono">' + esc(a.aliasLabel) + '</td>'
142
                 + '<td><span class="ocs-pill ocs-pill-alias">' + esc(a.divisionLabel)
143
                 + '</span></td>'
144
                 + '<td>' + dash(a.note) + '</td>'
145
                 + '<td class="ocs-muted">' + dash(a.createdBy) + '</td>'
146
                 + '<td><button class="ocs-btn ocs-btn-sm ocs-btn-danger" type="button" '
147
                 + 'data-ocs-alias-remove="' + esc(a.aliasLabel) + '">Remove</button></td>'
148
                 + '</tr>';
149
        });
150
        body.html(html);
151
    }
152
 
153
    function load() {
154
        ocsGet(ctx() + '/offerCircular/divisions', function (response) {
155
            var payload = payloadOf(response);
156
            if (!payload) {
157
                say(false, 'Could not read the configuration response');
158
                return;
159
            }
160
            state.divisions = payload.divisions || [];
161
            state.aliases = payload.aliases || [];
162
            state.brands = payload.brands || [];
163
            state.categories = payload.categories || [];
164
            state.loaded = true;
165
            renderPickers();
166
            renderDivisions();
167
            renderAliases();
168
        }, function (xhr) {
169
            root().find('[data-ocs-divisions]').html('<tr><td colspan="9" class="ocs-empty">'
170
                + 'Could not load divisions: ' + esc(failMessage(xhr)) + '</td></tr>');
171
        });
172
    }
173
 
174
    // ------------------------------------------------------------------ actions
175
 
176
    function divisionForm() {
177
        return {
178
            pdfLabel: root().find('[data-ocs-d-label]').val(),
179
            displayName: root().find('[data-ocs-d-display]').val(),
180
            brandId: root().find('[data-ocs-d-brand]').val(),
181
            categoryId: root().find('[data-ocs-d-category]').val(),
182
            inScope: root().find('[data-ocs-d-inscope]').val(),
183
            dropReason: root().find('[data-ocs-d-reason]').val()
184
        };
185
    }
186
 
187
    function clearDivisionForm() {
188
        root().find('[data-ocs-d-label], [data-ocs-d-display], [data-ocs-d-reason]').val('');
189
        root().find('[data-ocs-d-brand], [data-ocs-d-category]').val('');
190
        root().find('[data-ocs-d-inscope]').val('true');
191
    }
192
 
193
    function afterWrite(response, fallback) {
194
        var payload = payloadOf(response) || {};
195
        say(payload.success !== false, payload.message || fallback);
196
        if (payload.success !== false) {
197
            if (payload.warning) {
198
                window.setTimeout(function () { say(false, payload.warning); }, 300);
199
            }
200
            load();
201
        }
202
    }
203
 
204
    $(document).on('click', '.ocs-root [data-ocs-d-save]', function () {
205
        var form = divisionForm();
206
        if (!String(form.pdfLabel || '').trim()) {
207
            say(false, 'The OEM label is required');
208
            return;
209
        }
210
        ocsPost(ctx() + '/offerCircular/division', form, function (response) {
211
            afterWrite(response, 'Saved');
212
        }, function (xhr) { say(false, 'Could not save: ' + failMessage(xhr)); });
213
    });
214
 
215
    $(document).on('click', '.ocs-root [data-ocs-d-clear]', clearDivisionForm);
216
 
217
    $(document).on('click', '.ocs-root [data-ocs-edit]', function () {
218
        var d = state.divisions[$(this).data('ocs-edit')];
219
        if (!d) { return; }
220
        root().find('[data-ocs-d-label]').val(d.pdfLabel);
221
        root().find('[data-ocs-d-display]').val(d.displayName || '');
222
        root().find('[data-ocs-d-brand]').val(d.brandId === null ? '' : d.brandId);
223
        root().find('[data-ocs-d-category]').val(d.categoryId === null ? '' : d.categoryId);
224
        root().find('[data-ocs-d-inscope]').val(d.inScope ? 'true' : 'false');
225
        root().find('[data-ocs-d-reason]').val(d.dropReason || '');
226
        showPanel('divisions');
227
        root().find('[data-ocs-d-label]').focus();
228
    });
229
 
230
    $(document).on('click', '.ocs-root [data-ocs-toggle]', function () {
231
        var d = state.divisions[$(this).data('ocs-toggle')];
232
        if (!d) { return; }
233
        var reason = null;
234
        if (d.inScope) {
235
            // The reason is the only record of why something stopped being ingested, so
236
            // it is asked for rather than defaulted.
237
            reason = window.prompt('Take "' + d.pdfLabel + '" out of scope.\n\n'
238
                + (d.offerCount > 0
239
                    ? d.offerCount + ' existing offers stay untouched; this changes what the '
240
                      + 'next ingest keeps.\n\n' : '')
241
                + 'Why is it out of scope?', d.dropReason || '');
242
            if (reason === null) { return; }
243
            if (!reason.trim()) {
244
                say(false, 'A reason is required to take a division out of scope');
245
                return;
246
            }
247
        }
248
        ocsPost(ctx() + '/offerCircular/divisionScope',
249
            { divisionId: d.divisionId, inScope: !d.inScope, dropReason: reason },
250
            function (response) { afterWrite(response, 'Updated'); },
251
            function (xhr) { say(false, 'Could not update: ' + failMessage(xhr)); });
252
    });
253
 
254
    $(document).on('click', '.ocs-root [data-ocs-a-save]', function () {
255
        var aliasLabel = root().find('[data-ocs-a-label]').val();
256
        var divisionLabel = root().find('[data-ocs-a-division]').val();
257
        if (!String(aliasLabel || '').trim() || !divisionLabel) {
258
            say(false, 'Both the printed label and the division it means are required');
259
            return;
260
        }
261
        ocsPost(ctx() + '/offerCircular/divisionAlias',
262
            { aliasLabel: aliasLabel, divisionLabel: divisionLabel,
263
              note: root().find('[data-ocs-a-note]').val() },
264
            function (response) {
265
                var payload = payloadOf(response) || {};
266
                if (payload.success !== false) {
267
                    root().find('[data-ocs-a-label], [data-ocs-a-note]').val('');
268
                }
269
                afterWrite(response, 'Saved');
270
            },
271
            function (xhr) { say(false, 'Could not save: ' + failMessage(xhr)); });
272
    });
273
 
274
    $(document).on('click', '.ocs-root [data-ocs-alias-remove]', function () {
275
        var label = $(this).data('ocs-alias-remove');
276
        if (!window.confirm('Remove the alias "' + label + '"? That label will be reported '
277
                + 'as UNKNOWN on the next ingest.')) {
278
            return;
279
        }
280
        ocsPost(ctx() + '/offerCircular/divisionAliasRemove', { aliasLabel: label },
281
            function (response) { afterWrite(response, 'Removed'); },
282
            function (xhr) { say(false, 'Could not remove: ' + failMessage(xhr)); });
283
    });
284
 
285
    $(document).on('click', '.ocs-root [data-ocs-reload]', load);
286
 
287
    $(document).on('click', '.ocs-root [data-ocs-export]', function () {
288
        var box = root().find('[data-ocs-export-box]');
289
        if (box.is(':visible')) {
290
            box.hide();
291
            return;
292
        }
293
        ocsGet(ctx() + '/offerCircular/scopeExport', function (response) {
294
            var payload = payloadOf(response) || {};
295
            box.val(payload.sql || '').show();
296
            box[0].select();
297
        }, function (xhr) { say(false, 'Could not export: ' + failMessage(xhr)); });
298
    });
299
 
300
    function showPanel(name) {
301
        root().find('[data-ocs-panel]').hide();
302
        root().find('[data-ocs-panel="' + name + '"]').show();
303
        root().find('[data-ocs-tab]').removeClass('is-on');
304
        root().find('[data-ocs-tab="' + name + '"]').addClass('is-on');
305
    }
306
 
307
    $(document).on('click', '.ocs-root [data-ocs-tab]', function () {
308
        showPanel($(this).data('ocs-tab'));
309
    });
310
 
311
    // sidebar entry / any trigger loads the fragment then initialises it
312
    function openOfferCircularScope(domId) {
313
        ocsGet(ctx() + '/offerCircularScope', function (response) {
314
            $('#' + (domId || 'main-content')).html(response);
315
            state = { divisions: [], aliases: [], brands: [], categories: [], loaded: false };
316
            load();
317
        });
318
    }
319
    window.openOfferCircularScope = openOfferCircularScope;
320
 
321
    // called by the standalone bootstrap in the .vm when the URL is opened directly
322
    window.initOfferCircularScope = load;
323
 
324
    $(document).on('click', '.offer-circular-scope', function (e) {
325
        e.preventDefault();
326
        openOfferCircularScope('main-content');
327
    });
328
});