Refactor input value handling and improve validation in ObjectsGlobal component

This commit is contained in:
Jeppe Bundgaard
2026-06-29 11:08:47 +02:00
parent d23d5da165
commit 220013c4f0
16 changed files with 413 additions and 2 deletions
@@ -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>
+8
View File
@@ -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.",
+8
View File
@@ -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.",
+8
View File
@@ -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.",
+8
View File
@@ -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'}",
+8
View File
@@ -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.",
+8
View File
@@ -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"
+34
View File
@@ -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");