View | Details | Raw Unified | Return to bug 14962
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/DisplaysBatchAddItems.vue (+177 lines)
Line 0 Link Here
1
<template>
2
    <h2>{{ $__("Batch add items from list") }}</h2>
3
    <div class="page-section" id="list">
4
        <form @submit="batchAdd($event)">
5
            <fieldset class="rows" id="display_list">
6
                <h3>{{ $__("Sepcify items to add") }}:</h3>
7
                <ol>
8
                    <li>
9
                        <label for="barcodes">{{ $__("Item barcodes") }}:</label>
10
                        <textarea
11
                            id="barcodes"
12
                            v-model="barcodes"
13
                            label="barcodes"
14
                            :cols="`25`"
15
                            :rows="`10`"
16
                            :required="!barcodes"
17
                        />
18
                        <span class="required">{{ $__("Required") }}</span>
19
                        <div class="hint">
20
                            {{ $__("List of item barcodes, one per line") }}<br />
21
                        </div>
22
                    </li>
23
                    <li>
24
                        <label for="display_id"
25
                            >{{ $__("To the following display") }}:</label
26
                        >
27
                        <v-select
28
                            id="display_id"
29
                            v-model="display_id"
30
                            label="display_name"
31
                            :reduce="d => d.display_id"
32
                            :options="displays"
33
                            :clearable="false"
34
                            :required="!display_id"
35
                        >
36
                            <template #search="{ attributes, events }">
37
                                <input
38
                                    :required="!display_id"
39
                                    class="vs__search"
40
                                    v-bind="attributes"
41
                                    v-on="events"
42
                                />
43
                            </template>
44
                        </v-select>
45
                        <span class="required">{{ $__("Required") }}</span>
46
                    </li>
47
                    <li>
48
                        <label for="date_remove"
49
                            >{{ $__("To remove on this date") }}:</label
50
                        >
51
                        <FlatPickrWrapper
52
                            :id="`date_remove`"
53
                            :name="`date_remove`"
54
                            v-model="date_remove"
55
                            label="date_remove"
56
                        />
57
                    </li>
58
                </ol>
59
            </fieldset>
60
            <fieldset class="action">
61
                <ButtonSubmit
62
                    :title="$__('Save')"
63
                />
64
                <router-link
65
                    :to="{
66
                        name: 'DisplaysList',
67
                    }"
68
                    role="button"
69
                    class="cancel"
70
                    >{{ $__("Cancel") }}</router-link
71
                >
72
            </fieldset>
73
        </form>
74
    </div>
75
</template>
76
77
<script>
78
import { ref, inject, onBeforeMount } from "vue";
79
import ButtonSubmit from "../ButtonSubmit.vue";
80
import FlatPickrWrapper from "@koha-vue/components/FlatPickrWrapper.vue";
81
import { storeToRefs } from "pinia";
82
import { APIClient } from "../../fetch/api-client.js";
83
import { $__ } from "@koha-vue/i18n";
84
85
export default {
86
    props: {
87
        routeAction: String,
88
        component: String,
89
        embedded: { type: Boolean, default: false },
90
        componentPropData: Object,
91
        embedEvent: Function,
92
    },
93
    setup(props) {
94
        const DisplayStore = inject("DisplayStore");
95
        const { config } = storeToRefs(DisplayStore);
96
        const { setMessage, setWarning, setError } = inject("mainStore");
97
98
        const displays = ref([]);
99
        const display_id = ref(null);
100
        const barcodes = ref(null);
101
        const date_remove = ref(null);
102
103
        const batchAdd = event => {
104
            event.preventDefault();
105
106
            barcodes.value = barcodes.value
107
            .split("\n")
108
            .map(n => Number(n))
109
            .filter(n => {
110
                if (n == '')
111
                    return false;
112
113
                return true;
114
            });
115
116
            const client = APIClient.display;
117
            const importData = {
118
                display_id: display_id.value,
119
                barcodes: barcodes.value,
120
            };
121
            if (date_remove.value != null)
122
                importData.date_remove = date_remove.value;
123
124
            client.displayItems.batchAdd(importData).then(
125
                success => {
126
                    if (success.job_id)
127
                        setMessage(`${$__('Batch job successfully queued.')} <a href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id=${success.job_id}" target="_blank">${$__('Click here to view job progress')}</a>`, true);
128
129
                    if (!success.job_id)
130
                        setWarning($__('Batch job failed to queue. Please check your list, and try again.'), true);
131
                },
132
                error => {
133
                    setError($__('Internal Server Error. Please check the browser console for diagnostic information.'), true);
134
                    console.error(error);
135
                },
136
            );
137
            clearForm();
138
        };
139
        const clearForm = () => {
140
            display_id.value = null;
141
            barcodes.value = null;
142
            date_remove.value = null;
143
        };
144
145
        onBeforeMount(() => {
146
            const client = APIClient.display;
147
            client.displays.getAll().then(
148
                result => {
149
                    displays.value = result;
150
                },
151
                error => {}
152
            );
153
        });
154
        return {
155
            setMessage,
156
            setWarning,
157
            displays,
158
            display_id,
159
            barcodes,
160
            date_remove,
161
            batchAdd,
162
            clearForm,
163
        };
164
    },
165
    components: {
166
        ButtonSubmit,
167
        FlatPickrWrapper,
168
    },
169
    name: "DisplaysBatchAddItems",
170
};
171
</script>
172
173
<style scoped>
174
label {
175
    margin: 0px 10px 0px 0px;
176
}
177
</style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/DisplaysBatchRemoveItems.vue (+153 lines)
Line 0 Link Here
1
<template>
2
    <h2>{{ $__("Batch remove items from list") }}</h2>
