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

(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/DisplaysBatchAddItems.vue (+191 lines)
Line 0 Link Here
1
<template>
2
    <h2>{{ $__("Batch add items from file") }}</h2>
3
    <div class="page-section" id="files">
4
        <form @submit="batchAdd($event)" class="file_upload">
5
            <fieldset class="rows" id="display_list">
6
                <h3>{{ $__("Select file for upload") }}:</h3>
7
                <ol>
8
                    <li>
9
                        <label for="import_file">{{ $__("File") }}:</label>
10
                        <input
11
                            type="file"
12
                            @change="selectFile($event)"
13
                            :id="`import_file`"
14
                            :name="`import_file`"
15
                            required="required"
16
                            ref="fileLoader"
17
                        />
18
                        <span class="required">{{ $__("Required") }}</span>
19
                    </li>
20
                    <li>
21
                        <label for="display_id"
22
                            >{{ $__("To the following display") }}:</label
23
                        >
24
                        <v-select
25
                            id="display_id"
26
                            v-model="display_id"
27
                            label="display_name"
28
                            :reduce="d => d.display_id"
29
                            :options="displays"
30
                            :clearable="false"
31
                            :required="!display_id"
32
                        >
33
                            <template #search="{ attributes, events }">
34
                                <input
35
                                    :required="!display_id"
36
                                    class="vs__search"
37
                                    v-bind="attributes"
38
                                    v-on="events"
39
                                />
40
                            </template>
41
                        </v-select>
42
                        <span class="required">{{ $__("Required") }}</span>
43
                    </li>
44
                    <li>
45
                        <label for="date_remove"
46
                            >{{ $__("To remove on this date") }}:</label
47
                        >
48
                        <FlatPickrWrapper
49
                            :id="`date_remove`"
50
                            :name="`date_remove`"
51
                            v-model="date_remove"
52
                            label="date_remove"
53
                        />
54
                    </li>
55
                </ol>
56
            </fieldset>
57
            <fieldset class="action">
58
                <ButtonSubmit />
59
                <a @click="clearForm()" role="button" class="cancel">{{
60
                    $__("Clear form")
61
                }}</a>
62
            </fieldset>
63
        </form>
64
    </div>
65
</template>
66
67
<script>
68
import { ref, inject, useTemplateRef, onBeforeMount } from "vue";
69
import ButtonSubmit from "../ButtonSubmit.vue";
70
import FlatPickrWrapper from "@koha-vue/components/FlatPickrWrapper.vue";
71
import { storeToRefs } from "pinia";
72
import { APIClient } from "../../fetch/api-client.js";
73
import { $__ } from "@koha-vue/i18n";
74
75
export default {
76
    props: {
77
        routeAction: String,
78
        embedded: { type: Boolean, default: false },
79
        embedEvent: Function,
80
    },
81
    setup(props) {
82
        const DisplayStore = inject("DisplayStore");
83
        const { config } = storeToRefs(DisplayStore);
84
        const { setMessage, setWarning, setError } = inject("mainStore");
85
        const fileLoader = useTemplateRef("fileLoader");
86
87
        const displays = ref([]);
88
        const display_id = ref(null);
89
        const file = ref({
90
            filename: null,
91
            file_content: null,
92
        });
93
        const item_ids = ref(null);
94
        const date_remove = ref(null);
95
96
        const selectFile = event => {
97
            let files = event.target.files;
98
            if (!files)
99
                return;
100
            let newFile = files[0];
101
            const reader = new FileReader();
102
            reader.onload = event => loadFile(newFile.name, event.target.result);
103
            reader.readAsText(newFile);
104
        };
105
        const loadFile = (filename, content) => {
106
            file.value.filename = filename;
107
            file.value.file_content = content;
108
            file.value.file_as_array = content.split("\n") || undefined;
109
        };
110
        const batchAdd = event => {
111
            event.preventDefault();
112
113
            item_ids.value = file.value.file_as_array
114
            .map(n => Number(n))
115
            .filter(n => {
116
                if (n == '')
117
                    return false;
118
119
                return true;
120
            });
121
122
            const client = APIClient.display;
123
            const importData = {
124
                display_id: display_id.value,
125
                item_ids: item_ids.value,
126
            };
127
            if (date_remove.value != null)
128
                importData.date_remove = date_remove.value;
129
130
            client.displayItems.batchAdd(importData).then(
131
                success => {
132
                    if (success.job_id)
133
                        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);
134
135
                    if (!success.job_id)
136
                        setWarning($__('Batch job failed to queue. Please check your list, and try again.'), true);
137
                },
138
                error => {
139
                    setError($__('Internal Server Error. Please check the browser console for diagnostic information.'), true);
140
                    console.error(error);
141
                },
142
            );
143
            clearForm();
144
        };
145
        const clearForm = () => {
146
            display_id.value = null;
147
            file.value = {
148
                filename: null,
149
                file_content: null,
150
                file_as_array: null,
151
            };
152
            fileLoader.files = null;
153
            fileLoader.value = null;
154
            date_remove.value = null;
155
        };
156
157
        onBeforeMount(() => {
158
            const client = APIClient.display;
159
            client.displays.getAll().then(
160
                result => {
161
                    displays.value = result;
162
                },
163
                error => {}
164
            );
165
        });
166
        return {
167
            setMessage,
168
            setWarning,
169
            fileLoader,
170
            displays,
171
            display_id,
172
            file,
173
            selectFile,
174
            date_remove,
175
            batchAdd,
176
            clearForm,
177
        };
178
    },
179
    components: {
180
        ButtonSubmit,
181
        FlatPickrWrapper,
182
    },
183
    name: "DisplaysBatchAddItems",
184
};
185
</script>
186
187
<style scoped>
188
label {
189
    margin: 0px 10px 0px 0px;
190
}
191
</style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/DisplaysBatchRemoveItems.vue (+169 lines)
Line 0 Link Here
1
<template>
2
    <h2>{{ $__("Batch remove items from file") }}</h2>
