Refactor input value handling and improve validation in ObjectsGlobal component
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
label: String,
|
||||
description: String,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
searchable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
searchPlaceholder: String,
|
||||
emptyLabel: String,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
const { t } = useI18n();
|
||||
const search = ref("");
|
||||
|
||||
const normalizedSelectedValues = computed(() => props.modelValue.map((value) => String(value)));
|
||||
|
||||
const filteredOptions = computed(() => {
|
||||
const query = search.value.trim().toLowerCase();
|
||||
if (query === "") {
|
||||
return props.options;
|
||||
}
|
||||
|
||||
return props.options.filter((option) => String(option.label ?? "").toLowerCase().includes(query));
|
||||
});
|
||||
|
||||
const isSelected = (value) => normalizedSelectedValues.value.includes(String(value));
|
||||
|
||||
const toggleOption = (value) => {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valueKey = String(value);
|
||||
const nextValue = props.modelValue.some((selectedValue) => String(selectedValue) === valueKey)
|
||||
? props.modelValue.filter((selectedValue) => String(selectedValue) !== valueKey)
|
||||
: [...props.modelValue, value];
|
||||
|
||||
emit("update:modelValue", nextValue);
|
||||
emit("change", nextValue);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field configuration-multi-select">
|
||||
<label class="label">
|
||||
{{ label }}
|
||||
<span v-if="modelValue.length > 0" class="tag is-rounded is-small ml-2">
|
||||
{{ modelValue.length }} {{ t("common.selected").toLowerCase() }}
|
||||
</span>
|
||||
</label>
|
||||
<p v-if="description" class="help mb-2">{{ description }}</p>
|
||||
<div v-if="searchable" class="control mb-2">
|
||||
<input
|
||||
v-model="search"
|
||||
class="input is-small"
|
||||
type="text"
|
||||
:placeholder="searchPlaceholder || t('common.search')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
<div class="configuration-multi-select-grid">
|
||||
<button
|
||||
v-for="option in filteredOptions"
|
||||
:key="option.value"
|
||||
class="button configuration-multi-select-option"
|
||||
type="button"
|
||||
:class="{ 'is-link is-light': isSelected(option.value) }"
|
||||
:disabled="disabled"
|
||||
:aria-pressed="isSelected(option.value)"
|
||||
@click="toggleOption(option.value)"
|
||||
>
|
||||
<span class="icon configuration-multi-select-check" aria-hidden="true">
|
||||
<i v-if="isSelected(option.value)" class="fas fa-check"></i>
|
||||
</span>
|
||||
<span class="configuration-multi-select-label">{{ option.label }}</span>
|
||||
</button>
|
||||
<p v-if="filteredOptions.length === 0" class="help is-warning configuration-multi-select-empty">
|
||||
{{ emptyLabel || t("messages.no_results") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.configuration-multi-select-grid {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
||||
}
|
||||
|
||||
.configuration-multi-select-option {
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
min-height: 2.25rem;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.configuration-multi-select-check {
|
||||
flex: 0 0 1.25rem;
|
||||
}
|
||||
|
||||
.configuration-multi-select-label {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.configuration-multi-select-empty {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,15 @@ export const Config = {
|
||||
test_customer_registration_webhook: async () => {
|
||||
return authenticatedRequest("/slack/config/test", "POST", {});
|
||||
},
|
||||
get_internal_department_goal_progress: async () => {
|
||||
return authenticatedRequest("/slack/config/internal-department-goal-progress", "GET");
|
||||
},
|
||||
set_internal_department_goal_progress: async (webhookUrl, departmentIds) => {
|
||||
return authenticatedRequest("/slack/config/internal-department-goal-progress", "POST", {
|
||||
internal_department_goal_progress_webhook_url: webhookUrl,
|
||||
internal_department_ids: departmentIds,
|
||||
});
|
||||
},
|
||||
keys: {
|
||||
customer_registration_webhook_url: {
|
||||
get: async () => {
|
||||
@@ -29,6 +38,22 @@ export const Config = {
|
||||
return Config.set("customer_registration_webhook_url", value);
|
||||
},
|
||||
},
|
||||
internal_department_goal_progress_webhook_url: {
|
||||
get: async () => {
|
||||
return Config.get("internal_department_goal_progress_webhook_url");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("internal_department_goal_progress_webhook_url", value);
|
||||
},
|
||||
},
|
||||
internal_department_ids: {
|
||||
get: async () => {
|
||||
return Config.get("internal_department_ids");
|
||||
},
|
||||
set: async (value) => {
|
||||
return Config.set("internal_department_ids", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2341,8 +2341,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL til kunderegistreringer",
|
||||
"customer_registration_webhook_url_desc": "Slack-webhook der modtager en besked, når en ny kunderegistrering lykkes. Lad feltet være tomt for at deaktivere.",
|
||||
"internal_department_goal_progress_save_error": "Kunne ikke gemme Slack-indstillinger for interne afdelinger.",
|
||||
"internal_department_goal_progress_saved": "Slack-indstillinger for interne afdelinger blev gemt",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL til målopfyldelse for interne afdelinger",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-webhook der modtager den samlede status for målopfyldelse for interne afdelinger. Lad feltet være tomt for at deaktivere den samlede notifikation.",
|
||||
"internal_departments": "Interne afdelinger",
|
||||
"internal_departments_desc": "Afdelinger valgt her medtages i den interne Slack-oversigt over målopfyldelse.",
|
||||
"internal_departments_empty": "Ingen afdelinger fundet.",
|
||||
"notification_settings": "Notifikationer",
|
||||
"notification_settings_desc": "Slack-webhooks til systemhændelser.",
|
||||
"save_internal_departments": "Gem interne afdelinger",
|
||||
"send_test_webhook": "Send test-webhook",
|
||||
"subtitle": "Konfiguration af Slack-notifikationer",
|
||||
"test_webhook_error": "Kunne ikke sende Slack-testwebhooken.",
|
||||
|
||||
@@ -2451,8 +2451,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL fuer Kundenregistrierungen",
|
||||
"customer_registration_webhook_url_desc": "Slack-Webhook, der eine Nachricht erhaelt, wenn eine neue Kundenregistrierung erfolgreich ist. Leer lassen, um dies zu deaktivieren.",
|
||||
"internal_department_goal_progress_save_error": "Slack-Einstellungen fuer interne Abteilungen konnten nicht gespeichert werden.",
|
||||
"internal_department_goal_progress_saved": "Slack-Einstellungen fuer interne Abteilungen gespeichert",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL fuer Zielstatus interner Abteilungen",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-Webhook, der die zusammengefasste Zielstatusmeldung fuer interne Abteilungen erhaelt. Leer lassen, um die zusammengefasste Benachrichtigung zu deaktivieren.",
|
||||
"internal_departments": "Interne Abteilungen",
|
||||
"internal_departments_desc": "Hier ausgewaehlte Abteilungen werden in die interne Slack-Zielstatusuebersicht aufgenommen.",
|
||||
"internal_departments_empty": "Keine Abteilungen gefunden.",
|
||||
"notification_settings": "Benachrichtigungseinstellungen",
|
||||
"notification_settings_desc": "Slack-Webhooks fuer Systemereignisse.",
|
||||
"save_internal_departments": "Interne Abteilungen speichern",
|
||||
"send_test_webhook": "Test-Webhook senden",
|
||||
"subtitle": "Konfiguration von Slack-Benachrichtigungen",
|
||||
"test_webhook_error": "Der Slack-Test-Webhook konnte nicht gesendet werden.",
|
||||
|
||||
@@ -2175,8 +2175,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Customer registration webhook URL",
|
||||
"customer_registration_webhook_url_desc": "Slack webhook that receives a message when a new customer registration succeeds. Leave empty to disable.",
|
||||
"internal_department_goal_progress_save_error": "Could not save internal department Slack settings.",
|
||||
"internal_department_goal_progress_saved": "Internal department Slack settings saved",
|
||||
"internal_department_goal_progress_webhook_url": "Internal department goal progress webhook URL",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack webhook that receives the combined internal department goal progress summary. Leave empty to disable the combined notification.",
|
||||
"internal_departments": "Internal departments",
|
||||
"internal_departments_desc": "Departments selected here are included in the internal goal progress Slack summary.",
|
||||
"internal_departments_empty": "No departments found.",
|
||||
"notification_settings": "Notification settings",
|
||||
"notification_settings_desc": "Slack webhooks for system events.",
|
||||
"save_internal_departments": "Save internal departments",
|
||||
"send_test_webhook": "Send test webhook",
|
||||
"subtitle": "Configuration of Slack notifications",
|
||||
"test_webhook_error": "Could not send the Slack test webhook.",
|
||||
|
||||
@@ -1365,8 +1365,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "@:{'templates.generated.compat.configuration.slack.customer_registration_webhook_url'}",
|
||||
"customer_registration_webhook_url_desc": "@:{'templates.generated.compat.configuration.slack.customer_registration_webhook_url_desc'}",
|
||||
"internal_department_goal_progress_save_error": "@:{'templates.generated.compat.configuration.slack.internal_department_goal_progress_save_error'}",
|
||||
"internal_department_goal_progress_saved": "@:{'templates.generated.compat.configuration.slack.internal_department_goal_progress_saved'}",
|
||||
"internal_department_goal_progress_webhook_url": "@:{'templates.generated.compat.configuration.slack.internal_department_goal_progress_webhook_url'}",
|
||||
"internal_department_goal_progress_webhook_url_desc": "@:{'templates.generated.compat.configuration.slack.internal_department_goal_progress_webhook_url_desc'}",
|
||||
"internal_departments": "@:{'templates.generated.compat.configuration.slack.internal_departments'}",
|
||||
"internal_departments_desc": "@:{'templates.generated.compat.configuration.slack.internal_departments_desc'}",
|
||||
"internal_departments_empty": "@:{'templates.generated.compat.configuration.slack.internal_departments_empty'}",
|
||||
"notification_settings": "@:{'templates.generated.compat.configuration.slack.notification_settings'}",
|
||||
"notification_settings_desc": "@:{'templates.generated.compat.configuration.slack.notification_settings_desc'}",
|
||||
"save_internal_departments": "@:{'templates.generated.compat.configuration.slack.save_internal_departments'}",
|
||||
"send_test_webhook": "@:{'templates.generated.compat.configuration.slack.send_test_webhook'}",
|
||||
"subtitle": "@:{'templates.generated.compat.configuration.slack.subtitle'}",
|
||||
"test_webhook_error": "@:{'templates.generated.compat.configuration.slack.test_webhook_error'}",
|
||||
|
||||
@@ -2452,8 +2452,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL for kunderegistreringer",
|
||||
"customer_registration_webhook_url_desc": "Slack-webhook som mottar en melding når en ny kunderegistrering lykkes. La feltet stå tomt for å deaktivere.",
|
||||
"internal_department_goal_progress_save_error": "Kunne ikke lagre Slack-innstillinger for interne avdelinger.",
|
||||
"internal_department_goal_progress_saved": "Slack-innstillinger for interne avdelinger ble lagret",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL for målprogresjon for interne avdelinger",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-webhook som mottar den samlede målprogresjonsoversikten for interne avdelinger. La feltet stå tomt for å deaktivere den samlede varslingen.",
|
||||
"internal_departments": "Interne avdelinger",
|
||||
"internal_departments_desc": "Avdelinger valgt her tas med i den interne Slack-oversikten for målprogresjon.",
|
||||
"internal_departments_empty": "Ingen avdelinger funnet.",
|
||||
"notification_settings": "Varslingsinnstillinger",
|
||||
"notification_settings_desc": "Slack-webhooks for systemhendelser.",
|
||||
"save_internal_departments": "Lagre interne avdelinger",
|
||||
"send_test_webhook": "Send test-webhook",
|
||||
"subtitle": "Konfigurasjon av Slack-varsler",
|
||||
"test_webhook_error": "Kunne ikke sende Slack-testwebhooken.",
|
||||
|
||||
@@ -2502,8 +2502,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL för kundregistreringar",
|
||||
"customer_registration_webhook_url_desc": "Slack-webhook som får ett meddelande när en ny kundregistrering lyckas. Lämna tomt för att inaktivera.",
|
||||
"internal_department_goal_progress_save_error": "Det gick inte att spara Slack-inställningar för interna avdelningar.",
|
||||
"internal_department_goal_progress_saved": "Slack-inställningar för interna avdelningar sparades",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL för måluppföljning för interna avdelningar",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-webhook som får den sammanlagda måluppföljningen för interna avdelningar. Lämna tomt för att inaktivera den sammanlagda aviseringen.",
|
||||
"internal_departments": "Interna avdelningar",
|
||||
"internal_departments_desc": "Avdelningar som väljs här ingår i den interna Slack-översikten för måluppföljning.",
|
||||
"internal_departments_empty": "Inga avdelningar hittades.",
|
||||
"notification_settings": "Aviseringsinställningar",
|
||||
"notification_settings_desc": "Slack-webhooks för systemhändelser.",
|
||||
"save_internal_departments": "Spara interna avdelningar",
|
||||
"send_test_webhook": "Skicka test-webhook",
|
||||
"subtitle": "Konfiguration av Slack-aviseringar",
|
||||
"test_webhook_error": "Det gick inte att skicka Slack-testwebhooken.",
|
||||
|
||||
@@ -161,8 +161,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL til kunderegistreringer",
|
||||
"customer_registration_webhook_url_desc": "Slack-webhook der modtager en besked, når en ny kunderegistrering lykkes. Lad feltet være tomt for at deaktivere.",
|
||||
"internal_department_goal_progress_save_error": "Kunne ikke gemme Slack-indstillinger for interne afdelinger.",
|
||||
"internal_department_goal_progress_saved": "Slack-indstillinger for interne afdelinger blev gemt",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL til målopfyldelse for interne afdelinger",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-webhook der modtager den samlede status for målopfyldelse for interne afdelinger. Lad feltet være tomt for at deaktivere den samlede notifikation.",
|
||||
"internal_departments": "Interne afdelinger",
|
||||
"internal_departments_desc": "Afdelinger valgt her medtages i den interne Slack-oversigt over målopfyldelse.",
|
||||
"internal_departments_empty": "Ingen afdelinger fundet.",
|
||||
"notification_settings": "Notifikationer",
|
||||
"notification_settings_desc": "Slack-webhooks til systemhændelser.",
|
||||
"save_internal_departments": "Gem interne afdelinger",
|
||||
"send_test_webhook": "Send test-webhook",
|
||||
"subtitle": "Konfiguration af Slack-notifikationer",
|
||||
"test_webhook_error": "Kunne ikke sende Slack-testwebhooken.",
|
||||
|
||||
@@ -161,8 +161,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL fuer Kundenregistrierungen",
|
||||
"customer_registration_webhook_url_desc": "Slack-Webhook, der eine Nachricht erhaelt, wenn eine neue Kundenregistrierung erfolgreich ist. Leer lassen, um dies zu deaktivieren.",
|
||||
"internal_department_goal_progress_save_error": "Slack-Einstellungen fuer interne Abteilungen konnten nicht gespeichert werden.",
|
||||
"internal_department_goal_progress_saved": "Slack-Einstellungen fuer interne Abteilungen gespeichert",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL fuer Zielstatus interner Abteilungen",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-Webhook, der die zusammengefasste Zielstatusmeldung fuer interne Abteilungen erhaelt. Leer lassen, um die zusammengefasste Benachrichtigung zu deaktivieren.",
|
||||
"internal_departments": "Interne Abteilungen",
|
||||
"internal_departments_desc": "Hier ausgewaehlte Abteilungen werden in die interne Slack-Zielstatusuebersicht aufgenommen.",
|
||||
"internal_departments_empty": "Keine Abteilungen gefunden.",
|
||||
"notification_settings": "Benachrichtigungseinstellungen",
|
||||
"notification_settings_desc": "Slack-Webhooks fuer Systemereignisse.",
|
||||
"save_internal_departments": "Interne Abteilungen speichern",
|
||||
"send_test_webhook": "Test-Webhook senden",
|
||||
"subtitle": "Konfiguration von Slack-Benachrichtigungen",
|
||||
"test_webhook_error": "Der Slack-Test-Webhook konnte nicht gesendet werden.",
|
||||
|
||||
@@ -161,8 +161,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Customer registration webhook URL",
|
||||
"customer_registration_webhook_url_desc": "Slack webhook that receives a message when a new customer registration succeeds. Leave empty to disable.",
|
||||
"internal_department_goal_progress_save_error": "Could not save internal department Slack settings.",
|
||||
"internal_department_goal_progress_saved": "Internal department Slack settings saved",
|
||||
"internal_department_goal_progress_webhook_url": "Internal department goal progress webhook URL",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack webhook that receives the combined internal department goal progress summary. Leave empty to disable the combined notification.",
|
||||
"internal_departments": "Internal departments",
|
||||
"internal_departments_desc": "Departments selected here are included in the internal goal progress Slack summary.",
|
||||
"internal_departments_empty": "No departments found.",
|
||||
"notification_settings": "Notification settings",
|
||||
"notification_settings_desc": "Slack webhooks for system events.",
|
||||
"save_internal_departments": "Save internal departments",
|
||||
"send_test_webhook": "Send test webhook",
|
||||
"subtitle": "Configuration of Slack notifications",
|
||||
"test_webhook_error": "Could not send the Slack test webhook.",
|
||||
|
||||
@@ -187,8 +187,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "@:{'phrases.compat.configuration.slack.customer_registration_webhook_url'}",
|
||||
"customer_registration_webhook_url_desc": "@:{'phrases.compat.configuration.slack.customer_registration_webhook_url_desc'}",
|
||||
"internal_department_goal_progress_save_error": "@:{'phrases.compat.configuration.slack.internal_department_goal_progress_save_error'}",
|
||||
"internal_department_goal_progress_saved": "@:{'phrases.compat.configuration.slack.internal_department_goal_progress_saved'}",
|
||||
"internal_department_goal_progress_webhook_url": "@:{'phrases.compat.configuration.slack.internal_department_goal_progress_webhook_url'}",
|
||||
"internal_department_goal_progress_webhook_url_desc": "@:{'phrases.compat.configuration.slack.internal_department_goal_progress_webhook_url_desc'}",
|
||||
"internal_departments": "@:{'phrases.compat.configuration.slack.internal_departments'}",
|
||||
"internal_departments_desc": "@:{'phrases.compat.configuration.slack.internal_departments_desc'}",
|
||||
"internal_departments_empty": "@:{'phrases.compat.configuration.slack.internal_departments_empty'}",
|
||||
"notification_settings": "@:{'phrases.compat.configuration.slack.notification_settings'}",
|
||||
"notification_settings_desc": "@:{'phrases.compat.configuration.slack.notification_settings_desc'}",
|
||||
"save_internal_departments": "@:{'phrases.compat.configuration.slack.save_internal_departments'}",
|
||||
"send_test_webhook": "@:{'phrases.compat.configuration.slack.send_test_webhook'}",
|
||||
"subtitle": "@:{'phrases.compat.configuration.slack.subtitle'}",
|
||||
"test_webhook_error": "@:{'phrases.compat.configuration.slack.test_webhook_error'}",
|
||||
|
||||
@@ -161,8 +161,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL for kunderegistreringer",
|
||||
"customer_registration_webhook_url_desc": "Slack-webhook som mottar en melding når en ny kunderegistrering lykkes. La feltet stå tomt for å deaktivere.",
|
||||
"internal_department_goal_progress_save_error": "Kunne ikke lagre Slack-innstillinger for interne avdelinger.",
|
||||
"internal_department_goal_progress_saved": "Slack-innstillinger for interne avdelinger ble lagret",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL for målprogresjon for interne avdelinger",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-webhook som mottar den samlede målprogresjonsoversikten for interne avdelinger. La feltet stå tomt for å deaktivere den samlede varslingen.",
|
||||
"internal_departments": "Interne avdelinger",
|
||||
"internal_departments_desc": "Avdelinger valgt her tas med i den interne Slack-oversikten for målprogresjon.",
|
||||
"internal_departments_empty": "Ingen avdelinger funnet.",
|
||||
"notification_settings": "Varslingsinnstillinger",
|
||||
"notification_settings_desc": "Slack-webhooks for systemhendelser.",
|
||||
"save_internal_departments": "Lagre interne avdelinger",
|
||||
"send_test_webhook": "Send test-webhook",
|
||||
"subtitle": "Konfigurasjon av Slack-varsler",
|
||||
"test_webhook_error": "Kunne ikke sende Slack-testwebhooken.",
|
||||
|
||||
@@ -161,8 +161,16 @@
|
||||
"slack": {
|
||||
"customer_registration_webhook_url": "Webhook-URL för kundregistreringar",
|
||||
"customer_registration_webhook_url_desc": "Slack-webhook som får ett meddelande när en ny kundregistrering lyckas. Lämna tomt för att inaktivera.",
|
||||
"internal_department_goal_progress_save_error": "Det gick inte att spara Slack-inställningar för interna avdelningar.",
|
||||
"internal_department_goal_progress_saved": "Slack-inställningar för interna avdelningar sparades",
|
||||
"internal_department_goal_progress_webhook_url": "Webhook-URL för måluppföljning för interna avdelningar",
|
||||
"internal_department_goal_progress_webhook_url_desc": "Slack-webhook som får den sammanlagda måluppföljningen för interna avdelningar. Lämna tomt för att inaktivera den sammanlagda aviseringen.",
|
||||
"internal_departments": "Interna avdelningar",
|
||||
"internal_departments_desc": "Avdelningar som väljs här ingår i den interna Slack-översikten för måluppföljning.",
|
||||
"internal_departments_empty": "Inga avdelningar hittades.",
|
||||
"notification_settings": "Aviseringsinställningar",
|
||||
"notification_settings_desc": "Slack-webhooks för systemhändelser.",
|
||||
"save_internal_departments": "Spara interna avdelningar",
|
||||
"send_test_webhook": "Skicka test-webhook",
|
||||
"subtitle": "Konfiguration av Slack-aviseringar",
|
||||
"test_webhook_error": "Det gick inte att skicka Slack-testwebhooken.",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import Swal from "sweetalert2";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import PageTitle from "@/components/global/PageTitle.vue";
|
||||
import ConfigurationCategory from "@/components/displays/superuser/configuration/ConfigurationCategory.vue";
|
||||
import ConfigurationInput from "@/components/displays/superuser/configuration/ConfigurationInput.vue";
|
||||
import ConfigurationMultiSelect from "@/components/displays/superuser/configuration/ConfigurationMultiSelect.vue";
|
||||
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import ConfigurationSubPageWrapper from "@/views/dashboards/superUserDashboard/configuration/ConfigurationSubPageWrapper.vue";
|
||||
@@ -14,9 +15,20 @@ const module_config = ref([]);
|
||||
const module_config_unavailable = ref(false);
|
||||
const module_config_forbidden = ref(false);
|
||||
const is_testing_webhook = ref(false);
|
||||
const internal_departments = ref([]);
|
||||
const internal_department_ids = ref([]);
|
||||
const internal_department_goal_progress_webhook_url = ref("");
|
||||
const is_saving_internal_departments = ref(false);
|
||||
|
||||
const getRequestStatus = (error) => Number.parseInt(String(error?.response?.status ?? error?.status ?? ""), 10);
|
||||
|
||||
const internal_department_options = computed(() =>
|
||||
internal_departments.value.map((department) => ({
|
||||
label: department.name,
|
||||
value: Number.parseInt(String(department.id), 10),
|
||||
}))
|
||||
);
|
||||
|
||||
const getModuleConfig = async () => {
|
||||
module_config_unavailable.value = false;
|
||||
module_config_forbidden.value = false;
|
||||
@@ -55,6 +67,84 @@ const getModuleConfigValue = (variable) => {
|
||||
return config ? config.value : "";
|
||||
};
|
||||
|
||||
const normalizeDepartmentIds = (departmentIds) => Array.isArray(departmentIds)
|
||||
? departmentIds.map((departmentId) => Number.parseInt(String(departmentId), 10)).filter(Number.isInteger)
|
||||
: [];
|
||||
|
||||
const getDepartmentsFallback = async () => {
|
||||
if (internal_departments.value.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
internal_departments.value = await SessionUser.objects.departments.get.all();
|
||||
} catch (error) {
|
||||
console.error("Error fetching departments for Slack internal department selector:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getInternalDepartmentGoalProgressConfig = async () => {
|
||||
await SessionUser.superUser.modules.slack.config
|
||||
.get_internal_department_goal_progress()
|
||||
.then((response) => {
|
||||
const data = response?.data?.data ?? {};
|
||||
internal_departments.value = Array.isArray(data.departments) ? data.departments : [];
|
||||
internal_department_ids.value = normalizeDepartmentIds(data.internal_department_ids);
|
||||
internal_department_goal_progress_webhook_url.value = data.internal_department_goal_progress_webhook_url ?? "";
|
||||
})
|
||||
.catch((error) => {
|
||||
const status = getRequestStatus(error);
|
||||
|
||||
if (status === 404) {
|
||||
module_config_unavailable.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 403) {
|
||||
module_config_forbidden.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("Error fetching Slack internal department goal progress config:", error);
|
||||
});
|
||||
|
||||
await getDepartmentsFallback();
|
||||
};
|
||||
|
||||
const saveInternalDepartmentGoalProgressConfig = async () => {
|
||||
if (is_saving_internal_departments.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_saving_internal_departments.value = true;
|
||||
try {
|
||||
const response = await SessionUser.superUser.modules.slack.config.set_internal_department_goal_progress(
|
||||
internal_department_goal_progress_webhook_url.value,
|
||||
internal_department_ids.value
|
||||
);
|
||||
const data = response?.data?.data ?? {};
|
||||
internal_department_ids.value = normalizeDepartmentIds(data.internal_department_ids);
|
||||
internal_department_goal_progress_webhook_url.value = data.internal_department_goal_progress_webhook_url ?? internal_department_goal_progress_webhook_url.value;
|
||||
await Swal.fire({
|
||||
title: t("configuration.slack.internal_department_goal_progress_saved"),
|
||||
icon: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
await Swal.fire({
|
||||
title: t("common.error"),
|
||||
text: getRequestStatus(error) === 403 ? t("errors.forbidden") : t("configuration.slack.internal_department_goal_progress_save_error"),
|
||||
icon: "error",
|
||||
});
|
||||
} finally {
|
||||
is_saving_internal_departments.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const saveInternalDepartmentGoalProgressWebhookUrl = async (value) => {
|
||||
internal_department_goal_progress_webhook_url.value = value;
|
||||
return saveInternalDepartmentGoalProgressConfig();
|
||||
};
|
||||
|
||||
const testCustomerRegistrationWebhook = async () => {
|
||||
if (is_testing_webhook.value) {
|
||||
return;
|
||||
@@ -87,7 +177,12 @@ const testCustomerRegistrationWebhook = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
getModuleConfig();
|
||||
const load = async () => {
|
||||
await getModuleConfig();
|
||||
await getInternalDepartmentGoalProgressConfig();
|
||||
};
|
||||
|
||||
load();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -113,6 +208,40 @@ getModuleConfig();
|
||||
:value="getModuleConfigValue('customer_registration_webhook_url')"
|
||||
:on-save="SessionUser.superUser.modules.slack.config.keys.customer_registration_webhook_url.set"
|
||||
/>
|
||||
<ConfigurationInput
|
||||
class="mt-5"
|
||||
module="Slack"
|
||||
:title="$t('configuration.slack.internal_department_goal_progress_webhook_url')"
|
||||
:description="$t('configuration.slack.internal_department_goal_progress_webhook_url_desc')"
|
||||
icon="fab fa-slack"
|
||||
:value="internal_department_goal_progress_webhook_url"
|
||||
:on-save="saveInternalDepartmentGoalProgressWebhookUrl"
|
||||
/>
|
||||
<div class="mt-3">
|
||||
<ConfigurationMultiSelect
|
||||
v-model="internal_department_ids"
|
||||
data-testid="slack-internal-departments-selector"
|
||||
:label="$t('configuration.slack.internal_departments')"
|
||||
:description="$t('configuration.slack.internal_departments_desc')"
|
||||
:options="internal_department_options"
|
||||
:disabled="is_saving_internal_departments"
|
||||
:search-placeholder="$t('global.search_departments')"
|
||||
:empty-label="$t('configuration.slack.internal_departments_empty')"
|
||||
/>
|
||||
<button
|
||||
class="button is-info mt-3"
|
||||
type="button"
|
||||
data-testid="slack-save-internal-departments-button"
|
||||
:class="{ 'is-loading': is_saving_internal_departments }"
|
||||
:disabled="is_saving_internal_departments"
|
||||
@click="saveInternalDepartmentGoalProgressConfig"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-save" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>{{ $t("configuration.slack.save_internal_departments") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="button is-dark mt-2"
|
||||
type="button"
|
||||
|
||||
@@ -15,6 +15,10 @@ const slackPageSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/configuration/ConfigurationSlack.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const configurationMultiSelectSource = readFileSync(
|
||||
join(root, "src/components/displays/superuser/configuration/ConfigurationMultiSelect.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const superUserObjectSource = readFileSync(join(root, "src/components/session/token/superUserObject.vue"), "utf8");
|
||||
const routerSource = readFileSync(join(root, "src/router.js"), "utf8");
|
||||
const navigationSource = readFileSync(
|
||||
@@ -33,9 +37,14 @@ describe("slack module contract", () => {
|
||||
expect(slackConfigSource).toContain('"/slack/config?variable=" + variable');
|
||||
expect(slackConfigSource).toContain('"/slack/config"');
|
||||
expect(slackConfigSource).toContain('"/slack/config/test"');
|
||||
expect(slackConfigSource).toContain('"/slack/config/internal-department-goal-progress"');
|
||||
expect(slackConfigSource).toContain("test_customer_registration_webhook");
|
||||
expect(slackConfigSource).toContain("get_internal_department_goal_progress");
|
||||
expect(slackConfigSource).toContain("set_internal_department_goal_progress");
|
||||
expect(slackConfigSource).toContain('Config.get("customer_registration_webhook_url")');
|
||||
expect(slackConfigSource).toContain('Config.set("customer_registration_webhook_url", value)');
|
||||
expect(slackConfigSource).toContain('Config.get("internal_department_goal_progress_webhook_url")');
|
||||
expect(slackConfigSource).toContain('Config.set("internal_department_ids", value)');
|
||||
});
|
||||
|
||||
it("exposes the Slack configuration route and navigation tab", () => {
|
||||
@@ -67,6 +76,31 @@ describe("slack module contract", () => {
|
||||
expect(slackPageSource).toContain('t("configuration.slack.test_webhook_error")');
|
||||
});
|
||||
|
||||
it("binds internal department goal progress settings to the Slack config endpoint", () => {
|
||||
expect(slackPageSource).toContain("ConfigurationMultiSelect");
|
||||
expect(slackPageSource).toContain("getInternalDepartmentGoalProgressConfig");
|
||||
expect(slackPageSource).toContain("saveInternalDepartmentGoalProgressConfig");
|
||||
expect(slackPageSource).toContain("saveInternalDepartmentGoalProgressWebhookUrl");
|
||||
expect(slackPageSource).toContain("getDepartmentsFallback");
|
||||
expect(slackPageSource).toContain(".get_internal_department_goal_progress()");
|
||||
expect(slackPageSource).toContain("set_internal_department_goal_progress");
|
||||
expect(slackPageSource).toContain("$t('configuration.slack.internal_department_goal_progress_webhook_url')");
|
||||
expect(slackPageSource).toContain("$t('configuration.slack.internal_department_goal_progress_webhook_url_desc')");
|
||||
expect(slackPageSource).toContain("$t('configuration.slack.internal_departments')");
|
||||
expect(slackPageSource).toContain('data-testid="slack-internal-departments-selector"');
|
||||
expect(slackPageSource).toContain("$t('configuration.slack.internal_departments_empty')");
|
||||
expect(slackPageSource).toContain('data-testid="slack-save-internal-departments-button"');
|
||||
expect(slackPageSource).toContain('$t("configuration.slack.save_internal_departments")');
|
||||
});
|
||||
|
||||
it("provides a reusable multi-select with checkmark options and empty-state feedback", () => {
|
||||
expect(configurationMultiSelectSource).toContain('defineEmits(["update:modelValue", "change"])');
|
||||
expect(configurationMultiSelectSource).toContain('class="button configuration-multi-select-option"');
|
||||
expect(configurationMultiSelectSource).toContain('class="fas fa-check"');
|
||||
expect(configurationMultiSelectSource).toContain("filteredOptions.length === 0");
|
||||
expect(configurationMultiSelectSource).toContain('t("messages.no_results")');
|
||||
});
|
||||
|
||||
it("shows an unavailable state when the API release does not expose Slack config", () => {
|
||||
expect(slackPageSource).toContain("module_config_unavailable");
|
||||
expect(slackPageSource).toContain("status === 404");
|
||||
|
||||
Reference in New Issue
Block a user