3
    <div class="page-section" id="list">
4
        <form @submit="batchRemove($event)">
5
            <fieldset class="rows" id="display_list">
6
                <h3>{{ $__("Specify items to remove") }}:</h3>
7
                <ol>
8
                    <li>
9
                        <label for="barcodes">{{ $__("Item barcodes") }}:</label>
10
                        <textarea
11
                            id="barcodes"
12
                            v-model="barcodes"
13
                            label="barcodes"
14
                            :cols="`25`"
15
                            :rows="`10`"
16
                            :required="!barcodes"
17
                        />
18
                        <span class="required">{{ $__("Required") }}</span>
19
                        <div class="hint">
20
                            {{ $__("List of item barcodes, one per line") }}<br />
21
                        </div>
22
                    </li>
23
                    <li>
24
                        <label for="display_id"
25
                            >{{ $__("From the following display") }}:</label
26
                        >
27
                        <v-select
28
                            id="display_id"
29
                            v-model="display_id"
30
                            label="display_name"
31
                            :reduce="d => d.display_id"
32
                            :options="displays"
33
                            :clearable="false"
34
                            :required="!display_id"
35
                        >
36
                            <template #search="{ attributes, events }">
37
                                <input
38
                                    :required="!display_id"
39
                                    class="vs__search"
40
                                    v-bind="attributes"
41
                                    v-on="events"
42
                                />
43
                            </template>
44
                        </v-select>
45
                        <span class="required">{{ $__("Required") }}</span>
46
                    </li>
47
                </ol>
48
            </fieldset>
49
            <fieldset class="action">
50
                <ButtonSubmit
51
                    :title="$__('Save')"
52
                />
53
                <router-link
54
                    :to="{
55
                        name: 'DisplaysList',
56
                    }"
57
                    role="button"
58
                    class="cancel"
59
                    >{{ $__("Cancel") }}</router-link
60
                >
61
            </fieldset>
62
        </form>
63
    </div>
64
</template>
65
66
<script>
67
import { ref, inject, useTemplateRef, onBeforeMount } from "vue";
68
import ButtonSubmit from "../ButtonSubmit.vue";
69
import { storeToRefs } from "pinia";
70
import { APIClient } from "../../fetch/api-client.js";
71
import { $__ } from "@koha-vue/i18n";
72
73
export default {
74
    props: {
75
        routeAction: String,
76
        embedded: { type: Boolean, default: false },
77
        embedEvent: Function,
78
    },
79
    setup(props) {
80
        const DisplayStore = inject("DisplayStore");
81
        const { config } = storeToRefs(DisplayStore);
82
        const { setMessage, setWarning, setError } = inject("mainStore");
83
84
        const displays = ref([]);
85
        const display_id = ref(null);
86
        const barcodes = ref(null);
87
88
        const batchRemove = event => {
89
            event.preventDefault();
90
91
            barcodes.value = barcodes.value
92
            .split("\n")
93
            .map(n => Number(n))
94
            .filter(n => {
95
                if (n == '')
96
                    return false;
97
98
                return true;
99
            });
100
101
            const client = APIClient.display;
102
            const importData = {
103
                display_id: display_id.value,
104
                barcodes: barcodes.value,
105
            };
106
107
            client.displayItems.batchDelete(importData).then(
108
                success => {
109
                    setMessage(`${$__('Batch job successfully queued.')} <a href="/cgi-bin/koha/admin/background_jobs.pl" target="_blank">${$__('Click here to view job progress')}</a>`, true);
110
                },
111
                error => {
112
                    setError($__('Internal Server Error. Please check the browser console for diagnostic information.'), true);
113
                    console.error(error);
114
                },
115
            );
116
            clearForm();
117
        };
118
        const clearForm = () => {
119
            display_id.value = null;
120
            barcodes.value = null;
121
        };
122
123
        onBeforeMount(() => {
124
            const client = APIClient.display;
125
            client.displays.getAll().then(
126
                result => {
127
                    displays.value = result;
128
                },
129
                error => {}
130
            );
131
        });
132
        return {
133
            setMessage,
134
            setWarning,
135
            displays,
136
            display_id,
137
            barcodes,
138
            batchRemove,
139
            clearForm,
140
        };
141
    },
142
    components: {
143
        ButtonSubmit,
144
    },
145
    name: "DisplaysBatchRemoveItems",
146
};
147
</script>
148
149
<style scoped>
150
label {
151
    margin: 0px 10px 0px 0px;
152
}
153
</style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/DisplaysResource.vue (+531 lines)
Line 0 Link Here
1
<template>
2
    <BaseResource