3
    <div class="page-section" id="files">
4
        <form @submit="batchRemove($event)" class="file_upload">
5
            <fieldset class="rows" id="display_list">
6
                <h3>{{ $__("Select file for upload") }}:</h3>
7
                <ol>
8
                    <li>
9
                        <label for="import_file">{{ $__("File") }}:</label>
10
                        <input
11
                            type="file"
12
                            @change="selectFile($event)"
13
                            :id="`import_file`"
14
                            :name="`import_file`"
15
                            required="required"
16
                            ref="fileLoader"
17
                        />
18
                        <span class="required">{{ $__("Required") }}</span>
19
                    </li>
20
                    <li>
21
                        <label for="display_id"
22
                            >{{ $__("To the following display") }}:</label
23
                        >
24
                        <v-select
25
                            id="display_id"
26
                            v-model="display_id"
27
                            label="display_name"
28
                            :reduce="d => d.display_id"
29
                            :options="displays"
30
                            :clearable="false"
31
                            :required="!display_id"
32
                        >
33
                            <template #search="{ attributes, events }">
34
                                <input
35
                                    :required="!display_id"
36
                                    class="vs__search"
37
                                    v-bind="attributes"
38
                                    v-on="events"
39
                                />
40
                            </template>
41
                        </v-select>
42
                        <span class="required">{{ $__("Required") }}</span>
43
                    </li>
44
                </ol>
45
            </fieldset>
46
            <fieldset class="action">
47
                <ButtonSubmit />
48
                <a @click="clearForm()" role="button" class="cancel">{{
49
                    $__("Clear form")
50
                }}</a>
51
            </fieldset>
52
        </form>
53
    </div>
