dynamic appointments loading

This commit is contained in:
2025-06-14 15:08:43 +00:00
parent 4738740292
commit 2e8c852ee6
10 changed files with 187 additions and 52 deletions

Binary file not shown.

View File

@@ -14,7 +14,7 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# --- Python-Abhängigkeiten kopieren und installieren --- # --- Python-Abhängigkeiten kopieren und installieren ---
COPY dash_using_fullcalendar-0.0.1.tar.gz ./ COPY dash_using_fullcalendar-0.1.0.tar.gz ./
COPY requirements.txt ./ COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt

View File

@@ -27,7 +27,7 @@ RUN groupadd -g ${GROUP_ID} infoscreen_taa \
WORKDIR /app WORKDIR /app
# Kopiere nur Requirements für schnellen Rebuild # Kopiere nur Requirements für schnellen Rebuild
COPY dash_using_fullcalendar-0.0.1.tar.gz ./ COPY dash_using_fullcalendar-0.1.0.tar.gz ./
COPY requirements.txt ./ COPY requirements.txt ./
COPY requirements-dev.txt ./ COPY requirements-dev.txt ./

View File

@@ -1,12 +1,16 @@
# dashboard/callbacks/appointments_callbacks.py # dashboard/callbacks/appointments_callbacks.py
import os
import sys import sys
sys.path.append('/workspace') sys.path.append('/workspace')
from dash import Input, Output, State, callback, ctx, dash from dash import Input, Output, State, callback, ctx, dash
from flask import session from flask import session
import json import json
import requests
print("appointments_callbacks.py geladen") print("appointments_callbacks.py geladen")
API_BASE_URL = os.getenv("API_BASE_URL", "http://192.168.43.100")
@callback( @callback(
dash.Output('output', 'children'), dash.Output('output', 'children'),
dash.Input('calendar', 'lastDateClick') dash.Input('calendar', 'lastDateClick')
@@ -34,3 +38,25 @@ def display_select(select_info):
return f"Markiert: {select_info['start']} bis {select_info['end']} (ganztägig: {select_info['allDay']})" return f"Markiert: {select_info['start']} bis {select_info['end']} (ganztägig: {select_info['allDay']})"
return "Markiere einen Bereich im Kalender!" return "Markiere einen Bereich im Kalender!"
@callback(
dash.Output('calendar', 'events'),
dash.Input('calendar', 'lastNavClick'),
)
def load_events(view_dates):
"""
Lädt Events aus der API für den angezeigten Zeitraum im Kalender.
"""
print("Lade Events für Zeitraum:", view_dates)
if not view_dates or "start" not in view_dates or "end" not in view_dates:
return []
start = view_dates["start"]
end = view_dates["end"]
try:
resp = requests.get(f"{API_BASE_URL}/api/events", params={"start": start, "end": end})
resp.raise_for_status()
events = resp.json()
return events
except Exception as e:
print("Fehler beim Laden der Events:", e)
return []

Binary file not shown.

View File

@@ -2,36 +2,24 @@
from dash import html, dcc from dash import html, dcc
import dash import dash
from dash_using_fullcalendar import DashUsingFullcalendar from dash_using_fullcalendar import DashUsingFullcalendar
# dash.register_page(__name__, path="/appointments", name="Termine")
# def layout(): # Als Funktion definieren
# return html.Div([
# dcc.Store(id="calendar-click-store"),
# dcc.Store(id="calendar-event-store"),
# dcc.Input(id="test-input"),
# html.Div("TEST")
# ])
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
dash.register_page(__name__, path="/appointments", name="Termine") dash.register_page(__name__, path="/appointments", name="Termine")
layout = dbc.Container([ layout = dbc.Container([
dbc.Row([ dbc.Row([
dbc.Col(html.H2("Dash FullCalendar mit Bootstrap 5")) dbc.Col(html.H2("Dash FullCalendar"))
]), ]),
dbc.Row([ dbc.Row([
dbc.Col( dbc.Col(
DashUsingFullcalendar( DashUsingFullcalendar(
id='calendar', id='calendar',
events=[ events=[],
{"id": "1", "title": "Meeting", "date": "2025-06-13T09:00:00"},
{"id": "2", "title": "Workshop", "date": "2025-06-15T14:30:00"}
],
initialView="timeGridWeek", initialView="timeGridWeek",
headerToolbar={ headerToolbar={
"left": "prev,next today", "left": "prev,next today",
"center": "title", "center": "title",
"right": "dayGridMonth,timeGridWeek,timeGridDay" # "right": "dayGridMonth,timeGridWeek,timeGridDay"
}, },
height=600, height=600,
locale="de", locale="de",
@@ -43,7 +31,7 @@ layout = dbc.Container([
allDaySlot=False, allDaySlot=False,
firstDay=1, firstDay=1,
# themeSystem kann auf "bootstrap5" gesetzt werden, wenn das Plugin eingebunden ist # themeSystem kann auf "bootstrap5" gesetzt werden, wenn das Plugin eingebunden ist
themeSystem="bootstrap5" # themeSystem="bootstrap5"
) )
) )
]), ]),

View File

@@ -10,4 +10,4 @@ paho-mqtt>=2.1.0
python-dotenv>=1.1.0 python-dotenv>=1.1.0
PyMySQL>=1.1.1 PyMySQL>=1.1.1
SQLAlchemy>=2.0.41 SQLAlchemy>=2.0.41
./dash_using_fullcalendar-0.0.1.tar.gz ./dash_using_fullcalendar-0.1.0.tar.gz

View File

@@ -3,7 +3,7 @@ from sqlalchemy.orm import sessionmaker
from models import Event, EventMedia, EventType from models import Event, EventMedia, EventType
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta
# .env laden # .env laden
load_dotenv() load_dotenv()
@@ -32,7 +32,7 @@ session.query(Event).filter_by(client_uuid=client_uuid).delete()
session.commit() session.commit()
# --- Ende Löschungen --- # --- Ende Löschungen ---
now = datetime.now(timezone.utc) now = datetime.now()
events = [ events = [
Event( Event(
@@ -48,10 +48,10 @@ events = [
), ),
Event( Event(
client_uuid=client_uuid, client_uuid=client_uuid,
title="Webseite: Schulhomepage", title="Schulwebsite Update",
description="Zeigt die Schulhomepage an.", description="Neue Inhalte auf der Schulwebsite.",
start=now + timedelta(days=1, hours=10), start=now + timedelta(days=2, hours=10),
end=now + timedelta(days=1, hours=11), end=now + timedelta(days=2, hours=11),
event_type=EventType.website, event_type=EventType.website,
created_by=created_by, created_by=created_by,
updated_by=None, updated_by=None,
@@ -59,10 +59,10 @@ events = [
), ),
Event( Event(
client_uuid=client_uuid, client_uuid=client_uuid,
title="Video: Sicherheitsunterweisung", title="Lehrvideo ansehen",
description="Video zur Sicherheit im Chemieunterricht.", description="Video zum Thema Photosynthese.",
start=now + timedelta(days=2, hours=8), start=now + timedelta(days=3, hours=9),
end=now + timedelta(days=2, hours=8, minutes=30), end=now + timedelta(days=3, hours=10),
event_type=EventType.video, event_type=EventType.video,
created_by=created_by, created_by=created_by,
updated_by=None, updated_by=None,
@@ -70,10 +70,10 @@ events = [
), ),
Event( Event(
client_uuid=client_uuid, client_uuid=client_uuid,
title="Nachricht: Vertretungsplan", title="Nachricht vom Lehrer",
description="Aktuelle Vertretungen werden angezeigt.", description="Wichtige Mitteilung zum Unterricht.",
start=now + timedelta(days=2, hours=9), start=now + timedelta(days=4, hours=13),
end=now + timedelta(days=2, hours=9, minutes=15), end=now + timedelta(days=4, hours=14),
event_type=EventType.message, event_type=EventType.message,
created_by=created_by, created_by=created_by,
updated_by=None, updated_by=None,
@@ -81,10 +81,65 @@ events = [
), ),
Event( Event(
client_uuid=client_uuid, client_uuid=client_uuid,
title="WebUntis Stundenplan", title="Sonstiges Event",
description="Automatisch importierter Stundenplan.", description="Allgemeines Event ohne Kategorie.",
start=now + timedelta(days=3, hours=7), start=now + timedelta(days=5, hours=11),
end=now + timedelta(days=3, hours=8), end=now + timedelta(days=5, hours=12),
event_type=EventType.other,
created_by=created_by,
updated_by=None,
is_active=True
),
Event(
client_uuid=client_uuid,
title="WebUntis Termin",
description="Termin aus WebUntis importiert.",
start=now + timedelta(days=6, hours=8),
end=now + timedelta(days=6, hours=9),
event_type=EventType.webuntis,
created_by=created_by,
updated_by=None,
is_active=True
),
Event(
client_uuid=client_uuid,
title="Englisch Präsentation",
description="Präsentation zu Shakespeare.",
start=now + timedelta(days=8, hours=10),
end=now + timedelta(days=8, hours=11),
event_type=EventType.presentation,
created_by=created_by,
updated_by=None,
is_active=True
),
Event(
client_uuid=client_uuid,
title="Website Relaunch",
description="Vorstellung der neuen Schulwebsite.",
start=now + timedelta(days=10, hours=9),
end=now + timedelta(days=10, hours=10),
event_type=EventType.website,
created_by=created_by,
updated_by=None,
is_active=True
),
Event(
client_uuid=client_uuid,
title="Videoanalyse",
description="Analyse eines Lehrvideos.",
start=now + timedelta(days=12, hours=14),
end=now + timedelta(days=12, hours=15),
event_type=EventType.video,
created_by=created_by,
updated_by=None,
is_active=True
),
Event(
client_uuid=client_uuid,
title="WebUntis Info",
description="Weitere Termine aus WebUntis.",
start=now + timedelta(days=14, hours=8),
end=now + timedelta(days=14, hours=9),
event_type=EventType.webuntis, event_type=EventType.webuntis,
created_by=created_by, created_by=created_by,
updated_by=None, updated_by=None,
@@ -92,10 +147,12 @@ events = [
), ),
] ]
session.add_all(events) # Events anlegen und speichern
session.commit() for event in events:
session.add(event)
session.commit() # Jetzt haben alle Events eine ID
# EventMedia zuordnen (IDs müssen jetzt existieren) # EventMedia mit den richtigen event_id-Werten anlegen
event_media = [ event_media = [
EventMedia( EventMedia(
event_id=events[0].id, event_id=events[0].id,
@@ -106,32 +163,60 @@ event_media = [
EventMedia( EventMedia(
event_id=events[1].id, event_id=events[1].id,
media_type=EventType.website, media_type=EventType.website,
url="https://schule.example.com", url="https://schule.example.com/update",
order=1 order=1
), ),
EventMedia( EventMedia(
event_id=events[2].id, event_id=events[2].id,
media_type=EventType.video, media_type=EventType.video,
url="https://videos.example.com/sicherheit.mp4", url="https://example.com/photosynthese-video.mp4",
order=1, order=1
autoplay=True,
loop=False,
volume=0.8
), ),
EventMedia( EventMedia(
event_id=events[3].id, event_id=events[3].id,
media_type=EventType.message, media_type=EventType.message,
url="https://schule.example.com/vertretungsplan", url="https://example.com/lehrer-nachricht",
order=1 order=1
), ),
EventMedia( EventMedia(
event_id=events[4].id, event_id=events[4].id,
media_type=EventType.other,
url="https://example.com/sonstiges-event.pdf",
order=1
),
EventMedia(
event_id=events[5].id,
media_type=EventType.webuntis, media_type=EventType.webuntis,
url="https://webuntis.example.com/stundenplan", url="https://webuntis.example.com/termin",
order=1
),
EventMedia(
event_id=events[6].id,
media_type=EventType.presentation,
url="https://example.com/praesentation-englisch.pdf",
order=1
),
EventMedia(
event_id=events[7].id,
media_type=EventType.website,
url="https://schule.example.com/relaunch",
order=1
),
EventMedia(
event_id=events[8].id,
media_type=EventType.video,
url="https://example.com/videoanalyse.mp4",
order=1
),
EventMedia(
event_id=events[9].id,
media_type=EventType.webuntis,
url="https://webuntis.example.com/info",
order=1 order=1
), ),
] ]
session.add_all(event_media) for media in event_media:
session.add(media)
session.commit() session.commit()
print("Test-Events und EventMedia wurden angelegt.") print("Test-Events und EventMedia wurden angelegt.")

View File

@@ -1,10 +1,10 @@
# server/wsgi.py # server/wsgi.py
import glob import glob
import os import os
from flask import Flask, jsonify, send_from_directory from flask import Flask, jsonify, send_from_directory, request
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine from sqlalchemy import create_engine, and_
from models import Client, Base from models import Client, Base, Event
DB_USER = os.getenv("DB_USER") DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD") DB_PASSWORD = os.getenv("DB_PASSWORD")
@@ -63,5 +63,41 @@ def get_clients():
session.close() session.close()
return jsonify(result) return jsonify(result)
@app.route("/api/events")
def get_events():
"""
Liefert Events für einen Zeitraum (start, end) als FullCalendar-kompatible Objekte.
Query-Parameter: start, end (ISO-Format), optional: client_uuid
"""
session = Session()
start = request.args.get("start")
end = request.args.get("end")
client_uuid = request.args.get("client_uuid")
query = session.query(Event).filter(Event.is_active == True)
if start and end:
query = query.filter(and_(Event.start < end, Event.end > start))
if client_uuid:
query = query.filter(Event.client_uuid == client_uuid)
events = query.all()
result = []
for e in events:
result.append({
"id": str(e.id),
"title": e.title,
"start": e.start.isoformat() if e.start else None,
"end": e.end.isoformat() if e.end else None,
"allDay": False,
"classNames": [e.event_type.value] if e.event_type else [],
"extendedProps": {
"description": e.description,
"client_uuid": e.client_uuid,
"event_type": e.event_type.value if e.event_type else None,
"created_by": e.created_by,
"updated_by": e.updated_by,
}
})
session.close()
return jsonify(result)
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)