3
        :routeAction="routeAction"
4
        :instancedResource="this"
5
    ></BaseResource>
6
</template>
7
<script>
8
import { inject, onBeforeMount } from "vue";
9
import BaseResource from "../BaseResource.vue";
10
import { useBaseResource } from "../../composables/base-resource.js";
11
import { storeToRefs } from "pinia";
12
import { APIClient } from "../../fetch/api-client.js";
13
import { $__ } from "@koha-vue/i18n";
14
15
export default {
16
    props: {
17
        routeAction: String,
18
        embedded: { type: Boolean, default: false },
19
        embedEvent: Function,
20
    },
21
    setup(props) {
22
        const DisplayStore = inject("DisplayStore");
23
        const { displayReturnOverMapping, config } = storeToRefs(DisplayStore);
24
        const { setError } = inject("mainStore");
25
26
        const filters = [];
27
28
        const additionalToolbarButtons = resource => {
29
            return {
30
                list: [
31
                    {
32
                        to: { name: "DisplaysBatchAddItems" },
33
                        icon: "plus",
34
                        title: $__("Batch add items from list"),
35
                    },
36
                    {
37
                        to: { name: "DisplaysBatchRemoveItems" },
38
                        icon: "minus",
39
                        title: $__("Batch remove items from list"),
40
                    },
41
                ],
42
            };
43
        };
44
45
        const baseResource = useBaseResource({
46
            resourceName: "displays",
47
            nameAttr: "display_name",
48
            idAttr: "display_id",
49
            components: {
50
                show: "DisplaysShow",
51
                list: "DisplaysList",
52
                add: "DisplaysFormAdd",
53
                edit: "DisplaysFormAddEdit",
54
            },
55
            apiClient: APIClient.display.displays,
56
            i18n: {
57
                deleteConfirmationMessage: $__(
58
                    "Are you sure you want to remove this display?"
59
                ),
60
                deleteSuccessMessage: $__("Display %s deleted"),
61
                displayName: $__("Display"),
62
                editLabel: $__("Edit display #%s"),
63
                emptyListMessage: $__("There are no displays defined"),
64
                newLabel: $__("New display"),
65
            },
66
            table: {
67
                addFilters: true,
68
                resourceTableUrl:
69
                    APIClient.display.httpClientDisplays._baseURL + "",
70
                filters,
71
            },
72
            embedded: props.embedded,
73
            config,
74
            props,
75
            resourceAttrs: [
76
                {
77
                    name: "display_id",
78
                    label: $__("ID"),
79
                    type: "text",
80
                    hideIn: ["Form", "Show"],
81
                },
82
                {
83
                    name: "display_name",
84
                    label: $__("Name"),
85
                    type: "text",
86
                    required: true,
87
                },
88
                {
89
                    name: "display_branch",
90
                    label: $__("Home library"),
91
                    type: "relationshipSelect",
92
                    relationshipAPIClient:
93
                        APIClient.library.libraries,
94
                    relationshipOptionLabelAttr: "name",
95
                    relationshipRequiredKey: "library_id",
96
                    tableColumnDefinition: {
97
                        title: $__("Home library"),
98
                        data: "display_branch",
99
                        searchable: true,
100
                        orderable: true,
101
                        render: function (data, type, row, meta) {
102
                            if (row.home_library === null)
103
                                return (escape_str(
104
                                    ``
105
                                ));
106
                            else
107
                                return (escape_str(
108
                                    `${row["home_library"]["name"]}`
109
                                ));
110
                        },
111
                    },
112
                    showElement: {
113
                        type: "text",
114
                        value: "home_library.name"
115
                    },
116
                },
117
                {
118
                    name: "display_holding_branch",
119
                    label: $__("Holding library"),
120
                    type: "relationshipSelect",
121
                    relationshipAPIClient:
122
                        APIClient.library.libraries,
123
                    relationshipOptionLabelAttr: "name",
124
                    relationshipRequiredKey: "library_id",
125
                    tableColumnDefinition: {
126
                        title: $__("Holding library"),
127
                        data: "display_holding_branch",
128
                        searchable: true,
129
                        orderable: true,
130
                        render: function (data, type, row, meta) {
131
                            if (row.holding_library === null)
132
                                return (escape_str(
133
                                    ``
134
                                ));
135
                            else
136
                                return (escape_str(
137
                                    `${row["holding_library"]["name"]}`
138
                                ));
139
                        },
140
                    },
141
                    showElement: {
142
                        type: "text",
143
                        value: "holding_library.name"
144
                    },
145
                },
146
                {
147
                    name: "display_location",
148
                    label: $__("Shelving location"),
149
                    type: "select",
150
                    avCat: "av_loc",
151
                },
152
                {
153
                    name: "display_code",
154
                    label: $__("Collection code"),
155
                    type: "select",
156
                    avCat: "av_ccode",
157
                },
158
                {
159
                    name: "display_itype",
160
                    label: $__("Item type"),
161
                    type: "relationshipSelect",
162
                    relationshipAPIClient:
163
                        APIClient.item_type.item_types,
164
                    relationshipOptionLabelAttr: "description",
165
                    relationshipRequiredKey: "item_type_id",
166
                    tableColumnDefinition: {
167
                        title: $__("Item type"),
168
                        data: "display_itype",
169
                        searchable: true,
170
                        orderable: true,
171
                        render: function (data, type, row, meta) {
172
                            if (row.item_type === null)
173
                                return (escape_str(
174
                                    ``
175
                                ));
176
                            else
177
                                return (escape_str(
178
                                    `${row["item_type"]["description"]}`
179
                                ));
180
                        },
181
                    },
182
                    showElement: {
183
                        type: "text",
184
                        value: "item_type.description"
185
                    },
186
                },
187
                {
188
                    name: "display_return_over",
189
                    label: $__("Return behaviour"),
190
                    hint: $__("Remove items from display on checkin"),
191
                    type: "select",
192
                    selectLabel: "value",
193
                    requiredKey: "variable",
194
                    options: displayReturnOverMapping.value,
195
                    defaultValue: null,
196
                    required: true,
197
                    tableColumnDefinition: {
198
                        title: $__("Return behaviour"),
199
                        data: "display_return_over",
200
                        searchable: false,
201
                        orderable: true,
202
                        render: function (data, type, row, meta) {
203
                            let this_value = '';
204
205
                            DisplayStore.displayReturnOverMapping.forEach(mapping => {
206
                                if(mapping.variable == data) this_value = mapping.value;
207
                            });
208
209
                            return (escape_str(
210
                                `${this_value}`
211
                            ));
212
                        },
213
                    },
214
                },
215
                {
216
                    name: "start_date",
217
                    label: $__("Start of display"),
218
                    hint: $__("When the display becomes effective"),
219
                    type: "date",
220
                },
221
                {
222
                    name: "end_date",
223
                    label: $__("End of display"),
224
                    hint: $__("When the display's effectiveness ends"),
225
                    type: "date",
226
                },
227
                {
228
                    name: "display_days",
229
                    label: $__("Duration of display"),
230
                    hint: $__("Days in which items will remain on display"),
231
                    type: "number",
232
                    hideIn: ["List"],
233
                },
234
                {
235
                    name: "staff_note",
236
                    label: $__("Staff note"),
237
                    hint: $__("Notes only visible on staff client"),
238
                    type: "textarea",
239
                    hideIn: ["List"],
240
                },
241
                {
242
                    name: "public_note",
243
                    label: $__("Public note"),
244
                    hint: $__("Notes visible on both staff client and OPAC"),
245
                    type: "textarea",
246
                    hideIn: ["List"],
247
                },
248
                {
249
                    name: "enabled",
250
                    label: $__("Enabled"),
251
                    type: "boolean",
252
                    required: true,
253
                },
254
                {
255
                    name: "display_items",
256
                    type: "relationshipWidget",
257
                    showElement: {
258
                        type: "table",
259
                        columnData: "display_items",
260
                        hidden: display => !!display.display_items?.length,
261
                        columns: [
262
                            {
263
                                name: $__("Record number"),
264
                                value: "biblionumber",
265
                                link: {
266
                                    href: "/cgi-bin/koha/catalogue/detail.pl",
267
                                    params: {
268
                                        biblionumber: "biblionumber",
269
                                    },
270
                                },
271
                            },
272
                            {
273
                                name: $__("Internal item number"),
274
                                value: "itemnumber",
275
                                link: {
276
                                    href: "/cgi-bin/koha/catalogue/moredetail.pl",
277
                                    params: {
278
                                        itemnumber: "itemnumber",
279
                                    },
280
                                },
281
                            },
282
                            {
283
                                name: $__("Item barcode"),
284
                                value: "barcode",
285
                                link: {
286
                                    href: "/cgi-bin/koha/catalogue/moredetail.pl",
287
                                    params: {
288
                                        itemnumber: "itemnumber",
289
                                    },
290
                                },
291
                            },
292
                            {
293
                                name: $__("Date added"),
294
                                value: "date_added",
295
                                format: $date,
296
                            },
297
                            {
298
                                name: $__("Date to remove"),
299
                                value: "date_remove",
300
                                format: $date,
301
                            },
302
                        ],
303
                    },
304
                    group: $__("Display items"),
305
                    componentProps: {
306
                        resourceRelationships: {
307
                            resourceProperty: "display_items",
308
                        },
309
                        relationshipI18n: {
310
                            nameUpperCase: __("Display item"),
311
                            removeThisMessage: __(
312
                                "Remove this display item"
313
                            ),
314
                            addNewMessage: __("Add new display item"),
315
                            noneCreatedYetMessage: __(
316
                                "There are no display items created yet"
317
                            ),
318
                        },
319
                        newRelationshipDefaultAttrs: {
320
                            type: "object",
321
                            value: {
322
                                biblionumber: null,
323
                                itemnumber: null,
324
                                barcode: null,
325
                                date_added: null,
326
                                date_remove: null,
327
                            },
328
                        },
329
                    },
330
                    relationshipFields: [
331
                        {
332
                            name: "barcode",
333
                            type: "text",
334
                            label: $__("Item barcode"),
335
                            required: true,
336
                            indexRequired: true,
337
                        },
338
                        {
339
                            name: "date_added",
340
                            type: "date",
341
                            label: $__("Date added"),
342
                            required: false,
343
                            indexRequired: true,
344
                        },
345
                        {
346
                            name: "date_remove",
347
                            type: "date",
348
                            label: $__("Date to remove"),
349
                            required: false,
350
                            indexRequired: true,
351
                        },
352
                    ],
353
                    hideIn: ["List"],
354
                },
355
            ],
356
            additionalToolbarButtons,
357
            moduleStore: "DisplayStore",
358
            props: props,
359
        });
360
361
        const tableOptions = {
362
            url: "/api/v1/displays/",
363
            options: {
364
                embed: "home_library,holding_library,item_type,+strings",
365
            },
366
            add_filters: true,
367
            actions: {
368
                0: ["show"],
369
                1: ["show"],
370
                "-1": ["edit", "delete"]
371
            },
372
        };
373
374
        const getItemFromId = (async id => {
375
            const itemsApiClient = APIClient.item.items;
376
            let item = undefined;
377
378
            await itemsApiClient.get(id)
379
            .then(data => {
380
                item = data;
381
            })
382
            .catch(error => {
383
                console.error(error);
384
            });
385
386
            return item;
387
        });
388
389
        const getItemFromExternalId = (async external_id => {
390
            const itemsApiClient = APIClient.item.items;
391
            let item = undefined;
392
393
            await itemsApiClient.getByExternalId(external_id)
394
            .then(data => {
395
                if (data.length == 1)
396
                    item = data[0];
397
            })
398
            .catch(error => {
399
                console.error(error);
400
            });
401
402
            return item;
403
        });
404
405
        const checkForm = (async display => {
406
            let errors = [];
407
408
            let display_items = display.display_items;
409
            // Do not use di.display_item.name here! Its name is not the one linked with di.display_item_id
410
            // At this point di.display_item is meaningless, form/template only modified di.display_item_id
411
            const display_item_ids = display_items.map(di => di.display_item_id);
412
            const duplicate_display_item_ids = display_item_ids.filter(
413
                (id, i) => display_item_ids.indexOf(id) !== i
414
            );
415
416
            if (duplicate_display_item_ids.length) {
417
                errors.push($__("A display item is used several times"));
418
            }
419
420
            for await (const display_item of display_items) {
421
                const item = await getItemFromExternalId(display_item.barcode);
422
                
423
                if (item == undefined || item.item_id === undefined || item.external_id !== display_item.barcode)
424
                    errors.push($__("The barcode entered does not match an item"));
425
            }
426
427
            baseResource.setWarning(errors.join("<br>"));
428
            return !errors.length;
429
        });
430
        const onFormSave = (async (e, displayToSave) => {
431
            e.preventDefault();
432
433
            const display = JSON.parse(JSON.stringify(displayToSave));
434
            const displayId = display.display_id;
435
            const epoch = new Date();
436
437
            if (!await checkForm(display)) {
438
                return false;
439
            }
440
441
            delete display.display_id;
442
            delete display.item_type;
443
            delete display.home_library;
444
            delete display.holding_library;
445
            delete display._strings;
446
447
            display.display_items = display.display_items.map(
448
                ({ display_item_id, ...keepAttrs }) =>
449
                    keepAttrs
450
            );
451
452
            let display_items = display.display_items;
453
            delete display.display_items;
454
            display.display_items = [];
455
456
            for await (const display_item of display_items) {
457
                const item = await getItemFromExternalId(display_item.barcode);
458
459
                delete display_item.barcode;
460
461
                display_item.biblionumber = item.biblio_id;
462
                display_item.itemnumber = item.item_id;
463
464
                await display.display_items.push(display_item);
465
            }
466
467
            if (display.start_date == null) display.start_date = epoch.toISOString().substr(0, 10);
468
            if (display.end_date == null && display.display_days != undefined) {
469
                let calculated_date = epoch;
470
                calculated_date.setDate(epoch.getDate() + Number(display.display_days));
471
472
                display.end_date = calculated_date.toISOString().substr(0, 10);
473
            }
474
            if (display.display_days == "") display.display_days = null;
475
            if (display.public_note == "") display.public_note = null;
476
            if (display.staff_note == "") display.staff_note = null;
477
478
            if (displayId) {
479
                baseResource.apiClient
480
                    .update(display, displayId)
481
                    .then(
482
                        success => {
483
                            baseResource.setMessage($__("Display updated"));
484
                            baseResource.router.push({ name: "DisplaysList" });
485
                        },
486
                        error => {}
487
                );
488
            } else {
489
                baseResource.apiClient.create(display).then(
490
                    success => {
491
                        baseResource.setMessage($__("Display created"));
492
                        baseResource.router.push({ name: "DisplaysList" });
493
                    },
494
                    error => {}
495
                );
496
            }
497
        });
498
        const afterResourceFetch = ((componentData, resource, caller) => {
499
            if(caller === "show" || caller === "form") {
500
                resource.display_items.forEach((display_item, idx) => {
501
                    getItemFromId(display_item.itemnumber)
502
                    .then(item => {
503
                        componentData.resource.value.display_items[idx] = {
504
                            barcode: item.external_id,
505
                            ...display_item,
506
                        };
507
                    })
508
                    .catch(error => {
509
                        console.error(error);
510
                    });
511
                });
512
            }
513
        });
514
515
        onBeforeMount(() => {});
516
517
        return {
518
            ...baseResource,
519
            tableOptions,
520
            checkForm,
521
            onFormSave,
522
            afterResourceFetch,
523
        };
524
    },
525
    emits: ["select-resource"],
526
    name: "DisplaysResource",
527
    components: {
528
        BaseResource,
529
    },
530
};
531
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/Home.vue (+9 lines)
Line 0 Link Here
1
<template>
2
    <div id="home"></div>
