From 63f335f1b2706a6c792de0fa27e64125d788d6f0 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 09:25:34 +0100 Subject: [PATCH 01/10] 6871: Fixed screen save issue --- assets/admin/components/screen/screen-manager.jsx | 14 ++++++++------ .../admin/components/util/helpers/id-from-url.jsx | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index bdb4f1c91..4ef4f184f 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -127,7 +127,7 @@ function ScreenManager({ * @returns {Array | null} A mapped array with group ids or null */ function mapGroups() { - if (formStateObject.inScreenGroups) { + if (formStateObject?.inScreenGroups instanceof Array) { return formStateObject.inScreenGroups.map((group) => { return idFromUrl(group); }); @@ -167,16 +167,17 @@ function ScreenManager({ * * @returns {Array | null} A mapped array with playlist, regions and weight or null */ - function mapPlaylistsWithRegion() { + function mapPlaylistsWithRegion(playlists, regions) { const returnArray = []; - const { playlists, regions } = formStateObject; - const regionIds = regions.map((r) => idFromUrl(r["@id"])); + const regionIds = regions.map((r) => + typeof r === "string" ? idFromUrl(r, 1) : idFromUrl(r["@id"]) + ); // The playlists all have a regionId, the following creates a unique list of relevant regions If there are not // playlists, then an empty playlist is to be saved per region let playlistRegions = []; if (playlists?.length > 0) { - playlistRegions = [...new Set(playlists.map(({ region }) => region))]; + playlistRegions = [...new Set(playlists.map(({ region }) => idFromUrl(region)))]; } // Then the playlists are mapped by region Looping through the regions that have a playlist connected... @@ -191,6 +192,7 @@ function ScreenManager({ regionId: idFromUrl(regionId), }); }); + // The remaining regions are added with empty playlist arrays. if (regionIds.length > 0) { regionIds.forEach((regionId) => @@ -252,7 +254,7 @@ function ScreenManager({ resolution: getResolution(), groups: mapGroups(), orientation: getOrientation(), - regions: mapPlaylistsWithRegion(), + regions: mapPlaylistsWithRegion(localFormStateObject.playlists, localFormStateObject.regions), }), }; diff --git a/assets/admin/components/util/helpers/id-from-url.jsx b/assets/admin/components/util/helpers/id-from-url.jsx index 4aef6b861..355a73d0d 100644 --- a/assets/admin/components/util/helpers/id-from-url.jsx +++ b/assets/admin/components/util/helpers/id-from-url.jsx @@ -5,7 +5,7 @@ */ function idFromUrl(string, index = 0) { if (typeof string === "string") { - return string.match(/[A-Za-z0-9]{26}/g)[index]; + return (string.match(/[A-Za-z0-9]{26}/g) ?? [])[index] ?? ""; } return ""; } From d2132261b0eea33a849e33f2ead664a4051e05b6 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 09:30:18 +0100 Subject: [PATCH 02/10] 6871: Avoid calling routes with null values --- assets/admin/components/screen/util/campaign-icon.jsx | 4 ++-- assets/admin/components/util/fetch-data-hook.js | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assets/admin/components/screen/util/campaign-icon.jsx b/assets/admin/components/screen/util/campaign-icon.jsx index fe40606c0..b61f13598 100644 --- a/assets/admin/components/screen/util/campaign-icon.jsx +++ b/assets/admin/components/screen/util/campaign-icon.jsx @@ -28,10 +28,10 @@ function CampaignIcon({ id, delay = 1000 }) { const { data: campaigns, isLoading } = useGetV2ScreensByIdCampaignsQuery( { id }, - { skip: !getData }, + { skip: !getData || !id }, ); const { data: groups, isLoading: isLoadingScreenGroups } = - useGetV2ScreensByIdScreenGroupsQuery({ id }, { skip: !getData }); + useGetV2ScreensByIdScreenGroupsQuery({ id }, { skip: !getData || !id }); useEffect(() => { if (campaigns) { diff --git a/assets/admin/components/util/fetch-data-hook.js b/assets/admin/components/util/fetch-data-hook.js index 656cf9afc..c60666098 100644 --- a/assets/admin/components/util/fetch-data-hook.js +++ b/assets/admin/components/util/fetch-data-hook.js @@ -10,6 +10,16 @@ function useFetchDataHook(apiCall, ids, params = {}, key = "id") { useEffect(() => { if (!ids || ids.length === 0) return; + // Filter out null/undefined/empty IDs + const validIds = ids.filter((id) => id != null && id !== ""); + if (validIds.length === 0) return; + + // Check if params contain invalid values + const hasInvalidParams = Object.values(params).some( + (value) => value === "" || value == null, + ); + if (hasInvalidParams) return; + async function fetchItems() { setLoading(true); From 15fa4b46f9d764c80d2e4b01499d0659e3c2cd22 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 09:37:31 +0100 Subject: [PATCH 03/10] 6871: Fixed double useEffect and function parameters instead of value outside scope --- .../admin/components/screen/screen-manager.jsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index 4ef4f184f..0d5899ce2 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -61,14 +61,6 @@ function ScreenManager({ { data: postData, error: saveErrorPost, isSuccess: isSaveSuccessPost }, ] = usePostV2ScreensMutation(); - /** If the screen is saved, display the success message */ - useEffect(() => { - if (isSaveSuccessPost || isSaveSuccessPut) { - displaySuccess(t("success-messages.saved-screen")); - setSavingScreen(false); - } - }, [isSaveSuccessPost, isSaveSuccessPut]); - /** If the screen is saved with error, display the error message */ useEffect(() => { if (saveErrorPut || saveErrorPost) { @@ -142,9 +134,7 @@ function ScreenManager({ * @returns {Array | null} A mapped array with playlist ids and weight * filtered by region id or null */ - function getPlaylistsByRegionId(regionId) { - const { playlists } = formStateObject; - + function getPlaylistsByRegionId(playlists, regionId) { return playlists .filter(({ region }) => idFromUrl(region) === idFromUrl(regionId)) .map((playlist, index) => { @@ -188,7 +178,7 @@ function ScreenManager({ // Add regionsId and connected playlists to the returnarray returnArray.push({ - playlists: getPlaylistsByRegionId(regionId), + playlists: getPlaylistsByRegionId(playlists, regionId), regionId: idFromUrl(regionId), }); }); @@ -274,7 +264,8 @@ function ScreenManager({ /** Handle submitting is done. */ useEffect(() => { - if (isSaveSuccessPut || isSaveSuccessPost) { + if (isSaveSuccessPost || isSaveSuccessPut) { + displaySuccess(t("success-messages.saved-screen")); setSavingScreen(false); if (saveWithoutClose) { From bd00ea442209af4c45fc9839ff5afb0feee73dda Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 09:40:27 +0100 Subject: [PATCH 04/10] 6871: Fixed saveWithoutClose replaced with a useRef --- assets/admin/components/screen/screen-manager.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index 0d5899ce2..0cf35cc1d 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { @@ -34,7 +34,7 @@ function ScreenManager({ initialState = null, }) { const { t } = useTranslation("common", { keyPrefix: "screen-manager" }); - const [saveWithoutClose, setSaveWithoutClose] = useState(false); + const saveWithoutCloseRef = useRef(false); const navigate = useNavigate(); const orientationOptions = [ { title: "Vertikal", "@id": "vertical" }, @@ -258,7 +258,7 @@ function ScreenManager({ }; const handleSubmitWithRedirect = () => { - setSaveWithoutClose(true); + saveWithoutCloseRef.current = true; handleSubmit(); }; @@ -268,8 +268,8 @@ function ScreenManager({ displaySuccess(t("success-messages.saved-screen")); setSavingScreen(false); - if (saveWithoutClose) { - setSaveWithoutClose(false); + if (saveWithoutCloseRef.current) { + saveWithoutCloseRef.current = false; if (isSaveSuccessPost) { navigate(`/screen/edit/${idFromUrl(postData["@id"])}`); From 3e035da3f3196646bad72dd5a6176bbf8d219191 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 09:47:04 +0100 Subject: [PATCH 05/10] 6871: Fixed inconsistencies --- assets/admin/components/screen/screen-manager.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index 0cf35cc1d..9711a1e74 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -77,7 +77,7 @@ function ScreenManager({ if (loadingError) { displayError(t("error-messages.load-screen-error", { id }), loadingError); } - }, [loadingError]); + }, [loadingError, id]); /** * Set state on change in input field @@ -257,7 +257,7 @@ function ScreenManager({ } }; - const handleSubmitWithRedirect = () => { + const handleSaveAndStay = () => { saveWithoutCloseRef.current = true; handleSubmit(); }; @@ -284,7 +284,7 @@ function ScreenManager({ <> {formStateObject && ( Date: Sat, 28 Mar 2026 10:30:54 +0100 Subject: [PATCH 06/10] 6871: Fixed issues with screen manager --- .../components/screen/screen-manager.jsx | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index 9711a1e74..f92020458 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -13,6 +13,15 @@ import { import idFromUrl from "../util/helpers/id-from-url"; import { set } from "lodash/object"; +const orientationOptions = [ + { title: "Vertikal", "@id": "vertical" }, + { title: "Horisontal", "@id": "horizontal" }, +]; +const resolutionOptions = [ + { title: "4K", "@id": "4K" }, + { title: "HD", "@id": "HD" }, +]; + /** * The screen manager component. * @@ -36,14 +45,6 @@ function ScreenManager({ const { t } = useTranslation("common", { keyPrefix: "screen-manager" }); const saveWithoutCloseRef = useRef(false); const navigate = useNavigate(); - const orientationOptions = [ - { title: "Vertikal", "@id": "vertical" }, - { title: "Horisontal", "@id": "horizontal" }, - ]; - const resolutionOptions = [ - { title: "4K", "@id": "4K" }, - { title: "HD", "@id": "HD" }, - ]; const headerText = saveMethod === "PUT" ? t("edit-screen-header") : t("create-screen-header"); const [loadingMessage, setLoadingMessage] = useState(""); @@ -86,8 +87,7 @@ function ScreenManager({ * @param {object} props.target - Event target. */ const handleInput = ({ target }) => { - let localFormStateObject = { ...formStateObject }; - localFormStateObject = JSON.parse(JSON.stringify(localFormStateObject)); + const localFormStateObject = JSON.parse(JSON.stringify(formStateObject)); set(localFormStateObject, target.id, target.value); setFormStateObject(localFormStateObject); }; @@ -118,9 +118,9 @@ function ScreenManager({ * * @returns {Array | null} A mapped array with group ids or null */ - function mapGroups() { - if (formStateObject?.inScreenGroups instanceof Array) { - return formStateObject.inScreenGroups.map((group) => { + function mapGroups(screenState) { + if (screenState?.inScreenGroups instanceof Array) { + return screenState.inScreenGroups.map((group) => { return idFromUrl(group); }); } @@ -136,7 +136,7 @@ function ScreenManager({ */ function getPlaylistsByRegionId(playlists, regionId) { return playlists - .filter(({ region }) => idFromUrl(region) === idFromUrl(regionId)) + .filter(({ region }) => idFromUrl(region) === regionId) .map((playlist, index) => { return { id: idFromUrl(playlist["@id"]), weight: index }; }); @@ -147,8 +147,9 @@ function ScreenManager({ * @param {Array} array The array to remove from. */ function removeFromArray(itemId, array) { - if (array.indexOf(itemId) >= 0) { - array.splice(array.indexOf(itemId), 1); + const index = array.indexOf(itemId); + if (index >= 0) { + array.splice(index, 1); } } @@ -179,19 +180,17 @@ function ScreenManager({ // Add regionsId and connected playlists to the returnarray returnArray.push({ playlists: getPlaylistsByRegionId(playlists, regionId), - regionId: idFromUrl(regionId), + regionId, }); }); // The remaining regions are added with empty playlist arrays. - if (regionIds.length > 0) { - regionIds.forEach((regionId) => - returnArray.push({ - playlists: [], - regionId: idFromUrl(regionId), - }), - ); - } + regionIds.forEach((regionId) => + returnArray.push({ + playlists: [], + regionId, + }), + ); return returnArray; } @@ -200,8 +199,8 @@ function ScreenManager({ * * @returns {string} Orientation or empty string */ - function getOrientation() { - const { orientation } = formStateObject; + function getOrientation(screenState) { + const { orientation } = screenState; return orientation ? orientation[0]["@id"] : ""; } @@ -210,8 +209,8 @@ function ScreenManager({ * * @returns {string} Resolution or empty string */ - function getResolution() { - const { resolution } = formStateObject; + function getResolution(screenState) { + const { resolution } = screenState; return resolution && resolution.length > 0 ? resolution[0]["@id"] : ""; } @@ -219,6 +218,7 @@ function ScreenManager({ const handleSubmit = () => { setSavingScreen(true); setLoadingMessage(t("loading-messages.saving-screen")); + const localFormStateObject = JSON.parse(JSON.stringify(formStateObject)); const { title, @@ -241,15 +241,13 @@ function ScreenManager({ layout, location, enableColorSchemeChange, - resolution: getResolution(), - groups: mapGroups(), - orientation: getOrientation(), + resolution: getResolution(localFormStateObject), + groups: mapGroups(localFormStateObject), + orientation: getOrientation(localFormStateObject), regions: mapPlaylistsWithRegion(localFormStateObject.playlists, localFormStateObject.regions), }), }; - setLoadingMessage(t("loading-messages.saving-screen")); - if (saveMethod === "POST") { PostV2Screens(saveData); } else if (saveMethod === "PUT") { @@ -278,7 +276,7 @@ function ScreenManager({ navigate("/screen/list"); } } - }, [isSaveSuccessPut, isSaveSuccessPost]); + }, [isSaveSuccessPut, isSaveSuccessPost, postData]); return ( <> From 3c96c46069945cd4f01696652f6c5c806d84526b Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 10:39:09 +0100 Subject: [PATCH 07/10] 6871: Fixed issues in screen manager --- assets/admin/components/screen/screen-manager.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index f92020458..9af15ff6b 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -71,14 +71,14 @@ function ScreenManager({ ); setSavingScreen(false); } - }, [saveErrorPut, saveErrorPost]); + }, [saveErrorPut, saveErrorPost, t]); /** If the screen is not loaded, display the error message */ useEffect(() => { if (loadingError) { displayError(t("error-messages.load-screen-error", { id }), loadingError); } - }, [loadingError, id]); + }, [loadingError, id, t]); /** * Set state on change in input field @@ -136,7 +136,7 @@ function ScreenManager({ */ function getPlaylistsByRegionId(playlists, regionId) { return playlists - .filter(({ region }) => idFromUrl(region) === regionId) + .filter(({ region }) => region === regionId) .map((playlist, index) => { return { id: idFromUrl(playlist["@id"]), weight: index }; }); @@ -201,7 +201,7 @@ function ScreenManager({ */ function getOrientation(screenState) { const { orientation } = screenState; - return orientation ? orientation[0]["@id"] : ""; + return orientation && orientation.length > 0 ? orientation[0]["@id"] : ""; } /** @@ -276,7 +276,7 @@ function ScreenManager({ navigate("/screen/list"); } } - }, [isSaveSuccessPut, isSaveSuccessPost, postData]); + }, [isSaveSuccessPut, isSaveSuccessPost, postData, navigate, t]); return ( <> From 7ad6727e1df2b1014cdc724e11a1561dc6423931 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Sat, 28 Mar 2026 10:41:19 +0100 Subject: [PATCH 08/10] 6871: Removed require for screen.location field --- assets/admin/components/screen/screen-form.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/admin/components/screen/screen-form.jsx b/assets/admin/components/screen/screen-form.jsx index 8f64516c0..0f141cf33 100644 --- a/assets/admin/components/screen/screen-form.jsx +++ b/assets/admin/components/screen/screen-form.jsx @@ -206,7 +206,6 @@ function ScreenForm({ Date: Sat, 28 Mar 2026 10:52:18 +0100 Subject: [PATCH 09/10] 6871: Applied coding standards --- CHANGELOG.md | 2 ++ assets/admin/components/screen/screen-manager.jsx | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc4968012..561ad259e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ All notable changes to this project will be documented in this file. - Aligned environment variable names. - Aligned with v. 2.6.0. - Added relations checksum feature flag. +- Fixes saving issues described in issue where saving resulted in infinite spinner. +- Fixed loading of routes containing null string values. ### NB! Prior to 3.x the project was split into separate repositories diff --git a/assets/admin/components/screen/screen-manager.jsx b/assets/admin/components/screen/screen-manager.jsx index 9af15ff6b..0c141b493 100644 --- a/assets/admin/components/screen/screen-manager.jsx +++ b/assets/admin/components/screen/screen-manager.jsx @@ -161,14 +161,16 @@ function ScreenManager({ function mapPlaylistsWithRegion(playlists, regions) { const returnArray = []; const regionIds = regions.map((r) => - typeof r === "string" ? idFromUrl(r, 1) : idFromUrl(r["@id"]) + typeof r === "string" ? idFromUrl(r, 1) : idFromUrl(r["@id"]), ); // The playlists all have a regionId, the following creates a unique list of relevant regions If there are not // playlists, then an empty playlist is to be saved per region let playlistRegions = []; if (playlists?.length > 0) { - playlistRegions = [...new Set(playlists.map(({ region }) => idFromUrl(region)))]; + playlistRegions = [ + ...new Set(playlists.map(({ region }) => idFromUrl(region))), + ]; } // Then the playlists are mapped by region Looping through the regions that have a playlist connected... @@ -244,7 +246,10 @@ function ScreenManager({ resolution: getResolution(localFormStateObject), groups: mapGroups(localFormStateObject), orientation: getOrientation(localFormStateObject), - regions: mapPlaylistsWithRegion(localFormStateObject.playlists, localFormStateObject.regions), + regions: mapPlaylistsWithRegion( + localFormStateObject.playlists, + localFormStateObject.regions, + ), }), }; From 6ad9c390d4e8187223f855dca10a6c10fe97ca00 Mon Sep 17 00:00:00 2001 From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com> Date: Mon, 30 Mar 2026 08:39:22 +0200 Subject: [PATCH 10/10] 6871: Removed unnecessary param check --- assets/admin/components/util/fetch-data-hook.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/assets/admin/components/util/fetch-data-hook.js b/assets/admin/components/util/fetch-data-hook.js index c60666098..0a7644329 100644 --- a/assets/admin/components/util/fetch-data-hook.js +++ b/assets/admin/components/util/fetch-data-hook.js @@ -14,12 +14,6 @@ function useFetchDataHook(apiCall, ids, params = {}, key = "id") { const validIds = ids.filter((id) => id != null && id !== ""); if (validIds.length === 0) return; - // Check if params contain invalid values - const hasInvalidParams = Object.values(params).some( - (value) => value === "" || value == null, - ); - if (hasInvalidParams) return; - async function fetchItems() { setLoading(true);