fix: Runde-2-Fixes – Monatsauswahl, CalDAV-Update, Lösch-Dialog, EXDATE

- Monatsansicht: selectedDate von currentDate getrennt, Klick verschiebt View nicht mehr
- Selected-Day Styling: weißer Text auf Primary-Hintergrund statt nur Textfarbe
- Kontextmenü: --bg-surface statt fehlendem --bg-card
- CalDAV Update/Delete: parent Calendar-Objekt übergeben (behebt NoneType-Fehler)
- HA-Kalender im Kalender-Selektor ergänzt
- Browser-confirm() durch styled Modal-Dialog ersetzt mit Serie/Einzeln-Option
- EXDATE-Support: einzelne Vorkommen wiederkehrender Termine löschen (lokal + CalDAV)
- Fehlende i18n-Keys für Lösch-Dialog ergänzt (DE + EN)
This commit is contained in:
Scarriffle
2026-04-29 18:13:12 +02:00
parent e3984eb5cf
commit d4ea097831
10 changed files with 220 additions and 43 deletions

View File

@@ -57,6 +57,7 @@ class EventUpdate(BaseModel):
description: Optional[str] = None
color: Optional[str] = None
rrule: Optional[str] = None
exdate: Optional[str] = None
def _account_dict(a: models.CalDAVAccount) -> dict:
@@ -84,6 +85,13 @@ def _account_dict(a: models.CalDAVAccount) -> dict:
def _expand_recurring_local(ev, local_cal, range_start, range_end):
"""Expand a recurring LocalEvent into individual occurrences within the date range."""
results = []
# Parse excluded dates
excluded = set()
if ev.exdate:
for d in ev.exdate.split(","):
d = d.strip()
if d:
excluded.add(d)
try:
ev_start_str = ev.start.replace("Z", "+00:00")
ev_end_str = ev.end.replace("Z", "+00:00")
@@ -98,6 +106,9 @@ def _expand_recurring_local(ev, local_cal, range_start, range_end):
occurrences = rule.between(r_start - timedelta(days=1), r_end + timedelta(days=1), inc=True)
for occ in occurrences:
occ_start = occ.date()
occ_key = occ_start.strftime("%Y%m%d")
if occ_key in excluded:
continue
occ_end = occ_start + duration
results.append({
"id": ev.uid,
@@ -132,6 +143,9 @@ def _expand_recurring_local(ev, local_cal, range_start, range_end):
r_end = r_end.replace(tzinfo=dt_timezone.utc)
occurrences = rule.between(r_start - timedelta(days=1), r_end + timedelta(days=1), inc=True)
for occ in occurrences:
occ_key = occ.strftime("%Y%m%d")
if occ_key in excluded:
continue
occ_end = occ + duration
results.append({
"id": ev.uid,
@@ -548,6 +562,7 @@ def update_event(
.all()
)
account = None
cal_url = None
if calendar_id is not None:
cal = (
db.query(models.Calendar)
@@ -557,8 +572,15 @@ def update_event(
)
if cal:
account = next((a for a in accounts if a.id == cal.account_id), None)
cal_url = cal.cal_id
if not account:
account = _find_account_for_event_url(event_url, accounts)
# Try to find the calendar URL for the account
if account and not cal_url:
for c in account.calendars:
if event_url.startswith(c.cal_id) or event_url.startswith(_normalize_url(c.cal_id)):
cal_url = c.cal_id
break
if not account:
raise HTTPException(404, "Event not found or not authorized")
try:
@@ -568,6 +590,7 @@ def update_event(
account.password,
event_url,
data.model_dump(exclude_none=True) if data else {},
calendar_url=cal_url,
)
return {"ok": True}
except Exception as exc:
@@ -588,6 +611,7 @@ def delete_event(
.all()
)
account = None
cal_url = None
if calendar_id is not None:
cal = (
db.query(models.Calendar)
@@ -597,13 +621,20 @@ def delete_event(
)
if cal:
account = next((a for a in accounts if a.id == cal.account_id), None)
cal_url = cal.cal_id
if not account:
account = _find_account_for_event_url(event_url, accounts)
if account and not cal_url:
for c in account.calendars:
if event_url.startswith(c.cal_id) or event_url.startswith(_normalize_url(c.cal_id)):
cal_url = c.cal_id
break
if not account:
raise HTTPException(404, "Event not found or not authorized")
try:
caldav_client.delete_event(
account.url, account.username, account.password, event_url
account.url, account.username, account.password, event_url,
calendar_url=cal_url,
)
return {"ok": True}
except Exception as exc:

View File

@@ -44,6 +44,7 @@ class EventUpdate(BaseModel):
description: Optional[str] = None
color: Optional[str] = None
rrule: Optional[str] = None
exdate: Optional[str] = None
def _cal_dict(cal: models.LocalCalendar) -> dict:
@@ -67,6 +68,7 @@ def _event_dict(ev: models.LocalEvent, cal: models.LocalCalendar) -> dict:
"description": ev.description or "",
"color": ev.color,
"rrule": ev.rrule,
"exdate": ev.exdate,
"calendar_id": f"local-{cal.id}",
"calendar_name": cal.name,
"calendarColor": cal.color,
@@ -225,6 +227,12 @@ def update_event(
ev.color = data.color
if data.rrule is not None:
ev.rrule = data.rrule if data.rrule else None
if data.exdate is not None:
existing = ev.exdate or ""
dates = [d for d in existing.split(",") if d]
if data.exdate not in dates:
dates.append(data.exdate)
ev.exdate = ",".join(dates)
db.commit()
return {"ok": True}