3
</template>
4
5
<script>
6
export default {
7
    components: {},
8
};
9
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/Main.vue (+115 lines)
Line 0 Link Here
1
<template>
2
    <div v-if="initialized && config.settings.enabled == 1">
3
        <div id="sub-header">
4
            <Breadcrumbs />
5
            <Help />
6
        </div>
7
        <div class="main container-fluid">
8
            <div class="row">
9
                <div class="col-md-10 order-md-2 order-sm-1">
10
                    <main>
11
                        <Dialog />
12
                        <router-view />
13
                    </main>
14
                </div>
15
16
                <div class="col-md-2 order-sm-2 order-md-1">
17
                    <LeftMenu :title="$__('Displays')"></LeftMenu>
18
                </div>
19
            </div>
20
        </div>
21
    </div>
22
    <div class="main container-fluid" v-else>
23
        <Dialog />
24
    </div>
25
</template>
26
27
<script>
28
import { inject, onBeforeMount, ref } from "vue";
29
import Breadcrumbs from "../Breadcrumbs.vue";
30
import Help from "../Help.vue";
31
import LeftMenu from "../LeftMenu.vue";
32
import Dialog from "../Dialog.vue";
33
import { APIClient } from "../../fetch/api-client.js";
34
import "vue-select/dist/vue-select.css";
35
import { storeToRefs } from "pinia";
36
import { $__ } from "@koha-vue/i18n";
37
38
export default {
39
    setup() {
40
        const mainStore = inject("mainStore");
41
42
        const { loading, loaded, setError } = mainStore;
43
44
        const DisplayStore = inject("DisplayStore");
45
46
        const { config, authorisedValues } = storeToRefs(DisplayStore);
47
        const { loadAuthorisedValues } = DisplayStore;
48
49
        const initialized = ref(false);
50
51
        onBeforeMount(() => {
52
            loading();
53
54
            const client = APIClient.display;
55
            client.config.get().then(result => {
56
                config.value = result;
57
                if (config.value.settings.enabled != 1) {
58
                    loaded();
59
                    return setError(
60
                        $__(
61
                            'The displays module is disabled, turn on <a href="/cgi-bin/koha/admin/preferences.pl?tab=&op=search&searchfield=UseDisplayModule">UseDisplayModule</a> to use it'
62
                        ),
63
                        false
64
                    );
65
                }
66
67
                DisplayStore.displayReturnOverMapping.push({
68
                    "variable": "yes - any library",
69
                    "value": $__('Yes, any library'),
70
                });
71
                DisplayStore.displayReturnOverMapping.push({
72
                    "variable": "yes - except at home library",
73
                    "value": $__('Yes, except at home library'),
74
                });
75
                DisplayStore.displayReturnOverMapping.push({
76
                    "variable": "no",
77
                    "value": $__('No'),
78
                });
79
80
                loadAuthorisedValues(
81
                    authorisedValues.value,
82
                    DisplayStore
83
                ).then(() => {
84
                    loaded();
85
                    initialized.value = true;
86
                });
87
88
            });
89
        });
90
91
        return {
92
            loading,
93
            loaded,
94
            config,
95
            setError,
96
            DisplayStore,
97
            initialized,
98
        };
99
    },
100
    components: {
101
        Breadcrumbs,
102
        Dialog,
103
        Help,
104
        LeftMenu,
105
    },
106
};
107
</script>
108
109
<style>
110
form .v-select {
111
    display: inline-block;
112
    background-color: white;
113
    width: 30%;
114
}
115
</style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/api-client.js (+8 lines)
Lines 6-12 import AcquisitionAPIClient from "@fetch/acquisition-api-client"; Link Here
6
import AdditionalFieldsAPIClient from "@fetch/additional-fields-api-client";
6
import AdditionalFieldsAPIClient from "@fetch/additional-fields-api-client";
7
import AVAPIClient from "@fetch/authorised-values-api-client";
7
import AVAPIClient from "@fetch/authorised-values-api-client";
8
import CashAPIClient from "@fetch/cash-api-client";
8
import CashAPIClient from "@fetch/cash-api-client";
9
import BiblioAPIClient from '@fetch/biblio-api-client.js';
10
import DisplayAPIClient from "@fetch/display-api-client";
9
import ItemAPIClient from "@fetch/item-api-client";
11
import ItemAPIClient from "@fetch/item-api-client";
12
import ItemTypeAPIClient from '@fetch/item-type-api-client.js';
13
import LibraryAPIClient from "@fetch/library-api-client";
10
import RecordSourcesAPIClient from "@fetch/record-sources-api-client";
14
import RecordSourcesAPIClient from "@fetch/record-sources-api-client";
11
import SysprefAPIClient from "@fetch/system-preferences-api-client";
15
import SysprefAPIClient from "@fetch/system-preferences-api-client";
12
import SIP2APIClient from "@fetch/sip2-api-client";
16
import SIP2APIClient from "@fetch/sip2-api-client";
Lines 19-25 export const APIClient = { Link Here
19
    additional_fields: new AdditionalFieldsAPIClient(HttpClient),
23
    additional_fields: new AdditionalFieldsAPIClient(HttpClient),
20
    authorised_values: new AVAPIClient(HttpClient),
24
    authorised_values: new AVAPIClient(HttpClient),
21
    cash: new CashAPIClient(HttpClient),
25
    cash: new CashAPIClient(HttpClient),
26
    biblio: new BiblioAPIClient(HttpClient),
27
    display: new DisplayAPIClient(HttpClient),
22
    item: new ItemAPIClient(HttpClient),
28
    item: new ItemAPIClient(HttpClient),
29
    item_type: new ItemTypeAPIClient(HttpClient),
30
    library: new LibraryAPIClient(HttpClient),
23
    sysprefs: new SysprefAPIClient(HttpClient),
31
    sysprefs: new SysprefAPIClient(HttpClient),
24
    sip2: new SIP2APIClient(HttpClient),
32
    sip2: new SIP2APIClient(HttpClient),
25
    preservation: new PreservationAPIClient(HttpClient),
33
    preservation: new PreservationAPIClient(HttpClient),
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/http-client.js (+6 lines)
Lines 150-155 class HttpClient { Link Here
150
    }
150
    }
