From e8a13ba33c6c2da1e12b25340673fd664b5856fc Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 31 May 2026 16:37:08 +0200 Subject: [PATCH] feat: Gruppen im Web-Frontend + Gruppenkalender in /local/calendars - Sidebar-Sektion "Gruppen": Liste, Erstellen (Name + Mitglieder-Picker), Verwalten (Mitglieder hinzufuegen/entfernen), Loeschen. - Gruppenansicht: laedt /api/groups/{id}/combined fuer den sichtbaren Bereich; Event-Titel werden mit Besitzer-Initialen bzw. Gruppen-Icon praefixt; Banner mit "Gruppenansicht verlassen". - Server: GET /api/local/calendars liefert nun auch Gruppenkalender (group:true, read_write) fuer Mitglieder, damit sie im Editor waehlbar sind. Test ergaenzt (13 gruen). Co-Authored-By: Claude Opus 4.8 --- backend/routers/local_router.py | 22 +++- backend/tests/test_collaboration.py | 13 ++ frontend/css/app.css | 23 ++++ frontend/index.html | 41 +++++++ frontend/js/calendar.js | 183 ++++++++++++++++++++++++++++ frontend/js/i18n.js | 28 +++++ 6 files changed, 309 insertions(+), 1 deletion(-) diff --git a/backend/routers/local_router.py b/backend/routers/local_router.py index 7144c8b..a4a9a5d 100644 --- a/backend/routers/local_router.py +++ b/backend/routers/local_router.py @@ -105,16 +105,36 @@ def list_calendars( .filter(models.CalendarShare.user_id == current_user.id) .all() ) + seen_ids = {c.id for c in own} for share in shares: cal = share.calendar - if cal is None: + if cal is None or cal.id in seen_ids: continue + seen_ids.add(cal.id) owner = db.query(models.User).filter(models.User.id == cal.user_id).first() result.append(_cal_dict( cal, owned=False, shared_by=owner.username if owner else None, permission=share.permission, )) + + # Group calendars the user can reach via membership (read_write), so members + # can select the group calendar in the editor and see it in their list. + group_cals = ( + db.query(models.LocalCalendar, models.Group.name) + .join(models.GroupCalendar, models.GroupCalendar.calendar_id == models.LocalCalendar.id) + .join(models.Group, models.Group.id == models.GroupCalendar.group_id) + .join(models.GroupMember, models.GroupMember.group_id == models.GroupCalendar.group_id) + .filter(models.GroupMember.user_id == current_user.id) + .all() + ) + for cal, group_name in group_cals: + if cal.id in seen_ids: + continue + seen_ids.add(cal.id) + d = _cal_dict(cal, owned=False, shared_by=group_name, permission="read_write") + d["group"] = True + result.append(d) return result diff --git a/backend/tests/test_collaboration.py b/backend/tests/test_collaboration.py index 740cc24..4f3eb45 100644 --- a/backend/tests/test_collaboration.py +++ b/backend/tests/test_collaboration.py @@ -134,6 +134,19 @@ def test_group_members_can_write_group_calendar(client): assert r.status_code == 200, r.text +def test_group_calendar_listed_for_member(client): + admin = register_admin(client) + b_id, b_tok = create_user(client, admin, "bob") + group = client.post("/api/groups/", headers=auth(admin), + json={"name": "Team", "member_ids": [b_id]}).json() + gcal = group["group_calendar_id"] + # Bob (member, not owner) sees the group calendar in his local list, flagged. + cals = client.get("/api/local/calendars", headers=auth(b_tok)).json() + gc = [c for c in cals if c["id"] == gcal] + assert gc and gc[0].get("group") is True + assert gc[0]["permission"] == "read_write" and gc[0]["owned"] is False + + def test_combined_view_marks_owner_and_group_event(client): admin = register_admin(client) b_id, b_tok = create_user(client, admin, "bob") diff --git a/frontend/css/app.css b/frontend/css/app.css index e519df7..0072ec8 100644 --- a/frontend/css/app.css +++ b/frontend/css/app.css @@ -1801,3 +1801,26 @@ a { color: var(--primary); text-decoration: none; } color: var(--text-2); font-style: italic; } + +/* ── Groups ─────────────────────────────────────────────────── */ +.group-view-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 8px 16px; + background: rgba(66, 133, 244, 0.12); + border-bottom: 1px solid var(--border); + font-size: 14px; + color: var(--text-1); +} +.group-item-active { + background: rgba(66, 133, 244, 0.15); + border-radius: 8px; +} +.group-item .cal-item-name { cursor: pointer; flex: 1; } +.cal-list-empty { + padding: 6px 4px; + font-size: 13px; + color: var(--text-3); +} diff --git a/frontend/index.html b/frontend/index.html index df32297..a1f3bf8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -198,6 +198,17 @@
+ + +
+
+ Gruppen + +
+
+
@@ -205,6 +216,10 @@
+
@@ -349,6 +364,32 @@ + + +