54
</template>
55
56
<script>
57
import { ref, inject, useTemplateRef, onBeforeMount } from "vue";
58
import ButtonSubmit from "../ButtonSubmit.vue";
59
import { storeToRefs } from "pinia";
60
import { APIClient } from "../../fetch/api-client.js";
61
import { $__ } from "@koha-vue/i18n";
62
63
export default {
64
    props: {
65
        routeAction: String,
66
        embedded: { type: Boolean, default: false },
67
        embedEvent: Function,
68
    },
69
    setup(props) {
70
        const DisplayStore = inject("DisplayStore");
71
        const { config } = storeToRefs(DisplayStore);
72
        const { setMessage, setWarning, setError } = inject("mainStore");
73
        const fileLoader = useTemplateRef("fileLoader");
74
75
        const displays = ref([]);
76
        const display_id = ref(null);
77
        const file = ref({
78
            filename: null,
79
            file_content: null,
80
        });
81
        const item_ids = ref(null);
82
83
        const selectFile = event => {
84
            let files = event.target.files;
85
            if (!files)
86
                return;
87
            let newFile = files[0];
88
            const reader = new FileReader();
89
            reader.onload = event => loadFile(newFile.name, event.target.result);
90
            reader.readAsText(newFile);
91
        };
92
        const loadFile = (filename, content) => {
93
            file.value.filename = filename;
94
            file.value.file_content = content;
95
            file.value.file_as_array = content.split("\n") || undefined;
96
        };
97
        const batchRemove = event => {
98
            event.preventDefault();
99
100
            item_ids.value = file.value.file_as_array
101
            .map(n => Number(n))
102
            .filter(n => {
103
                if (n == '')
104
                    return false;
105
106
                return true;
107
            });
108
109
            const client = APIClient.display;
110
            const importData = {
111
                display_id: display_id.value,
112
                item_ids: item_ids.value,
113
            };
114
115
            client.displayItems.batchDelete(importData).then(
116
                success => {
117
                    setMessage(`${$__('Batch job successfully queued.')} <a href="/cgi-bin/koha/admin/background_jobs.pl" target="_blank">${$__('Click here to view all jobs')}</a>`, true);
118
                },
119
                error => {
120
                    setError($__('Internal Server Error. Please check the browser console for diagnostic information.'), true);
121
                    console.error(error);
122
                },
123
            );
124
            clearForm();
125
        };
126
        const clearForm = () => {
127
            display_id.value = null;
128
            file.value = {
129
                filename: null,
130
                file_content: null,
131
                file_as_array: null,
132
            };
133
            fileLoader.files = null;
134
            fileLoader.value = null;
135
        };
136
137
        onBeforeMount(() => {
138
            const client = APIClient.display;
139
            client.displays.getAll().then(
140
                result => {
141
                    displays.value = result;
142
                },
143
                error => {}
144
            );
145
        });
146
        return {
147
            setMessage,
148
            setWarning,
149
            fileLoader,
150
            displays,
151
            display_id,
152
            file,
153
            selectFile,
154
            batchRemove,
155
            clearForm,
156
        };
157
    },
158
    components: {
159
        ButtonSubmit,
160
    },
161
    name: "DisplaysBatchRemoveItems",
162
};
163
</script>
164
165
<style scoped>
166
label {
167
    margin: 0px 10px 0px 0px;
168
}
169
</style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Display/DisplaysResource.vue (+501 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 file"),
35
                    },
36
                    {
37
                        to: { name: "DisplaysBatchRemoveItems" },
38
                        icon: "minus",
39
                        title: $__("Batch remove items from file"),
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.library === null)
103
                                return (escape_str(
104
                                    ``
105
                                ));
106
                            else
107
                                return (escape_str(
108
                                    `${row["library"]["name"]}`
109
                                ));
110
                        },
111
                    },
112
                    showElement: {
113
                        type: "text",
114
                        value: "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.library === null)
132
                                return (escape_str(
133
                                    ``
134
                                ));
135
                            else
136
                                return (escape_str(
137
                                    `${row["library"]["name"]}`
138
                                ));
139
                        },
140
                    },
141
                    showElement: {
142
                        type: "text",
143
                        value: "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
                    type: "select",
191
                    selectLabel: "value",
192
                    requiredKey: "variable",
193
                    options: displayReturnOverMapping.value,
194
                    defaultValue: null,
195
                    required: true,
196
                    tableColumnDefinition: {
197
                        title: $__("Return behaviour"),
198
                        data: "display_return_over",
199
                        searchable: false,
200
                        orderable: true,
201
                        render: function (data, type, row, meta) {
202
                            let this_value = '';
203
204
                            DisplayStore.displayReturnOverMapping.forEach(mapping => {
205
                                if(mapping.variable == data) this_value = mapping.value;
206
                            });
207
208
                            return (escape_str(
209
                                `${this_value}`
210
                            ));
211
                        },
212
                    },
213
                },
214
                {
215
                    name: "start_date",
216
                    label: $__("Start of display"),
217
                    type: "date",
218
                },
