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:
@@ -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}]")
|
||||
|
||||
Reference in New Issue
Block a user