From 51efd98e7df10a0c09e084f50bb4407ac477aace Mon Sep 17 00:00:00 2001
From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com>
Date: Fri, 1 May 2026 15:34:57 +0200
Subject: [PATCH 1/3] 7303: Applied BRND changes from admin release/2.7.0
---
.../feed-sources/feed-source-manager.jsx | 7 ++++++-
.../feed-sources/templates/brnd-feed-type.jsx | 15 +++++++++++++++
assets/admin/translations/da/common.json | 3 ++-
docs/v2-changelogs/admin.md | 6 ++++++
docs/v2-changelogs/api.md | 2 ++
5 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/assets/admin/components/feed-sources/feed-source-manager.jsx b/assets/admin/components/feed-sources/feed-source-manager.jsx
index 8f85d8ab..6230db1e 100644
--- a/assets/admin/components/feed-sources/feed-source-manager.jsx
+++ b/assets/admin/components/feed-sources/feed-source-manager.jsx
@@ -114,7 +114,12 @@ function FeedSourceManager({
value: "App\\Feed\\BrndFeedType",
title: t("brnd-feed-type.title"),
key: "BrndFeedType",
- secretsDefault: {},
+ secretsDefault: {
+ api_base_uri: "",
+ company_id: "",
+ api_auth_key: "",
+ api_version: "1.0",
+ },
},
];
diff --git a/assets/admin/components/feed-sources/templates/brnd-feed-type.jsx b/assets/admin/components/feed-sources/templates/brnd-feed-type.jsx
index 035ad3fd..c5b2c66c 100644
--- a/assets/admin/components/feed-sources/templates/brnd-feed-type.jsx
+++ b/assets/admin/components/feed-sources/templates/brnd-feed-type.jsx
@@ -1,11 +1,16 @@
import React from "react";
import { useTranslation } from "react-i18next";
import FormInput from "../../util/forms/form-input";
+import Select from "../../util/forms/select";
const BrndFeedType = ({ handleInput, formStateObject, mode }) => {
const { t } = useTranslation("common", {
keyPrefix: "brnd-feed-type",
});
+ const apiVersionOptions = [
+ { key: "api-version-1-0", title: "1.0", value: "1.0" },
+ { key: "api-version-2-0", title: "2.0", value: "2.0" },
+ ];
return (
<>
@@ -43,6 +48,16 @@ const BrndFeedType = ({ handleInput, formStateObject, mode }) => {
}
value={formStateObject?.api_auth_key}
/>
+
+
>
);
};
diff --git a/assets/admin/translations/da/common.json b/assets/admin/translations/da/common.json
index 2ae6cba5..77e8a696 100644
--- a/assets/admin/translations/da/common.json
+++ b/assets/admin/translations/da/common.json
@@ -1166,7 +1166,8 @@
"company-id": "Company ID",
"api-auth-key": "API Auth Key",
"values-info": "Værdierne Company ID og API Auth Key udleveres af BRND. Ret henvendelse til BRND support.",
- "redacted-value-input-placeholder": "Skjult værdi"
+ "redacted-value-input-placeholder": "Skjult værdi",
+ "api-version": "API Version"
},
"event-database-api-v2-feed-type": {
"title": "Event databasen v.2",
diff --git a/docs/v2-changelogs/admin.md b/docs/v2-changelogs/admin.md
index 861678f6..c867d52c 100644
--- a/docs/v2-changelogs/admin.md
+++ b/docs/v2-changelogs/admin.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [2.7.0] - 2026-05-01
+
+- [#299](https://github.com/os2display/display-admin-client/pull/299)
+ - Added api_version configuration field to BRND feed source settings, allowing administrators to specify which BRND
+ API version to use.
+
## [2.6.1] - 2026-03-06
- [#295](https://github.com/os2display/display-admin-client/pull/295)
diff --git a/docs/v2-changelogs/api.md b/docs/v2-changelogs/api.md
index a2bdff77..a8c3895b 100644
--- a/docs/v2-changelogs/api.md
+++ b/docs/v2-changelogs/api.md
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [2.7.0] - 2026-05-01
+
- [#363](https://github.com/os2display/display-api-service/pull/363)
- Added optional 'area' and 'facility' configuration fields
- [#362](https://github.com/os2display/display-api-service/pull/362)
From cf8b4d6bf00407ea99c8bd5637ba1a48140fd940 Mon Sep 17 00:00:00 2001
From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com>
Date: Fri, 1 May 2026 15:57:11 +0200
Subject: [PATCH 2/3] 7303: Added changes from client release/2.7.0
---
assets/shared/templates/brnd.json | 15 ++
assets/shared/templates/brnd.jsx | 10 +
.../templates/brnd/brnd-idraetsanlaeg.jsx | 245 ++++++++++++++++++
assets/template/fixtures/slide-fixtures.js | 125 +++++++++
docs/v2-changelogs/template.md | 9 +
5 files changed, 404 insertions(+)
create mode 100644 assets/shared/templates/brnd/brnd-idraetsanlaeg.jsx
diff --git a/assets/shared/templates/brnd.json b/assets/shared/templates/brnd.json
index 7fdea49e..565f1cea 100644
--- a/assets/shared/templates/brnd.json
+++ b/assets/shared/templates/brnd.json
@@ -44,9 +44,24 @@
"key": "layout1",
"title": "Sportcenter Dagsprogram",
"value": "sportcenter-today"
+ },
+ {
+ "key": "layout2",
+ "title": "Idrætsanlæg - Dagsprogram",
+ "value": "idraetsanlaeg"
}
]
},
+ {
+ "key": "brnd-form-duration",
+ "input": "duration",
+ "name": "duration",
+ "type": "number",
+ "min": "1",
+ "label": "Varighed pr. slide (i sekunder)",
+ "helpText": "Her skal du skrive varigheden på slidet.",
+ "formGroupClasses": "col-md-6 mb-3"
+ },
{
"key": "brnd-form-form-14",
"input": "select",
diff --git a/assets/shared/templates/brnd.jsx b/assets/shared/templates/brnd.jsx
index cb84d768..17d442d7 100644
--- a/assets/shared/templates/brnd.jsx
+++ b/assets/shared/templates/brnd.jsx
@@ -8,6 +8,7 @@ import {
getFirstMediaUrlFromField,
ThemeStyles,
} from "../slide-utils/slide-util";
+import BrndIdraetsanlaeg from "./brnd/brnd-idraetsanlaeg";
import BrndSportcenterToday from "./brnd/brnd-sportcenter-today";
import GlobalStyles from "../slide-utils/GlobalStyles";
import "./brnd/brnd.scss";
@@ -105,6 +106,15 @@ function Brnd({ slide, content, run, slideDone, executionId }) {
getTitle={getTitle}
/>
)}
+ {layout === "idraetsanlaeg" && (
+
+ )}
diff --git a/assets/shared/templates/brnd/brnd-idraetsanlaeg.jsx b/assets/shared/templates/brnd/brnd-idraetsanlaeg.jsx
new file mode 100644
index 00000000..b26edfe6
--- /dev/null
+++ b/assets/shared/templates/brnd/brnd-idraetsanlaeg.jsx
@@ -0,0 +1,245 @@
+import React, { Fragment, useEffect, useState } from "react";
+import PropTypes from "prop-types";
+import dayjs from "dayjs";
+import localeDa from "dayjs/locale/da";
+import localizedFormat from "dayjs/plugin/localizedFormat";
+import styled from "styled-components";
+
+/**
+ * BRND Idrætsanlæg dagsprogram.
+ *
+ * @param {object} props Component props.
+ * @param {object} props.content Slide content.
+ * @param {Array} props.bookings Booking entries.
+ * @param {string[]} [props.templateClasses] Template class names. Default is `[]`.
+ * @param {Function} props.getTitle Function to normalize displayed text.
+ * @returns {JSX.Element} BRND Idrætsanlæg Dagsprogram layout.
+ */
+function BrndIdraetsanlaeg({
+ content,
+ bookings,
+ templateClasses = [],
+ getTitle,
+}) {
+ const [currentDate, setCurrentDate] = useState(new Date());
+ const { title = "" } = content;
+
+ useEffect(() => {
+ dayjs.extend(localizedFormat);
+ const dateAndTimeInterval = setInterval(
+ () => setCurrentDate(new Date()),
+ 1000,
+ );
+
+ return () => clearInterval(dateAndTimeInterval);
+ }, []);
+
+ // Sort and keep only current/future bookings from today.
+ const getSortedBookings = (data) => {
+ const now = dayjs();
+
+ return data
+ .filter((entry) => {
+ const startDate = dayjs(entry.startTime * 1000);
+
+ return entry.endTime > now.unix() && startDate.date() === now.date();
+ })
+ .sort((a, b) => a.startTime - b.startTime);
+ };
+
+ const capitalize = (text) => {
+ if (!text) {
+ return "";
+ }
+
+ return text.charAt(0).toUpperCase() + text.slice(1);
+ };
+
+ return (
+
+ {title}
+
+ {currentDate &&
+ capitalize(dayjs().locale(localeDa).format("dddd D. MMMM HH:mm"))}
+
+
+
+
+
+ Hvornår
+
+
+ Hvor
+
+
+ Hvad
+
+
+ Hvem
+
+
+ Bemærkninger
+
+
+
+
+ {bookings?.length > 0 &&
+ getSortedBookings(bookings).map((entry, rowIndex) => (
+
+
+ {dayjs(entry.startTime * 1000)
+ .locale(localeDa)
+ .format("LT")}
+ {entry.endTime && (
+ <>
+ -
+ {dayjs(entry.endTime * 1000)
+ .locale(localeDa)
+ .format("LT")}
+ >
+ )}
+
+
+ {getTitle(entry.area)}
+
+
+ {getTitle(entry.activity)}
+
+
+ {getTitle(entry.bookingBy)}
+
+
+ {entry.remarks ?? ""}
+
+
+ ))}
+
+
+
+ );
+}
+
+const Wrapper = styled.div`
+ font-family: var(--font-family-base);
+ font-size: var(--font-size-base);
+ overflow: hidden;
+ height: 100%;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-color: var(--color-white);
+ color: var(--color-white);
+ display: grid;
+ grid-template-areas:
+ "title"
+ "date"
+ "content";
+ grid-template-rows: auto auto 1fr;
+ padding: 0;
+`;
+
+const DateRow = styled.div`
+ grid-area: date;
+ background-color: rgb(0, 12, 46);
+ color: var(--color-white);
+ text-align: center;
+ padding: 0 calc(var(--padding-size-base) * 0.7)
+ calc(var(--padding-size-base) * 0.7);
+ font-size: var(--h4-font-size);
+ font-weight: var(--font-weight-light);
+`;
+
+const Title = styled.div`
+ background-color: rgb(0, 12, 46);
+ font-size: var(--h4-font-size);
+ font-weight: var(--font-weight-bold);
+ text-align: center;
+ text-transform: uppercase;
+ color: var(--color-white);
+ padding: calc(var(--padding-size-base) * 0.7);
+`;
+
+const Content = styled.div`
+ background-color: rgb(0, 12, 46);
+ grid-area: content;
+`;
+
+const ContentItemsWrapper = styled.div`
+ display: grid;
+ grid-template-columns: 0.9fr 1fr 1.1fr 1.5fr 1.7fr;
+`;
+
+const ContentItem = styled.div`
+ display: flex;
+ align-items: center;
+ color: var(--color-black);
+ padding: calc(var(--padding-size-base) * 0.55);
+
+ &.row-even {
+ background-color: #f1f3f5;
+ }
+
+ &.row-odd {
+ background-color: var(--color-white);
+ }
+`;
+
+const ContentHeaderItem = styled.div`
+ display: flex;
+ align-items: center;
+ border-bottom: 1px solid var(--color-grey-600);
+ border-top: 1px solid var(--color-grey-600);
+ background-color: rgb(0, 12, 46);
+ color: var(--color-white);
+ font-weight: var(--font-weight-bold);
+ padding: calc(var(--padding-size-base) * 0.55);
+`;
+
+BrndIdraetsanlaeg.propTypes = {
+ templateClasses: PropTypes.arrayOf(PropTypes.string),
+ bookings: PropTypes.arrayOf(
+ PropTypes.shape({
+ bookingcode: PropTypes.string.isRequired,
+ remarks: PropTypes.string,
+ startTime: PropTypes.number.isRequired,
+ endTime: PropTypes.number,
+ complex: PropTypes.string.isRequired,
+ area: PropTypes.string.isRequired,
+ facility: PropTypes.string.isRequired,
+ activity: PropTypes.string.isRequired,
+ status: PropTypes.string.isRequired,
+ checkIn: PropTypes.bool,
+ bookingBy: PropTypes.string.isRequired,
+ changingRooms: PropTypes.string,
+ }),
+ ).isRequired,
+ content: PropTypes.shape({
+ title: PropTypes.string,
+ }).isRequired,
+ getTitle: PropTypes.func.isRequired,
+};
+
+export default BrndIdraetsanlaeg;
diff --git a/assets/template/fixtures/slide-fixtures.js b/assets/template/fixtures/slide-fixtures.js
index 55691a47..287e3ba8 100644
--- a/assets/template/fixtures/slide-fixtures.js
+++ b/assets/template/fixtures/slide-fixtures.js
@@ -153,6 +153,131 @@ const slideFixtures = [
fontSize: "font-size-xl",
},
},
+ {
+ id: "brnd-idraetsanlaeg",
+ templateData: {
+ id: "01K8TNT13XVCYYPXBJBBJVFENS",
+ },
+ type: "brnd",
+ themeFile: null,
+ feedData: {
+ bookings: [
+ {
+ bookingcode: "BKN-389928",
+ remarks: "",
+ startTime: dayjs().subtract(30, "minutes").unix(),
+ endTime: dayjs().add(2, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Svømmehal",
+ facility: "Svømmehal",
+ activity: "Svømning",
+ status: "Tildelt tid",
+ checkIn: false,
+ bookingBy: "Humlehøj-Hallerne",
+ changingRooms: "",
+ },
+ {
+ bookingcode: "BKN-363980",
+ remarks: "",
+ startTime: dayjs().add(1, "hour").add(30, "minutes").unix(),
+ endTime: dayjs().add(2, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Svømmehal",
+ facility: "Svømmehal",
+ activity: "Svømning",
+ status: "Tildelt tid",
+ checkIn: false,
+ bookingBy: "Offentlig svømning",
+ changingRooms: "",
+ },
+ {
+ bookingcode: "BKN-389984",
+ remarks: "Ekstratræning",
+ startTime: dayjs().add(2, "hour").unix(),
+ endTime: dayjs().add(3, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Hal 1",
+ facility: "Hal 1",
+ activity: "Fodbold",
+ status: "Tildelt tid",
+ checkIn: false,
+ bookingBy: "Dybbøl Fodbold Boys",
+ changingRooms: "",
+ },
+ {
+ bookingcode: "BKN-364281",
+ remarks: "",
+ startTime: dayjs().add(3, "hour").add(15, "minutes").unix(),
+ endTime: dayjs().add(4, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Motionsrum",
+ facility: "Motionsrum",
+ activity: "Styrketræning",
+ status: "Tildelt tid",
+ checkIn: false,
+ bookingBy: "Aahoo",
+ changingRooms: "",
+ },
+ {
+ bookingcode: "BKN-355806",
+ remarks: "",
+ startTime: dayjs().add(1, "hour").add(15, "minutes").unix(),
+ endTime: dayjs().add(2, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Spring- og klatrecenter",
+ facility: "Klatrevæg",
+ activity: "Klatring",
+ status: "Tildelt tid",
+ checkIn: false,
+ bookingBy: "Sønderborg Klatreklub",
+ changingRooms: "",
+ },
+ {
+ bookingcode: "BKN-306748",
+ remarks: "",
+ startTime: dayjs().add(5, "hour").unix(),
+ endTime: dayjs().add(8, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Spring- og klatrecenter",
+ facility: "Klatrevæg",
+ activity: "Klatring",
+ status: "Aflyst",
+ checkIn: false,
+ bookingBy: "Sønderborg Klatreklub",
+ changingRooms: "",
+ },
+ {
+ bookingcode: "BKN-395955",
+ remarks: "Bestyrelsesmøde",
+ startTime: dayjs().add(3, "hour").add(15, "minutes").unix(),
+ endTime: dayjs().add(4, "hour").unix(),
+ complex: "Humlehøj Hallen",
+ area: "Mødelokaler",
+ facility: "Mødelokale 1",
+ activity: "Møder",
+ status: "Tildelt tid",
+ checkIn: false,
+ bookingBy: "Kultur, Idræt og Fritid",
+ changingRooms: "",
+ },
+ ],
+ },
+ mediaData: {
+ "/v1/media/00000000000000000000000001": {
+ assets: {
+ uri: "/fixtures/template/images/centerhallen.png",
+ },
+ },
+ },
+ content: {
+ duration: 5000,
+ layout: "idraetsanlaeg",
+ title: "Idrætsanlæg - Dagsprogram",
+ mediaContain: false,
+ image: ["/v1/media/00000000000000000000000001"],
+ fontSize: "font-size-xl",
+ },
+ },
{
id: "calendar-0-multiple-days",
templateData: {
diff --git a/docs/v2-changelogs/template.md b/docs/v2-changelogs/template.md
index 793c8299..4ed09f2f 100644
--- a/docs/v2-changelogs/template.md
+++ b/docs/v2-changelogs/template.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
## Unreleased
+## [2.7.0] - 2026-05-01
+
+- [#199](https://github.com/os2display/display-templates/pull/199)
+ - Added `idraetsanlaeg` BRND layout with column order: Hvornår, Hvor, Hvad, Hvem, Bemærkninger.
+ - Added KK-specific styling updates (dark header area, zebra-striped rows, centered date row, adjusted spacing/alignment).
+ - Added local slide example for BRND `idraetsanlaeg` layout.
+- [#198](https://github.com/os2display/display-templates/pull/198)
+ - Added field for slide duration in BRND template.
+
## [2.6.1] - 2026-03-06
- [#196](https://github.com/os2display/display-templates/pull/196)
From 7407aecd18df07726379c827526fe783ee516cdc Mon Sep 17 00:00:00 2001
From: Troels Ugilt Jensen <6103205+tuj@users.noreply.github.com>
Date: Fri, 1 May 2026 16:03:02 +0200
Subject: [PATCH 3/3] 7303: Updated changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5eedbdb6..e3f38a3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@ All notable changes to this project will be documented in this file.
- Changed default CLIENT_PULL_STRATEGY_INTERVAL value to 10 minutes.
- Changed code analysis tool from psalm to phpstan.
- Changed src/Controller/Api/AuthOidcController.php to get session from request.
+- Aligned with release/2.7.0.
### NB! Prior to 3.x the project was split into separate repositories