151
151
152
    delete(params = {}) {
152
    delete(params = {}) {
153
        const body = params.body
154
            ? typeof params.body === "string"
155
                ? params.body
156
                : JSON.stringify(params.body)
157
            : undefined;
153
        let csrf_token = { "CSRF-TOKEN": this.csrf_token };
158
        let csrf_token = { "CSRF-TOKEN": this.csrf_token };
154
        let headers = { ...csrf_token, ...params.headers };
159
        let headers = { ...csrf_token, ...params.headers };
155
        return this._fetchJSON(
160
        return this._fetchJSON(
Lines 158-163 class HttpClient { Link Here
158
            {
163
            {
159
                parseResponse: false,
164
                parseResponse: false,
160
                ...params.options,
165
                ...params.options,
166
                body,
161
                method: "DELETE",
167
                method: "DELETE",
162
            },
168
            },
163
            params.return_response ?? true,
169
            params.return_response ?? true,
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/display.ts (+72 lines)
Line 0 Link Here
1
import { createApp } from "vue";
2
import { createWebHistory, createRouter } from "vue-router";
3
import { createPinia } from "pinia";
4
5
import { library } from "@fortawesome/fontawesome-svg-core";
6
import {
7
    faPlus,
8
    faMinus,
9
    faPencil,
10
    faTrash,
11
    faSpinner,
12
    faClose,
13
    faPaperPlane,
14
    faInbox,
15
} from "@fortawesome/free-solid-svg-icons";
16
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
17
import vSelect from "vue-select";
18
19
library.add(
20
    faPlus,
21
    faMinus,
22
    faPencil,
23
    faTrash,
24
    faSpinner,
25
    faClose,
26
    faPaperPlane,
27
    faInbox
28
);
29
30
import App from "../components/Display/Main.vue";
31
32
import { routes as routesDef } from "../routes/display";
33
34
import { useMainStore } from "../stores/main";
35
import { useDisplayStore } from "../stores/display";
36
import { useNavigationStore } from "../stores/navigation";
37
import i18n from "@koha-vue/i18n";
38
39
const pinia = createPinia();
40
41
const mainStore = useMainStore(pinia);
42
const navigationStore = useNavigationStore(pinia);
43
const routes = navigationStore.setRoutes(routesDef);
44
45
const router = createRouter({
46
    history: createWebHistory(),
47
    linkActiveClass: "current",
48
    routes,
49
});
50
51
const app = createApp(App);
52
53
const rootComponent = app
54
    .use(i18n)
55
    .use(pinia)
56
    .use(router)
57
    .component("font-awesome-icon", FontAwesomeIcon)
58
    .component("v-select", vSelect);
59
60
app.config.unwrapInjectedRef = true;
61
app.provide("mainStore", mainStore);
62
app.provide("navigationStore", navigationStore);
63
const DisplayStore = useDisplayStore(pinia);
64
app.provide("DisplayStore", DisplayStore);
65
66
app.mount("#display");
67
68
const { removeMessages } = mainStore;
69
router.beforeEach((to, from) => {
70
    navigationStore.$patch({ current: to.matched, params: to.params || {} });
71
    removeMessages(); // This will actually flag the messages as displayed already
72
});
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/display.js (+82 lines)
Line 0 Link Here
1
import { markRaw } from "vue";
2
3
import Home from "../components/Display/Home.vue";
4
import DisplaysBatchAddItems from "../components/Display/DisplaysBatchAddItems.vue";
5
import DisplaysBatchRemoveItems from "../components/Display/DisplaysBatchRemoveItems.vue";
6
7
import ResourceWrapper from "../components/ResourceWrapper.vue";
8
9
import { $__ } from "@koha-vue/i18n";
10
11
export const routes = [
12
    {
13
        path: "/cgi-bin/koha/display/display-home.pl",
14
        is_default: true,
15
        is_base: true,
16
        title: $__("Displays"),
17
        children: [
18
            {
19
                path: "",
20
                name: "Home",
21
                component: markRaw(Home),
22
                redirect: "/cgi-bin/koha/display/displays",
23
                is_navigation_item: false,
24
            },
25
            {
26
                path: "/cgi-bin/koha/display/displays",
27
                title: $__("Displays"),
28
                icon: "fa-solid fa-image-portrait",
29
                is_end_node: true,
30
                resource: "Display/DisplaysResource.vue",
31
                children: [
32
                    {
33
                        path: "",
34
                        name: "DisplaysList",
35
                        component: markRaw(ResourceWrapper),
36
                    },
37
                    {
38
                        path: ":display_id",
39
                        name: "DisplaysShow",
40
                        component: markRaw(ResourceWrapper),
41
                        title: "{display_name}",
42
                    },
43
                    {
44
                        path: "add",
45
                        name: "DisplaysFormAdd",
46
                        component: markRaw(ResourceWrapper),
47
                        title: $__("Add display"),
48
                    },
49
                    {
50
                        path: "edit/:display_id",
51
                        name: "DisplaysFormAddEdit",
52
                        component: markRaw(ResourceWrapper),
53
                        title: "{display_name}",
54
                        breadcrumbFormat: ({ match, params, query }) => {
55
                            match.name = "DisplaysShow";
56
                            return match;
57
                        },
58
                        additionalBreadcrumbs: [
59
                            { title: $__("Modify display"), disabled: true },
60
                        ],
61
                    },
62
                    {
63
                        path: "batch-add",
64
                        name: "DisplaysBatchAddItems",
65
                        component: markRaw(
66
                            DisplaysBatchAddItems
67
                        ),
68
                        title: $__("Batch add items from list"),
69
                    },
70
                    {
71
                        path: "batch-remove",
72
                        name: "DisplaysBatchRemoveItems",
73
                        component: markRaw(
74
                            DisplaysBatchRemoveItems
75
                        ),
76
                        title: $__("Batch remove items from list"),
77
                    },
78
                ],
79
            },
80
        ],
81
    },
82
];
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/stores/display.js (-1 / +27 lines)
Line 0 Link Here
0
- 
1
import { defineStore } from "pinia";
2
import { reactive, toRefs } from "vue";
3
import { withAuthorisedValueActions } from "../composables/authorisedValues";
4
5
export const useDisplayStore = defineStore("display", () => {
6
    const store = reactive({
7
        displayReturnOverMapping: [],
8
        config: {
9
            settings: {
10
                enabled: 0,
11
            },
12
        },
13
        authorisedValues: {
14
            av_loc: "LOC",
15
            av_ccode: "CCODE",
16
        },
17
    });
18
19
    const sharedActions = {
20
        ...withAuthorisedValueActions(store),
21
    };
22
23
    return {
24
        ...toRefs(store),
25
        ...sharedActions,
26
    };
27
});

Return to bug 14962