219
                {
220
                    name: "end_date",
221
                    label: $__("End of display"),
222
                    type: "date",
223
                },
224
                {
225
                    name: "display_days",
226
                    label: $__("Duration of display"),
227
                    type: "number",
228
                    hideIn: ["List"],
229
                },
230
                {
231
                    name: "staff_note",
232
                    label: $__("Staff note"),
233
                    type: "textarea",
234
                    hideIn: ["List"],
235
                },
236
                {
237
                    name: "public_note",
238
                    label: $__("Public note"),
239
                    type: "textarea",
240
                    hideIn: ["List"],
241
                },
242
                {
243
                    name: "enabled",
244
                    label: $__("Enabled"),
245
                    type: "boolean",
246
                    required: true,
247
                },
248
                {
249
                    name: "display_items",
250
                    type: "relationshipWidget",
251
                    showElement: {
252
                        type: "table",
253
                        columnData: "display_items",
254
                        hidden: display => !!display.display_items?.length,
255
                        columns: [
256
                            {
257
                                name: $__("Record number"),
258
                                value: "biblionumber",
259
                            },
260
                            {
261
                                name: $__("Internal item number"),
262
                                value: "itemnumber",
263
                            },
264
                            {
265
                                name: $__("Item barcode"),
266
                                value: "barcode",
267
                            },
268
                            {
269
                                name: $__("Date added"),
270
                                value: "date_added",
271
                                format: $date,
272
                            },
273
                            {
274
                                name: $__("Date to remove"),
275
                                value: "date_remove",
276
                                format: $date,
277
                            },
278
                        ],
279
                    },
280
                    group: $__("Display items"),
281
                    componentProps: {
282
                        resourceRelationships: {
283
                            resourceProperty: "display_items",
284
                        },
285
                        relationshipStrings: {
286
                            nameLowerCase: $__("display item"),
287
                            nameUpperCase: $__("Display item"),
288
                            namePlural: $__("display items"),
289
                        },
290
                        newRelationshipDefaultAttrs: {
291
                            type: "object",
292
                            value: {
293
                                biblionumber: null,
294
                                itemnumber: null,
295
                                barcode: null,
296
                                date_added: null,
297
                                date_remove: null,
298
                            },
299
                        },
300
                    },
301
                    relationshipFields: [
302
                        {
303
                            name: "barcode",
304
                            type: "number",
305
                            label: $__("Item barcode"),
306
                            required: true,
307
                            indexRequired: true,
308
                        },
309
                        {
310
                            name: "date_added",
311
                            type: "date",
312
                            label: $__("Date added"),
313
                            required: false,
314
                            indexRequired: true,
315
                        },
316
                        {
317
                            name: "date_remove",
318
                            type: "date",
319
                            label: $__("Date to remove"),
320
                            required: false,
321
                            indexRequired: true,
322
                        },
323
                    ],
324
                    hideIn: ["List"],
325
                },
326
            ],
327
            additionalToolbarButtons,
328
            moduleStore: "DisplayStore",
329
            props: props,
330
        });
331
332
        const tableOptions = {
333
            url: "/api/v1/displays",
334
            options: {
335
                embed: "library,item_type,+strings",
336
            },
337
            add_filters: true,
338
            actions: {
339
                0: ["show"],
340
                1: ["show"],
341
                "-1": ["edit", "delete"]
342
            },
343
        };
344
345
        const getItemFromId = (async id => {
346
            const itemsApiClient = APIClient.item.items;
347
            let item = undefined;
348
349
            await itemsApiClient.get(id)
350
            .then(data => {
351
                item = data;
352
            })
353
            .catch(error => {
354
                console.error(error);
355
            });
356
357
            return item;
358
        });
359
360
        const getItemFromExternalId = (async external_id => {
361
            const itemsApiClient = APIClient.item.items;
362
            let item = undefined;
363
364
            await itemsApiClient.getByExternalId(external_id)
365
            .then(data => {
366
                if (data.length == 1)
367
                    item = data[0];
368
            })
369
            .catch(error => {
370
                console.error(error);
371
            });
372
373
            return item;
374
        });
