Files
infoscreen/dashboard-dash-backup/callbacks/appointment_modal_callbacks.py

370 lines
13 KiB
Python

from dash import Input, Output, State, callback, html, dcc, no_update, ctx
import dash_mantine_components as dmc
from dash_iconify import DashIconify
import dash_quill
from datetime import datetime, date, timedelta
import re
# --- Typ-spezifische Felder anzeigen ---
@callback(
Output('type-specific-fields', 'children'),
Input('type-input', 'value'),
prevent_initial_call=True
)
def show_type_specific_fields(event_type):
if not event_type:
return html.Div()
if event_type == "presentation":
return dmc.Stack([
dmc.Divider(label="Präsentations-Details", labelPosition="center"),
dmc.Group([
dcc.Upload(
id='presentation-upload',
children=dmc.Button(
"Datei hochladen",
leftSection=DashIconify(icon="mdi:upload"),
variant="outline"
),
style={'width': 'auto'}
),
dmc.TextInput(
label="Präsentationslink",
placeholder="https://...",
leftSection=DashIconify(icon="mdi:link"),
id="presentation-link",
style={"minWidth": 0, "flex": 1, "marginBottom": 0}
)
], grow=True, align="flex-end", style={"marginBottom": 10}),
html.Div(id="presentation-upload-status")
], gap="sm")
elif event_type == "video":
return dmc.Stack([
dmc.Divider(label="Video-Details", labelPosition="center"),
dmc.Group([
dcc.Upload(
id='video-upload',
children=dmc.Button(
"Video hochladen",
leftSection=DashIconify(icon="mdi:video-plus"),
variant="outline"
),
style={'width': 'auto'}
),
dmc.TextInput(
label="Videolink",
placeholder="https://youtube.com/...",
leftSection=DashIconify(icon="mdi:youtube"),
id="video-link",
style={"minWidth": 0, "flex": 1, "marginBottom": 0}
)
], grow=True, align="flex-end", style={"marginBottom": 10}),
dmc.Group([
dmc.Checkbox(
label="Endlos wiederholen",
id="video-endless",
checked=True,
style={"marginRight": 20}
),
dmc.NumberInput(
label="Wiederholungen",
id="video-repeats",
value=1,
min=1,
max=99,
step=1,
disabled=True,
style={"width": 150}
),
dmc.Slider(
label="Lautstärke",
id="video-volume",
value=70,
min=0,
max=100,
step=5,
marks=[
{"value": 0, "label": "0%"},
{"value": 50, "label": "50%"},
{"value": 100, "label": "100%"}
],
style={"flex": 1, "marginLeft": 20}
)
], grow=True, align="flex-end"),
html.Div(id="video-upload-status")
], gap="sm")
elif event_type == "website":
return dmc.Stack([
dmc.Divider(label="Website-Details", labelPosition="center"),
dmc.TextInput(
label="Website-URL",
placeholder="https://example.com",
leftSection=DashIconify(icon="mdi:web"),
id="website-url",
required=True,
style={"flex": 1}
)
], gap="sm")
elif event_type == "message":
return dmc.Stack([
dmc.Divider(label="Nachrichten-Details", labelPosition="center"),
dash_quill.Quill(
id="message-content",
value="",
),
dmc.Group([
dmc.Select(
label="Schriftgröße",
data=[
{"value": "small", "label": "Klein"},
{"value": "medium", "label": "Normal"},
{"value": "large", "label": "Groß"},
{"value": "xlarge", "label": "Sehr groß"}
],
id="message-font-size",
value="medium",
style={"flex": 1}
),
dmc.ColorPicker(
id="message-color",
value="#000000",
format="hex",
swatches=[
"#000000", "#ffffff", "#ff0000", "#00ff00",
"#0000ff", "#ffff00", "#ff00ff", "#00ffff"
]
)
], grow=True, align="flex-end")
], gap="sm")
return html.Div()
# --- Callback zum Aktivieren/Deaktivieren des Wiederholungsfelds bei Video ---
@callback(
Output("video-repeats", "disabled"),
Input("video-endless", "checked"),
prevent_initial_call=True
)
def toggle_video_repeats(endless_checked):
return endless_checked
# --- Upload-Status für Präsentation ---
@callback(
Output('presentation-upload-status', 'children'),
Input('presentation-upload', 'contents'),
State('presentation-upload', 'filename'),
prevent_initial_call=True
)
def update_presentation_upload_status(contents, filename):
if contents is not None and filename is not None:
return dmc.Alert(
f"✓ Datei '{filename}' erfolgreich hochgeladen",
color="green",
className="mt-2"
)
return html.Div()
# --- Upload-Status für Video ---
@callback(
Output('video-upload-status', 'children'),
Input('video-upload', 'contents'),
State('video-upload', 'filename'),
prevent_initial_call=True
)
def update_video_upload_status(contents, filename):
if contents is not None and filename is not None:
return dmc.Alert(
f"✓ Video '{filename}' erfolgreich hochgeladen",
color="green",
className="mt-2"
)
return html.Div()
# --- Wiederholungsoptionen aktivieren/deaktivieren ---
@callback(
[
Output('weekdays-select', 'disabled'),
Output('repeat-until-date', 'disabled'),
Output('skip-holidays-checkbox', 'disabled'),
Output('weekdays-select', 'value'),
Output('repeat-until-date', 'value'),
Output('skip-holidays-checkbox', 'checked')
],
Input('repeat-checkbox', 'checked'),
prevent_initial_call=True
)
def toggle_repeat_options(is_repeat):
if is_repeat:
next_month = datetime.now().date() + timedelta(weeks=4)
return (
False, # weekdays-select enabled
False, # repeat-until-date enabled
False, # skip-holidays-checkbox enabled
None, # weekdays value
next_month, # repeat-until-date value
False # skip-holidays-checkbox checked
)
else:
return (
True, # weekdays-select disabled
True, # repeat-until-date disabled
True, # skip-holidays-checkbox disabled
None, # weekdays value
None, # repeat-until-date value
False # skip-holidays-checkbox checked
)
# --- Dynamische Zeitoptionen für Startzeit ---
def validate_and_format_time(time_str):
if not time_str:
return None, "Keine Zeit angegeben"
if re.match(r'^\d{2}:\d{2}$', time_str):
try:
h, m = map(int, time_str.split(':'))
if 0 <= h <= 23 and 0 <= m <= 59:
return time_str, "Gültige Zeit"
except:
pass
patterns = [
(r'^(\d{1,2}):(\d{2})$', lambda m: (int(m.group(1)), int(m.group(2)))),
(r'^(\d{1,2})\.(\d{2})$', lambda m: (int(m.group(1)), int(m.group(2)))),
(r'^(\d{1,2})(\d{2})$', lambda m: (int(m.group(1)), int(m.group(2)))),
(r'^(\d{1,2})$', lambda m: (int(m.group(1)), 0)),
]
for pattern, extractor in patterns:
match = re.match(pattern, time_str.strip())
if match:
try:
hours, minutes = extractor(match)
if 0 <= hours <= 23 and 0 <= minutes <= 59:
return f"{hours:02d}:{minutes:02d}", "Automatisch formatiert"
except:
continue
return None, "Ungültiges Zeitformat"
@callback(
[
Output('time-start', 'data'),
Output('start-time-feedback', 'children')
],
Input('time-start', 'searchValue'),
prevent_initial_call=True
)
def update_start_time_options(search_value):
time_options = [
{"value": f"{h:02d}:{m:02d}", "label": f"{h:02d}:{m:02d}"}
for h in range(6, 24) for m in [0, 30]
]
base_options = time_options.copy()
feedback = None
if search_value:
validated_time, status = validate_and_format_time(search_value)
if validated_time:
if not any(opt["value"] == validated_time for opt in base_options):
base_options.insert(0, {
"value": validated_time,
"label": f"{validated_time} (Ihre Eingabe)"
})
feedback = dmc.Text(f"{status}: {validated_time}", size="xs", c="green")
else:
feedback = dmc.Text(f"{status}", size="xs", c="red")
return base_options, feedback
# --- Dynamische Zeitoptionen für Endzeit ---
@callback(
[
Output('time-end', 'data'),
Output('end-time-feedback', 'children')
],
Input('time-end', 'searchValue'),
prevent_initial_call=True
)
def update_end_time_options(search_value):
time_options = [
{"value": f"{h:02d}:{m:02d}", "label": f"{h:02d}:{m:02d}"}
for h in range(6, 24) for m in [0, 30]
]
base_options = time_options.copy()
feedback = None
if search_value:
validated_time, status = validate_and_format_time(search_value)
if validated_time:
if not any(opt["value"] == validated_time for opt in base_options):
base_options.insert(0, {
"value": validated_time,
"label": f"{validated_time} (Ihre Eingabe)"
})
feedback = dmc.Text(f"{status}: {validated_time}", size="xs", c="green")
else:
feedback = dmc.Text(f"{status}", size="xs", c="red")
return base_options, feedback
# --- Automatische Endzeit-Berechnung mit Validation ---
@callback(
Output('time-end', 'value'),
[
Input('time-start', 'value'),
Input('btn-reset', 'n_clicks')
],
State('time-end', 'value'),
prevent_initial_call=True
)
def handle_end_time(start_time, reset_clicks, current_end_time):
if not ctx.triggered:
return no_update
trigger_id = ctx.triggered[0]['prop_id'].split('.')[0]
if trigger_id == 'btn-reset' and reset_clicks:
return None
if trigger_id == 'time-start' and start_time:
if current_end_time:
return no_update
try:
validated_start, _ = validate_and_format_time(start_time)
if validated_start:
start_dt = datetime.strptime(validated_start, "%H:%M")
end_dt = start_dt + timedelta(hours=1, minutes=30)
if end_dt.hour >= 24:
end_dt = end_dt.replace(hour=23, minute=59)
return end_dt.strftime("%H:%M")
except:
pass
return no_update
# --- Reset-Funktion erweitert ---
@callback(
[
Output('title-input', 'value'),
Output('start-date-input', 'value', allow_duplicate=True),
Output('time-start', 'value'),
Output('type-input', 'value'),
Output('description-input', 'value'),
Output('repeat-checkbox', 'checked'),
Output('weekdays-select', 'value', allow_duplicate=True),
Output('repeat-until-date', 'value', allow_duplicate=True),
Output('skip-holidays-checkbox', 'checked', allow_duplicate=True)
],
Input('btn-reset', 'n_clicks'),
prevent_initial_call=True
)
def reset_form(n_clicks):
if n_clicks:
return "", datetime.now().date(), "09:00", None, "", False, None, None, False
return no_update
# --- Speichern-Funktion (Demo) ---
@callback(
Output('save-feedback', 'children'),
Input('btn-save', 'n_clicks'),
prevent_initial_call=True
)
def save_appointments_demo(n_clicks):
if not n_clicks:
return no_update
return dmc.Alert(
"Demo: Termine würden hier gespeichert werden",
color="blue",
title="Speichern (Demo)"
)