From a4aec936f6f7cb5ccd078f13d98218b653219acd Mon Sep 17 00:00:00 2001
From: Pedro Amorim <pedro.amorim@ptfs-europe.com>
Date: Wed, 8 Nov 2023 11:34:19 -0100
Subject: [PATCH] Bug 35287: Vue preparation - AdditionalFields

New api client: additional-fields-api-client.js to interact with the REST endpoint introduced by bug 35197
2 new components:
- AdditionalFieldsEntry: Component used to extend the FormAdd component including the corresponding additional fields
- AdditionalFieldsDisplay: Component used to display the additional fields associated to a data record implementing it (makes use of strings_map)

Signed-off-by: Edith Speller <edith.speller@ukhsa.gov.uk>
Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
---
 .../components/AdditionalFieldsDisplay.vue    |  37 +++
 .../vue/components/AdditionalFieldsEntry.vue  | 271 ++++++++++++++++++
 .../vue/fetch/additional-fields-api-client.js |  20 ++
 .../prog/js/vue/fetch/api-client.js           |   2 +
 4 files changed, 330 insertions(+)
 create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsDisplay.vue
 create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsEntry.vue
 create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/fetch/additional-fields-api-client.js

diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsDisplay.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsDisplay.vue
new file mode 100644
index 00000000000..f7e232913a3
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsDisplay.vue
@@ -0,0 +1,37 @@
+<template>
+    <fieldset
+        v-if="additional_field_values.length"
+        class="rows"
+        id="additional_fields"
+    >
+        <legend>{{ $__("Additional fields") }}</legend>
+        <ol>
+            <li v-for="additional_field_value in additional_field_values">
+                <label
+                    :for="`additional_field_` + additional_field_value.field_id"
+                >
+                    {{ additional_field_value.field_label }}:
+                </label>
+                <span> {{ additional_field_value.value_str }} </span>
+            </li>
+        </ol>
+    </fieldset>
+</template>
+
+<script>
+import { inject } from "vue"
+import { APIClient } from "../fetch/api-client.js"
+
+export default {
+    data() {
+        return {
+            fields_to_display: [],
+        }
+    },
+    name: "AdditionalFieldsDisplay",
+    props: {
+        tablename: String,
+        additional_field_values: Array,
+    },
+}
+</script>
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsEntry.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsEntry.vue
new file mode 100644
index 00000000000..246656f3aea
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/AdditionalFieldsEntry.vue
@@ -0,0 +1,271 @@
+<template>
+    <fieldset
+        v-if="available_fields.length"
+        class="rows"
+        id="additional_fields"
+    >
+        <legend>{{ $__("Additional fields") }}</legend>
+        <ol>
+            <template v-for="available_field in available_fields">
+                <template
+                    v-if="
+                        available_field.authorised_value_category &&
+                        !available_field.repeatable
+                    "
+                >
+                    <li>
+                        <label :for="`additional_field_` + available_field.id"
+                            >{{ available_field.name }}:
+                        </label>
+                        <v-select
+                            :id="`additional_field_` + available_field.id"
+                            :name="available_field.name"
+                            v-model="
+                                current_additional_fields_values[
+                                    available_field.id
+                                ]
+                            "
+                            :options="
+                                av_options[
+                                    available_field.authorised_value_category
+                                ]
+                            "
+                        />
+                    </li>
+                </template>
+                <template
+                    v-if="
+                        available_field.authorised_value_category &&
+                        available_field.repeatable
+                    "
+                >
+                    <li>
+                        <label :for="`additional_field_` + available_field.id"
+                            >{{ available_field.name }}:
+                        </label>
+                        <v-select
+                            :id="`additional_field_` + available_field.id"
+                            :name="available_field.name"
+                            :multiple="available_field.repeatable"
+                            v-model="
+                                current_additional_fields_values[
+                                    available_field.id
+                                ]
+                            "
+                            :options="
+                                av_options[
+                                    available_field.authorised_value_category
+                                ]
+                            "
+                        />
+                    </li>
+                </template>
+
+                <template v-if="!available_field.authorised_value_category">
+                    <li
+                        v-for="current in current_additional_fields_values[
+                            available_field.id
+                        ]"
+                    >
+                        <label :for="`additional_field_` + available_field.id"
+                            >{{ available_field.name }}:
+                        </label>
+                        <input type="text" v-model="current.value" />
+                        <a
+                            href="#"
+                            class="clear_attribute"
+                            @click="clearField(current, $event)"
+                        >
+                            <i class="fa fa-fw fa-trash-can"></i>
+                            {{ $__("Clear") }}
+                        </a>
+                        <template v-if="available_field.repeatable">
+                            <a
+                                href="#"
+                                class="clone_attribute"
+                                @click="
+                                    cloneField(available_field, current, $event)
+                                "
+                            >
+                                <i class="fa fa-fw fa-plus"></i>
+                                {{ $__("New") }}
+                            </a>
+                        </template>
+                    </li>
+                </template>
+            </template>
+        </ol>
+    </fieldset>
+</template>
+
+<script>
+import { inject } from "vue"
+import { APIClient } from "../fetch/api-client.js"
+
+export default {
+    data() {
+        return {
+            available_fields: [],
+            av_options: [],
+            current_additional_fields_values: {},
+        }
+    },
+    beforeCreate() {
+        const client = APIClient.additional_fields
+        client.additional_fields.getAll(this.tablename).then(
+            available_fields => {
+                this.available_fields = available_fields
+                this.initialized = true
+            },
+            error => {}
+        )
+    },
+    watch: {
+        current_additional_fields_values: {
+            deep: true,
+            handler(current_additional_fields_values) {
+                this.updateParentAdditionalFieldValues(
+                    current_additional_fields_values
+                )
+            },
+        },
+        available_fields: function (available_fields) {
+            if (available_fields) {
+                const client_av = APIClient.authorised_values
+                let av_cat_array = available_fields
+                    .map(field => field.authorised_value_category)
+                    .filter(field => field)
+
+                client_av.values
+                    .getCategoriesWithValues([
+                        ...new Set(
+                            av_cat_array.map(av_cat => '"' + av_cat + '"')
+                        ),
+                    ]) // unique
+                    .then(av_categories => {
+                        av_cat_array.forEach(av_cat => {
+                            let av_match = av_categories.find(
+                                element => element.category_name == av_cat
+                            )
+                            this.av_options[av_cat] =
+                                av_match.authorised_values.map(av => ({
+                                    value: av.value,
+                                    label: av.description,
+                                }))
+                        })
+
+                        // Iterate on available fields
+                        available_fields.forEach(available_field => {
+                            // Initialize current field as empty array
+                            this.current_additional_fields_values[
+                                available_field.id
+                            ] = []
+
+                            // Grab all existing field values of this field
+                            let existing_field_values =
+                                this.additional_field_values.filter(
+                                    afv =>
+                                        afv.field_id == available_field.id &&
+                                        afv.value
+                                )
+
+                            // If there are existing field values for this field, add them to current_additional_fields_values
+                            if (existing_field_values.length) {
+                                existing_field_values.forEach(
+                                    existing_field_value => {
+                                        let label = ""
+                                        if (
+                                            available_field.authorised_value_category
+                                        ) {
+                                            let av_value = this.av_options[
+                                                available_field
+                                                    .authorised_value_category
+                                            ].filter(
+                                                av_option =>
+                                                    av_option.value ==
+                                                    existing_field_value.value
+                                            )
+                                            label = av_value.length
+                                                ? av_value[0].label
+                                                : ""
+                                        }
+                                        this.current_additional_fields_values[
+                                            existing_field_value.field_id
+                                        ].push({
+                                            value: existing_field_value.value,
+                                            label: label,
+                                        })
+                                    }
+                                )
+
+                                // Otherwise add them as empty if not AV field
+                            } else {
+                                if (
+                                    !available_field.authorised_value_category
+                                ) {
+                                    this.current_additional_fields_values[
+                                        available_field.id
+                                    ] = [
+                                        {
+                                            label: "",
+                                            value: "",
+                                        },
+                                    ]
+                                }
+                            }
+                        })
+                    })
+            }
+        },
+    },
+    methods: {
+        clearField: function (current_field, event) {
+            event.preventDefault()
+            current_field.value = ""
+        },
+        cloneField: function (available_field, current, event) {
+            event.preventDefault()
+            this.current_additional_fields_values[available_field.id].push({
+                value: current.value,
+                label: available_field.name,
+            })
+        },
+        updateParentAdditionalFieldValues: function (
+            current_additional_fields_values
+        ) {
+            let updatedAdditionalFields = []
+            Object.keys(current_additional_fields_values).forEach(field_id => {
+                if (
+                    !Array.isArray(
+                        current_additional_fields_values[field_id]
+                    ) &&
+                    current_additional_fields_values[field_id]
+                ) {
+                    current_additional_fields_values[field_id] = [
+                        current_additional_fields_values[field_id],
+                    ]
+                }
+                if (current_additional_fields_values[field_id]) {
+                    let new_extended_attributes =
+                        current_additional_fields_values[field_id].map(
+                            value => ({
+                                field_id: field_id,
+                                value: value.value,
+                            })
+                        )
+                    updatedAdditionalFields = updatedAdditionalFields.concat(
+                        new_extended_attributes
+                    )
+                }
+            })
+
+            this.$emit("additional-fields-changed", updatedAdditionalFields)
+        },
+    },
+    name: "AdditionalFieldsEntry",
+    props: {
+        tablename: String,
+        additional_field_values: Array,
+    },
+}
+</script>
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/additional-fields-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/additional-fields-api-client.js
new file mode 100644
index 00000000000..8d12305d51c
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/additional-fields-api-client.js
@@ -0,0 +1,20 @@
+import HttpClient from "./http-client";
+
+export class AdditionalFieldsAPIClient extends HttpClient {
+    constructor() {
+        super({
+            baseURL: "/api/v1/additional_fields",
+        });
+    }
+
+    get additional_fields() {
+        return {
+            getAll: tablename =>
+                this.get({
+                    endpoint: "?tablename=" + tablename,
+                }),
+        };
+    }
+}
+
+export default AdditionalFieldsAPIClient;
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/api-client.js b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/api-client.js
index 294693c238e..6a35dff20ce 100644
--- a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/api-client.js
+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/api-client.js
@@ -1,6 +1,7 @@
 import ERMAPIClient from "./erm-api-client";
 import PatronAPIClient from "./patron-api-client";
 import AcquisitionAPIClient from "./acquisition-api-client";
+import AdditionalFieldsAPIClient from "./additional-fields-api-client";
 import AVAPIClient from "./authorised-values-api-client";
 import ItemAPIClient from "./item-api-client";
 import SysprefAPIClient from "./system-preferences-api-client";
@@ -10,6 +11,7 @@ export const APIClient = {
     erm: new ERMAPIClient(),
     patron: new PatronAPIClient(),
     acquisition: new AcquisitionAPIClient(),
+    additional_fields: new AdditionalFieldsAPIClient(),
     authorised_values: new AVAPIClient(),
     item: new ItemAPIClient(),
     sysprefs: new SysprefAPIClient(),
-- 
2.39.2