375
376
        const checkForm = (async display => {
377
            let errors = [];
378
379
            let display_items = display.display_items;
380
            // Do not use di.display_item.name here! Its name is not the one linked with di.display_item_id
381
            // At this point di.display_item is meaningless, form/template only modified di.display_item_id
382
            const display_item_ids = display_items.map(di => di.display_item_id);
383
            const duplicate_display_item_ids = display_item_ids.filter(
384
                (id, i) => display_item_ids.indexOf(id) !== i
385
            );
386
387
            if (duplicate_display_item_ids.length) {
388
                errors.push($__("A display item is used several times"));
389
            }
390
391
            for await (const display_item of display_items) {
392
                const item = await getItemFromExternalId(display_item.barcode);
393
                
394
                if (item == undefined || item.item_id === undefined || item.external_id !== display_item.barcode)
395
                    errors.push($__("The barcode entered does not match an item"));
396
            }
397
398
            baseResource.setWarning(errors.join("<br>"));
399
            return !errors.length;
400
        });
401
        const onFormSave = (async (e, displayToSave) => {
402
            e.preventDefault();
403
404
            const display = JSON.parse(JSON.stringify(displayToSave));
405
            const displayId = display.display_id;
406
            const epoch = new Date();
407
408
            if (!await checkForm(display)) {
409
                return false;
410
            }
411
412
            delete display.display_id;
413
            delete display.item_type;
414
            delete display.library;
415
            delete display._strings;
416
417
            display.display_items = display.display_items.map(
418
                ({ display_item_id, ...keepAttrs }) =>
419
                    keepAttrs
420
            );
421
422
            let display_items = display.display_items;
423
            delete display.display_items;
424
            display.display_items = [];
425
426
            for await (const display_item of display_items) {
427
                const item = await getItemFromExternalId(display_item.barcode);
428
429
                delete display_item.barcode;
430
431
                display_item.biblionumber = item.biblio_id;
432
                display_item.itemnumber = item.item_id;
433
434
                await display.display_items.push(display_item);
435
            }
436
437
            if (display.start_date == null) display.start_date = epoch.toISOString().substr(0, 10);
438
            if (display.end_date == null && display.display_days != undefined) {
439
                let calculated_date = epoch;
440
                calculated_date.setDate(epoch.getDate() + Number(display.display_days));
441
442
                display.end_date = calculated_date.toISOString().substr(0, 10);
443
            }
444
            if (display.display_days == "") display.display_days = null;
445
            if (display.public_note == "") display.public_note = null;
446
            if (display.staff_note == "") display.staff_note = null;
447
448
            if (displayId) {
449
                baseResource.apiClient
450
                    .update(display, displayId)
451
                    .then(
452
                        success => {
453
                            baseResource.setMessage($__("Display updated"));
454
                            baseResource.router.push({ name: "DisplaysList" });
455
                        },
456
                        error => {}
457
                );
458
            } else {
459
                baseResource.apiClient.create(display).then(
460
                    success => {
461
                        baseResource.setMessage($__("Display created"));
462
                        baseResource.router.push({ name: "DisplaysList" });
463
                    },
464
                    error => {}
465
                );
466
            }
467
        });
468
        const afterResourceFetch = ((componentData, resource, caller) => {
469
            if(caller === "show" || caller === "form") {
470
                resource.display_items.forEach((display_item, idx) => {
471
                    getItemFromId(display_item.itemnumber)
472
                    .then(item => {
473
                        componentData.resource.value.display_items[idx] = {
474
                            barcode: item.external_id,
475
                            ...display_item,
476
                        };
477
                    })
478
                    .catch(error => {
479
                        console.log(error);
480
                    });
481
                });
482
            }
483
        });
484
485
        onBeforeMount(() => {});
486
487
        return {
488
            ...baseResource,
489
            tableOptions,
490
            checkForm,
491
            onFormSave,
492
            afterResourceFetch,
493
        };
494
    },
495
    emits: ["select-resource"],
496
    name: "DisplaysResource",
497
    components: {
498
        BaseResource,
499
    },
500
};
501
</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 file"),
69
                    },
70
                    {
71
                        path: "batch-remove",
72
                        name: "DisplaysBatchRemoveItems",
73
                        component: markRaw(
74
                            DisplaysBatchRemoveItems
75
                        ),
76
                        title: $__("Batch remove items from file"),
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