feat: server-definierte Gruppenfarben (per API) + Gruppentermine überall erstellen

- Pro Mitglied eine Farbe (group_members.color, auto aus Palette, vom Owner
  oder Mitglied selbst änderbar via PUT /groups/{id}/members/{uid}/color).
- Gruppentermin-Farbe = Farbe des Gruppenkalenders.
- API liefert Farben aus: GET /groups & /groups/{id} (member.color,
  group_calendar_color), GET /groups/{id}/combined (display_color pro Event)
  -> Apps können dieselben Farben anzeigen. Test ergänzt (18 grün).
- Web nutzt display_color; Gruppenkalender im Termin-Editor mit 👥 markiert
  (Gruppentermine ohne Gruppenansicht erstellbar); Mitglieder-Farben im
  Verwalten-Dialog editierbar. Version v37.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-05-31 18:52:40 +02:00
parent b0f1497bc8
commit a992d97796
8 changed files with 150 additions and 14 deletions

View File

@@ -299,17 +299,16 @@ async function fetchAndRender(force = false, silent = false) {
// cryptic initials); your own events stay unprefixed. Colour-code by
// owner so each member reads as a group.
let title = ev.title;
let color;
if (ev.is_group_event) {
// Group calendar: everyone adds; mark who added it.
const cName = ev.creator && ev.creator.display_name;
const cId = ev.creator && ev.creator.id;
title = (cName && cId !== me.id) ? `👥 ${firstName(cName)}: ${ev.title}` : `👥 ${ev.title}`;
color = ownerColor(cId) || ev.color;
} else {
if (ownerName && !isMine) title = `${firstName(ownerName)}: ${ev.title}`;
color = ownerColor(ownerId) || ev.color;
} else if (ownerName && !isMine) {
title = `${firstName(ownerName)}: ${ev.title}`;
}
// Server-defined colour (member colour / group colour) so web + apps match.
const color = ev.display_color || ownerColor(ownerId) || ev.color;
return { ...ev, title, color };
});
eventCache.start = null; eventCache.end = null; // invalidate normal cache
@@ -1429,11 +1428,12 @@ function populateCalendarSelect(selectedId) {
sel.appendChild(opt);
});
});
// Local calendars
// Local calendars (group calendars marked with 👥 so group events can be
// created from anywhere, not just the group view).
state.localCalendars.filter(c => !c.sidebar_hidden).forEach(cal => {
const opt = document.createElement('option');
opt.value = `local-${cal.id}`;
opt.textContent = cal.name;
opt.textContent = cal.group ? `👥 ${cal.name}` : cal.name;
if (`local-${cal.id}` === selectedId) opt.selected = true;
sel.appendChild(opt);
});
@@ -2358,9 +2358,41 @@ async function openGroupModal(groupId) {
modal.__memberIds = new Set([...existingMemberIds].filter(id => id !== me.id));
renderGroupMemberPicker();
// Member colours (edit mode only): owner can recolour any member.
const colorsGroup = document.getElementById('group-colors-group');
if (isEdit && detail) {
colorsGroup.style.display = '';
renderGroupMemberColors(groupId, detail.members || []);
} else {
colorsGroup.style.display = 'none';
}
openModal('modal-group');
}
function renderGroupMemberColors(groupId, members) {
const el = document.getElementById('group-member-colors');
if (!el) return;
el.innerHTML = members.map(m =>
`<div class="pick-row" style="cursor:default">
<span class="cal-item-dot group-color-dot" data-uid="${m.id}" data-color="${m.color || '#4285f4'}"
style="background:${m.color || '#4285f4'};cursor:pointer" title="${t('change_color')}"></span>
<span class="pick-name">${escHtml(m.display_name || '')}</span>
</div>`
).join('');
el.querySelectorAll('.group-color-dot').forEach(dot => {
dot.addEventListener('click', async () => {
const picked = await openColorPicker(dot, dot.dataset.color);
if (!picked) return;
try {
await api.put(`/groups/${groupId}/members/${dot.dataset.uid}/color`, { color: picked });
dot.style.background = picked;
dot.dataset.color = picked;
} catch (e) { showToast(e.message, true); }
});
});
}
const GROUP_ICONS = ['👥', '👨‍👩‍👧', '🏠', '❤️', '🧑‍🤝‍🧑', '⚽', '🎓', '💼', '🎉', '🐶', '✈️', '🎵', '🍕', '📚', '🌳', '⭐'];
function renderGroupIconPicker() {
const modal = document.getElementById('modal-group');

View File

@@ -124,6 +124,7 @@ const translations = {
group_icon: 'Icon',
group_visible_flag: 'Für deine Gruppen sichtbar',
group_new_event: '+ Gruppentermin',
group_member_colors: 'Farben der Mitglieder',
only_owner_color: 'Nur der Besitzer kann die Farbe ändern',
upload_too_large: 'Datei zu groß (Server-Limit). Bitte Upload-Limit erhöhen.',
shared_with_me: 'Mit dir geteilt',
@@ -392,6 +393,7 @@ const translations = {
group_icon: 'Icon',
group_visible_flag: 'Visible to your groups',
group_new_event: '+ Group event',
group_member_colors: 'Member colours',
only_owner_color: 'Only the owner can change the colour',
upload_too_large: 'File too large (server limit). Please raise the upload limit.',
shared_with_me: 'Shared with me',

View File

@@ -1,2 +1,2 @@
// Increment APP_VERSION with every code change
export const APP_VERSION = 'v36';
export const APP_VERSION = 'v37';