feat: flache sortierbare Kalenderliste (Drag&Drop) + Fixes

- Sidebar: eine flache Kalenderliste statt Quellen-Gruppen; Quelle/Konto
  klein-grau inline rechts neben dem Namen; per Drag&Drop sortierbar
  (Reihenfolge pro Geraet in localStorage).
- Gruppenkalender serverseitig auch beim Besitzer als group:true markiert
  -> erscheint nicht mehr in der "Fuer Gruppen sichtbar"-Auswahl und nicht
  in der normalen Kalenderliste (nur unter Gruppen).
- Settings-URL-State: uiSettingsOpen wird beim Init aus der URL gesetzt,
  bevor das erste writeUrlState() es ueberschreibt -> Reload bleibt jetzt
  wirklich in den Einstellungen.
- Auswahl-Markierungen (Mitglieder/Gruppen-Sichtbar) in Akzentfarbe,
  CSS-gezeichnet statt blauer Emoji. Version v28.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-05-31 17:23:28 +02:00
parent c7185a128e
commit 8d2f487607
5 changed files with 181 additions and 130 deletions

View File

@@ -91,13 +91,34 @@ def list_calendars(
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user),
):
# Map calendar_id -> group name for every group the user belongs to, so we
# can flag group calendars as such even when the user owns them (the creator
# owns the group calendar — it must still be marked group:true).
group_cal_map = {
cal_id: name
for cal_id, name in (
db.query(models.GroupCalendar.calendar_id, models.Group.name)
.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()
)
}
# Own calendars
own = (
db.query(models.LocalCalendar)
.filter(models.LocalCalendar.user_id == current_user.id)
.all()
)
result = [_cal_dict(c, owned=True) for c in own]
result = []
for c in own:
d = _cal_dict(c, owned=True)
if c.id in group_cal_map:
d["group"] = True
result.append(d)
seen_ids = {c.id for c in own}
# Calendars shared with this user
shares = (
@@ -105,33 +126,30 @@ 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 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(
d = _cal_dict(
cal, owned=False,
shared_by=owner.username if owner else None,
permission=share.permission,
))
)
if cal.id in group_cal_map:
d["group"] = True
result.append(d)
# 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:
# Group calendars reached via membership (read_write) that aren't already
# listed, so members can select/see the group calendar.
for cal_id, group_name in group_cal_map.items():
if cal_id in seen_ids:
continue
seen_ids.add(cal.id)
cal = db.query(models.LocalCalendar).filter(models.LocalCalendar.id == cal_id).first()
if not cal:
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)