From 52dd44a3e8cc3abe52c4cc2a58bb5ad1860bb67d Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Wed, 15 Apr 2026 20:22:42 -0500 Subject: [PATCH] Add group-level scheduling, group playlist assignment, and persist audio unlock Phase 4 group scheduling: schema migration adds group_id to schedules with CHECK constraint, scheduler evaluates group+device schedules with priority, group deletion converts schedules to per-device copies. Dashboard gets playlist assignment dropdown and current playlist label on group headers. Player persists audio unlock state in localStorage so version reloads don't lose audio on unattended displays. Co-Authored-By: Claude Opus 4.6 --- frontend/js/views/dashboard.js | 48 ++++++++++++- frontend/js/views/schedule.js | 122 +++++++++++++++++++++++++++---- server/db/database.js | 58 +++++++++++++++ server/db/schema.sql | 7 +- server/player/index.html | 51 +++++++------ server/routes/device-groups.js | 49 ++++++++++++- server/routes/schedules.js | 127 +++++++++++++++++++++++++-------- server/routes/status.js | 10 +-- server/services/scheduler.js | 15 +++- 9 files changed, 411 insertions(+), 76 deletions(-) diff --git a/frontend/js/views/dashboard.js b/frontend/js/views/dashboard.js index f9dfa64..1194c8c 100644 --- a/frontend/js/views/dashboard.js +++ b/frontend/js/views/dashboard.js @@ -105,17 +105,35 @@ function renderDeviceCard(device) { `; } -function renderGroupSection(group, devices) { +function getGroupPlaylistLabel(devices, playlists) { + const playlistMap = new Map((playlists || []).map(p => [p.id, p])); + const assigned = devices.filter(d => d.playlist_id).map(d => d.playlist_id); + if (assigned.length === 0) return ''; + const unique = [...new Set(assigned)]; + if (unique.length === 1) { + const pl = playlistMap.get(unique[0]); + return pl ? esc(pl.name) : 'Unknown playlist'; + } + return 'Mixed playlists'; +} + +function renderGroupSection(group, devices, playlists) { const onlineCount = devices.filter(d => d.status === 'online').length; + const playlistLabel = getGroupPlaylistLabel(devices, playlists); return `
${esc(group.name)} ${devices.length} device${devices.length !== 1 ? 's' : ''} · ${onlineCount} online + ${playlistLabel ? `Playlist: ${playlistLabel}` : ''}
${devices.length > 0 ? ` + - ${devices.map(d => ``).join('')} + ${devices.map(d => ``).join('')} @@ -42,9 +50,40 @@ export async function render(container) {