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 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 `<button class="gmf-chip${off ? ' gmf-off' : ''}" data-gmf="${key}">
|
||||
<span class="gmf-dot" style="background:${color || '#4285f4'}"></span>${escHtml(name)}</button>`;
|
||||
};
|
||||
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');
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// Increment APP_VERSION with every code change
|
||||
export const APP_VERSION = 'v41';
|
||||
export const APP_VERSION = 'v42';
|
||||
|
||||
Reference in New Issue
Block a user