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:
@@ -386,6 +386,10 @@
|
||||
<label data-i18n="group_members">Mitglieder</label>
|
||||
<div id="group-member-picker" class="share-user-picker"></div>
|
||||
</div>
|
||||
<div class="form-group" id="group-colors-group" style="display:none">
|
||||
<label data-i18n="group_member_colors">Farben der Mitglieder</label>
|
||||
<div id="group-member-colors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-danger hidden" id="group-delete" data-i18n="group_delete">Gruppe löschen</button>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// Increment APP_VERSION with every code change
|
||||
export const APP_VERSION = 'v36';
|
||||
export const APP_VERSION = 'v37';
|
||||
|
||||
Reference in New Issue
Block a user