From 12c14e3c029c9168b5bd68a488f1dbdea326ff78 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Mon, 1 Jun 2026 18:16:25 +0200 Subject: [PATCH] feat(web): hide individual member calendars in the group view In the group overlay a per-member filter bar appears under the banner: tap a member (or the group calendar) to hide/show their events, Outlook-style. Filtering is client-side by event owner; members + colours load from the group detail. Bumped version. Co-Authored-By: Claude Opus 4.8 --- frontend/css/app.css | 26 ++++++++++++++++++ frontend/js/calendar.js | 59 ++++++++++++++++++++++++++++++++++++++++- frontend/js/i18n.js | 2 ++ frontend/js/version.js | 2 +- 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/frontend/css/app.css b/frontend/css/app.css index 563083b..2e31c0a 100644 --- a/frontend/css/app.css +++ b/frontend/css/app.css @@ -1840,6 +1840,32 @@ a { color: var(--primary); text-decoration: none; } font-size: 14px; color: var(--text-1); } +/* Per-member filter bar under the group banner (hide individual people). */ +.group-members-filter { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 8px 16px; + background: color-mix(in srgb, var(--accent) 7%, var(--bg-app)); + border-bottom: 1px solid var(--border); +} +.gmf-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 999px; + border: 1px solid var(--border); + background: var(--bg-surface); + color: var(--text-1); + font-size: 12.5px; + cursor: pointer; + transition: opacity var(--transition), background var(--transition); +} +.gmf-chip:hover { background: var(--bg-hover); } +.gmf-chip.gmf-off { opacity: 0.4; text-decoration: line-through; } +.gmf-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; } + .group-item-active { background: var(--bg-surface); background: color-mix(in srgb, var(--accent) 18%, transparent); diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index a95d4fb..c08e40a 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -50,6 +50,8 @@ let state = { selectedEventColor: '', // '' = use calendar color groups: [], activeGroupId: null, // when set, the calendar shows the combined group view + activeGroupDetail: null, // full detail (members + colours) of the active group + hiddenGroupMembers: new Set(), // member ids / 'gc' hidden in the group overlay }; // ── URL state ──────────────────────────────────────────── @@ -456,6 +458,16 @@ function renderView() { } function filterEvents(events) { + // Group overlay: hide individual members' calendars / the group calendar + // (Outlook-style), filtered client-side by event owner. + const hidden = state.hiddenGroupMembers; + if (state.activeGroupId && hidden && hidden.size) { + return events.filter(ev => { + if (ev.is_group_event) return !hidden.has('gc'); + const oid = ev.owner && ev.owner.id; + return oid == null || !hidden.has(oid); + }); + } // If dimPast is enabled, events are still shown but CSS handles opacity via .past class return events; } @@ -2316,23 +2328,68 @@ function openGroupEventModal() { updatePrivateRow(false); } -function enterGroupView(groupId) { +async function enterGroupView(groupId) { state.activeGroupId = groupId; + state.hiddenGroupMembers = new Set(); + state.activeGroupDetail = null; const g = state.groups.find(x => x.id === groupId); document.getElementById('group-view-banner').classList.remove('hidden'); document.getElementById('group-view-label').textContent = t('group_view_label', { name: g ? g.name : '' }); renderGroupList(); + // Load the member list (with server colours) for the per-member filter. + try { state.activeGroupDetail = await api.get(`/groups/${groupId}`); } catch (e) { /* ignore */ } + renderGroupMemberFilter(); fetchAndRender(true); } function exitGroupView() { state.activeGroupId = null; + state.activeGroupDetail = null; + state.hiddenGroupMembers = new Set(); document.getElementById('group-view-banner').classList.add('hidden'); + renderGroupMemberFilter(); renderGroupList(); fetchAndRender(true); } +// Per-member filter bar shown under the group banner: tap a member (or the +// group calendar) to hide/show their events in the combined overlay. +function renderGroupMemberFilter() { + const banner = document.getElementById('group-view-banner'); + if (!banner) return; + let bar = document.getElementById('group-members-filter'); + if (!state.activeGroupId || !state.activeGroupDetail) { + if (bar) bar.remove(); + return; + } + if (!bar) { + bar = document.createElement('div'); + bar.id = 'group-members-filter'; + bar.className = 'group-members-filter'; + banner.insertAdjacentElement('afterend', bar); + } + const g = state.activeGroupDetail; + const chip = (key, name, color) => { + const off = state.hiddenGroupMembers.has(key); + return ``; + }; + const parts = (g.members || []).map(m => chip(String(m.id), m.display_name || '—', m.color)); + parts.push(chip('gc', t('group_calendar'), g.group_calendar_color)); + bar.innerHTML = parts.join(''); + bar.querySelectorAll('[data-gmf]').forEach(b => { + b.addEventListener('click', () => { + const raw = b.dataset.gmf; + const key = raw === 'gc' ? 'gc' : parseInt(raw); + if (state.hiddenGroupMembers.has(key)) state.hiddenGroupMembers.delete(key); + else state.hiddenGroupMembers.add(key); + renderGroupMemberFilter(); + renderView(); + }); + }); +} + // Open the group modal in create mode (no id) or manage mode (existing group). async function openGroupModal(groupId) { const modal = document.getElementById('modal-group'); diff --git a/frontend/js/i18n.js b/frontend/js/i18n.js index 1ce77f2..3fb1d3f 100644 --- a/frontend/js/i18n.js +++ b/frontend/js/i18n.js @@ -124,6 +124,7 @@ const translations = { group_created: 'Gruppe erstellt', group_view_label: 'Gruppenansicht: {name}', group_exit: 'Gruppenansicht verlassen', + group_calendar: 'Gruppenkalender', group_icon: 'Icon', group_visible_flag: 'Für deine Gruppen sichtbar', group_new_event: '+ Gruppentermin', @@ -396,6 +397,7 @@ const translations = { group_created: 'Group created', group_view_label: 'Group view: {name}', group_exit: 'Exit group view', + group_calendar: 'Group calendar', group_icon: 'Icon', group_visible_flag: 'Visible to your groups', group_new_event: '+ Group event', diff --git a/frontend/js/version.js b/frontend/js/version.js index c13fb25..468d31d 100644 --- a/frontend/js/version.js +++ b/frontend/js/version.js @@ -1,2 +1,2 @@ // Increment APP_VERSION with every code change -export const APP_VERSION = 'v41'; +export const APP_VERSION = 'v42';