feat: 2026.1.0-alpha.16 – dashboard banner refactor, period auto-activation, text & docs

Dashboard (dashboard/src/dashboard.tsx, settings.tsx, apiAcademicPeriods.ts):
- Refactor loadHolidayStatus to useCallback with stable empty-deps reference;
  removes location.pathname dependency that caused overlapping API calls at mount
  and left the banner unresolved via request-sequence cancellation
- Add key prop derived from severity:text to Syncfusion MessageComponent to force
  remount on state change, fixing stale banner that ignored React prop/children updates
- Correct German transliterated text to proper Umlauts throughout visible UI strings
  (fuer -> für, oe -> ö, ae -> ä etc. across dashboard and settings views)

Backend (server/init_academic_periods.py):
- Refactor to idempotent two-phase flow: seed default periods only when table is
  empty; on every run activate exactly the non-archived period covering date.today()
- Enforces single-active invariant by deactivating all periods before promoting match
- Emits explicit warning when no period covers current date instead of doing nothing

Deployment (docker-compose.prod.yml):
- Add init_academic_periods.py to server startup chain after migrations and defaults;
  eliminates manual post-deploy step to set an active academic period

Release docs:
- program-info.json: bump to 2026.1.0-alpha.16; fix JSON parse error caused by
  typographic curly quotes in the new changelog entry
- TECH-CHANGELOG.md: detailed alpha.16 section with root-cause motivation for both
  dashboard refactoring decisions (unstable callback ref + Syncfusion stale render)
- DEV-CHANGELOG.md: document dashboard refactor, Syncfusion key fix, Umlaut changes,
  and program-info JSON regression and fix
- README.md: add Latest Release Highlights section for alpha.16
- .github/copilot-instructions.md: sync file map, prod bootstrap note, backend and
  frontend pattern additions for academic period init and Syncfusion remount pattern
This commit is contained in:
2026-04-02 14:16:53 +00:00
parent 06411edfab
commit 4d652f0554
10 changed files with 1054 additions and 888 deletions

View File

@@ -1,7 +1,11 @@
#!/usr/bin/env python3
"""
Erstellt Standard-Schuljahre für österreichische Schulen
Führe dieses Skript nach der Migration aus, um Standard-Perioden zu erstellen.
Erstellt Standard-Schuljahre und setzt automatisch die aktive Periode.
Dieses Skript ist idempotent:
- Wenn keine Perioden existieren, werden Standard-Perioden erstellt.
- Danach wird (bei jedem Lauf) die nicht-archivierte Periode aktiviert,
die das heutige Datum abdeckt.
"""
from datetime import date
@@ -11,54 +15,94 @@ import sys
sys.path.append('/workspace')
def _create_default_periods_if_missing(session):
"""Erstellt Standard-Schuljahre nur dann, wenn noch keine Perioden existieren."""
existing = session.query(AcademicPeriod).first()
if existing:
print("Academic periods already exist. Skipping creation.")
return False
periods = [
{
'name': 'Schuljahr 2024/25',
'display_name': 'SJ 24/25',
'start_date': date(2024, 9, 2),
'end_date': date(2025, 7, 4),
'period_type': AcademicPeriodType.schuljahr,
'is_active': False
},
{
'name': 'Schuljahr 2025/26',
'display_name': 'SJ 25/26',
'start_date': date(2025, 9, 1),
'end_date': date(2026, 7, 3),
'period_type': AcademicPeriodType.schuljahr,
'is_active': False
},
{
'name': 'Schuljahr 2026/27',
'display_name': 'SJ 26/27',
'start_date': date(2026, 9, 7),
'end_date': date(2027, 7, 2),
'period_type': AcademicPeriodType.schuljahr,
'is_active': False
}
]
for period_data in periods:
period = AcademicPeriod(**period_data)
session.add(period)
session.flush()
print(f"Successfully created {len(periods)} academic periods")
return True
def _activate_period_for_today(session):
"""Aktiviert genau eine Periode: die Periode, die heute abdeckt."""
today = date.today()
period_for_today = (
session.query(AcademicPeriod)
.filter(
AcademicPeriod.is_archived == False,
AcademicPeriod.start_date <= today,
AcademicPeriod.end_date >= today,
)
.order_by(AcademicPeriod.start_date.desc())
.first()
)
# Immer zunächst alle aktiven Perioden deaktivieren, um den Zustand konsistent zu halten.
session.query(AcademicPeriod).filter(AcademicPeriod.is_active == True).update(
{AcademicPeriod.is_active: False},
synchronize_session=False,
)
if period_for_today:
period_for_today.is_active = True
print(
f"Activated academic period for today ({today}): {period_for_today.name} "
f"[{period_for_today.start_date} - {period_for_today.end_date}]"
)
else:
print(
f"WARNING: No academic period covers today ({today}). "
"All periods remain inactive."
)
def create_default_academic_periods():
"""Erstellt Standard-Schuljahre für österreichische Schulen"""
"""Erstellt Standard-Perioden (falls nötig) und setzt aktive Periode für heute."""
session = Session()
try:
# Prüfe ob bereits Perioden existieren
existing = session.query(AcademicPeriod).first()
if existing:
print("Academic periods already exist. Skipping creation.")
return
# Standard Schuljahre erstellen
periods = [
{
'name': 'Schuljahr 2024/25',
'display_name': 'SJ 24/25',
'start_date': date(2024, 9, 2),
'end_date': date(2025, 7, 4),
'period_type': AcademicPeriodType.schuljahr,
'is_active': True # Aktuelles Schuljahr
},
{
'name': 'Schuljahr 2025/26',
'display_name': 'SJ 25/26',
'start_date': date(2025, 9, 1),
'end_date': date(2026, 7, 3),
'period_type': AcademicPeriodType.schuljahr,
'is_active': False
},
{
'name': 'Schuljahr 2026/27',
'display_name': 'SJ 26/27',
'start_date': date(2026, 9, 7),
'end_date': date(2027, 7, 2),
'period_type': AcademicPeriodType.schuljahr,
'is_active': False
}
]
for period_data in periods:
period = AcademicPeriod(**period_data)
session.add(period)
_create_default_periods_if_missing(session)
_activate_period_for_today(session)
session.commit()
print(f"Successfully created {len(periods)} academic periods")
# Zeige erstellte Perioden
for period in session.query(AcademicPeriod).all():
for period in session.query(AcademicPeriod).order_by(AcademicPeriod.start_date.asc()).all():
status = "AKTIV" if period.is_active else "inaktiv"
print(
f" - {period.name} ({period.start_date} - {period.end_date}) [{status}]")