from datetime import datetime import dateutil.easter from PIL.SpiderImagePlugin import isInt from dateutil.easter import * from nicegui import ui, app, events from nicegui.html import button from lib.users import * from lib.definitions import * from calendar import monthrange from lib.web_ui import * import os.path import os from stat import S_IREAD, S_IRWXU import hashlib import calendar import locale import segno @ui.page('/admin') def page_admin(): ui.page_title(f"{app_title} {app_version}") data = load_adminsettings() try: browser_cookie = app.storage.user['admin_authenticated'] except: browser_cookie = False # Adminseite if browser_cookie: pageheader("Administration") def admin_logout(): app.storage.user.pop("admin_authenticated", None) ui.navigate.to("/") ui.button("Logout", on_click=admin_logout) with ui.tabs() as tabs: time_overview = ui.tab('Zeitübersichten') settings = ui.tab('Einstellungen') users = ui.tab('Benutzer') userlist = [ ] def update_userlist(): nonlocal userlist userlist = list_users() update_userlist() with (((ui.tab_panels(tabs, value=time_overview)))): with ui.tab_panel(time_overview): ui.markdown("##Übersichten") # Tabelle konstruieren with ui.card().classes('w-full'): with ui.row() as timetable_header: year_binder = ValueBinder() month_binder = ValueBinder() def update_months(): current_user = user(time_user.value) available_months = current_user.get_months(year_binder.value) available_months_dict = { } for element in available_months: available_months_dict[element] = calendar.month_name[int(element)] select_month.clear() select_month.set_options(available_months_dict) select_month.value = list(available_months)[0] def update_user(): current_user = user(time_user.value) available_years = current_user.get_years() try: select_year.clear() select_year.set_options(available_years) try: select_year.value = str(datetime.datetime.now().year) except: select_year.value = list(available_years)[0] try: select_month.value = datetime.datetime.now().month except: select_month.value = list(available_months)[0] except NameError: pass ui.markdown("Benutzer:") time_user = ui.select(options=userlist, on_change=update_user) time_user.value = userlist[0] user_to_select_for_start = userlist[0] current_year = datetime.datetime.now().year current_month = datetime.datetime.now().month current_user = user(user_to_select_for_start) available_years = current_user.get_years() available_months = current_user.get_months(current_year) available_months_dict = { } for element in available_months: available_months_dict[element] = calendar.month_name[int(element)] if current_month in available_months: set_month = current_month else: set_month = available_months[0] if str(current_year) in available_years: set_year = str(current_year) else: set_year = (available_years[0]) select_month = ui.select(options=available_months_dict, value=set_month).bind_value_to(month_binder, 'value') select_year = ui.select(options=available_years, value=set_year, on_change=update_months).bind_value_to(year_binder, 'value') month_header = ui.markdown(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**") # Tabelle aufbauen @ui.refreshable def timetable(): current_user = user(time_user.value) with ui.card().classes('w-full') as calendar_card: def update_month_and_year(): #current_user = user(time_user.value) # Archivstatus days_with_errors = current_user.archiving_validity_check(int(select_year.value), int(select_month.value)) with ui.grid(columns='auto auto auto 1fr 1fr 1fr 1fr').classes('w-full md:min-w-[600px] lg:min-w-[800px]') as table_grid: if int(select_month.value) > 1: archive_status = current_user.get_archive_status(int(select_year.value), int(select_month.value)) else: archive_status = current_user.get_archive_status(int(select_year.value) - 1, 12) def revoke_archive_status(): def revoke_status(): filestring = f"{current_user.userfolder}/{int(select_year.value)}-{int(select_month.value)}" filename = f"{filestring}.txt" os.chmod(filename, S_IRWXU) filename = f"{filestring}.json" os.chmod(filename, S_IRWXU) with open(filename, 'r') as json_file: data = json.load(json_file) data["archived"] = 0 json_dict = json.dumps(data) with open(filename, "w") as outputfile: outputfile.write(json_dict) timetable.refresh() dialog.close() with ui.dialog() as dialog, ui.card(): ui.label("Soll der Archivstatus für den aktuellen Monat aufgehoben werden, damit Änderungen vorgenommen werden können?") with ui.grid(columns=2): ui.button("Ja", on_click=revoke_status) ui.button("Nein", on_click=dialog.close) dialog.open() if archive_status == True: with ui.row().classes('text-right col-span-7 justify-center'): ui.button("Archiviert", on_click=revoke_archive_status).classes('bg-transparent text-black') ui.separator() calendar_card.classes('bg-yellow') else: calendar_card.classes('bg-white') # Überschriften ui.markdown("**Datum**") ui.markdown("**Buchungen**") ui.space() ui.markdown("**Ist**") ui.markdown("**Soll**") ui.markdown("**Saldo**") ui.space() timestamps = current_user.get_timestamps(year=select_year.value, month=select_month.value) user_absent = current_user.get_absence(year=select_year.value, month=select_month.value) # Dictionary für sortierte Timestamps timestamps_dict = { } # Dictionary mit zunächst leeren Tageinträgen befüllen for day in range(1, monthrange(int(select_year.value), int(select_month.value))[1] + 1): # Jeder Tag bekommt eine leere Liste timestamps_dict[day] = [ ] # Alle Timestamps durchgehen und sie den Dictionaryeinträgen zuordnen: for stamp in timestamps: day_of_month_of_timestamp = int(datetime.datetime.fromtimestamp(int(stamp)).day) timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) general_saldo = 0 for day in list(timestamps_dict): # Datum für Tabelle konstruieren day_in_list = datetime.datetime(int(select_year.value), int(select_month.value), day) class_content = "" if day_in_list.date() == datetime.datetime.now().date(): class_content = 'font-bold text-red-700 uppercase' ui.markdown(f"{day_in_list.strftime('%a')}., {day}. {calendar.month_name[int(select_month.value)]}").classes(class_content) # Buchungen with ui.row(): def delete_absence(day, absence_type): def execute_deletion(): current_user.del_absence(select_year.value, select_month.value, day) calendar_card.clear() update_month_and_year() dialog.close() ui.notify("Abwesenheitseintrag gelöscht") with ui.dialog() as dialog, ui.card(): ui.markdown(f'''Soll der Eintrag **{absence_type}** für den **{day}. {calendar.month_name[int(select_month.value)]} {select_year.value}** gelöscht werden? Dies kann nicht rückgängig gemacht werden!''') with ui.grid(columns=3): ui.button("Ja", on_click=execute_deletion) ui.space() ui.button("Nein", on_click=dialog.close) dialog.open() try: for i in list(user_absent): if int(i) == day: absence_button = ui.button(absence_entries[user_absent[i]]["name"], on_click=lambda i=i, day=day: delete_absence(day, absence_entries[user_absent[i]]["name"])).props(f'color={absence_entries[user_absent[i]]["color"]}') if archive_status: absence_button.disable() except: pass day_type = ui.markdown("Kein Arbeitstag") day_type.set_visibility(False) # Hier werden nur die Tage mit Timestamps behandelt if len(timestamps_dict[day]) > 0: timestamps_dict[day].sort() def edit_entry(t_stamp, day): with ui.dialog() as edit_dialog, ui.card(): ui.markdown("**Eintrag bearbeiten**") timestamp = datetime.datetime.fromtimestamp(int(t_stamp)) input_time = ui.time().props('format24h now-btn').classes('w-full justify-center') input_time.value = timestamp.strftime('%H:%M') def save_entry(day): nonlocal t_stamp t_stamp = f"{t_stamp}\n" position = timestamps.index(t_stamp) new_time_stamp = datetime.datetime(int(select_year.value), int(select_month.value), day, int(input_time.value[:2]), int(input_time.value[-2:])) timestamps[position] = str( int(new_time_stamp.timestamp())) + "\n" current_user = user(time_user.value) current_user.write_edited_timestamps(timestamps, select_year.value, select_month.value) edit_dialog.close() calendar_card.clear() update_month_and_year() month_header.set_content( f"###Buchungen für {calendar.month_name[int(select_month.value)]} {select_year.value}") ui.notify("Eintrag gespeichert") def del_entry(): nonlocal t_stamp t_stamp = f"{t_stamp}\n" timestamps.remove(t_stamp) timestamps.sort() current_user = user(time_user.value) current_user.write_edited_timestamps(timestamps, select_year.value, select_month.value) edit_dialog.close() calendar_card.clear() update_month_and_year() month_header.set_content( f"###Buchungen für {calendar.month_name[int(select_month.value)]} {select_year.value}") ui.notify("Eintrag gelöscht") with ui.row(): ui.button("Speichern", on_click=lambda day=day: save_entry(day)) ui.button("Löschen", on_click=del_entry) ui.button("Abbrechen", on_click=edit_dialog.close) edit_dialog.open() for i in range(0, len(timestamps_dict[day]), 2): try: temp_pair = [ timestamps_dict[day][i] , timestamps_dict[day][i+1] ] with ui.card().classes('bg-inherit'): with ui.row(): for j in temp_pair: timestamp_button = ui.button(datetime.datetime.fromtimestamp(int(j)).strftime('%H:%M'), on_click=lambda t_stamp=j, day=day: edit_entry(t_stamp, day)) if archive_status: timestamp_button.disable() except Exception as e: if len(timestamps_dict[day]) % 2 != 0: with ui.card().classes('bg-inherit'): timestamp_button = ui.button(datetime.datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M'), on_click=lambda t_stamp=timestamps_dict[day][i], day=day: edit_entry(t_stamp, day)) if archive_status: timestamp_button.disable() with ui.row(): # Fehlerhinweis if day in days_with_errors: ui.icon('warning', color='red').tooltip("Keine Schlussbuchung").classes('text-2xl') # Notizen anzeigen days_notes = current_user.get_day_notes(select_year.value, select_month.value, day) if days_notes != { }: with ui.icon('o_description').classes('text-2xl'): with ui.tooltip(): with ui.grid(columns='auto auto'): for username, text in days_notes.items(): admins_name = load_adminsettings()["admin_user"] if username == admins_name: ui.markdown('Administrator:') else: ui.markdown(current_user.fullname) ui.markdown(text) else: ui.space() # Arbeitszeit Ist bestimmen timestamps_of_this_day = [] # Suche mir alle timestamps für diesen Tag for i in timestamps: actual_timestamp = datetime.datetime.fromtimestamp(int(i)) timestamp_day = actual_timestamp.day if int(timestamp_day) == int(day): timestamps_of_this_day.append(i) timestamps_of_this_day.sort() time_sum = 0 if len(timestamps_of_this_day) > 1: if len(timestamps_of_this_day) % 2 == 0: for i in range(0, len(timestamps_of_this_day), 2): time_delta = int( timestamps_of_this_day[i + 1]) - int( timestamps_of_this_day[i]) time_sum = time_sum + time_delta else: for i in range(0, len(timestamps_of_this_day) - 1, 2): time_delta = int( timestamps_of_this_day[i + 1]) - int( timestamps_of_this_day[i]) time_sum = time_sum + time_delta ui.markdown(convert_seconds_to_hours(time_sum)).classes('text-right') else: ui.markdown("Kein") # Arbeitszeitsoll bestimmen hours_to_work = int(current_user.get_day_workhours(select_year.value, select_month.value, day)) if hours_to_work < 0: ui.space() day_type.content="Kein Arbeitsverhältnis" day_type.set_visibility(True) else: ui.markdown(f"{convert_seconds_to_hours(int(hours_to_work) * 3600)}").classes('text-right') if int(hours_to_work) == 0: day_type.content = "**Kein Arbeitstag**" day_type.set_visibility(True) if day_in_list.strftime("%Y-%m-%d") in data["holidays"]: day_type.content = f'**{data["holidays"][day_in_list.strftime("%Y-%m-%d")]}**' # Saldo für den Tag berechnen if time.time() > day_in_list.timestamp(): time_duty = int(current_user.get_day_workhours(select_year.value, select_month.value, day)) * 3600 if time_duty < 0: ui.space() else: saldo = int(time_sum) - int(time_duty) # Nach Abwesenheitseinträgen suchen try: for i in list(user_absent): if int(i) == day and user_absent[i] != "UU": saldo = 0 except: pass general_saldo = general_saldo + saldo ui.markdown(convert_seconds_to_hours(saldo)).classes('text-right') else: ui.markdown("-").classes('text-center') def add_entry(day): with ui.dialog() as add_dialog, ui.card(): ui.markdown("###Eintrag hinzufügen") input_time = ui.time().classes('w-full justify-center') def add_entry_save(): if input_time.value == None: ui.notify("Bitte eine Uhrzeit auswählen.") return new_time_stamp = datetime.datetime(int(year_binder.value), int(month_binder.value), day, int(input_time.value[:2]), int(input_time.value[-2:])).timestamp() current_user = user(time_user.value) current_user.timestamp(stamptime=int(new_time_stamp)) calendar_card.clear() update_month_and_year() add_dialog.close() ui.notify("Eintrag hinzugefügt") with ui.grid(columns=3): ui.button("Speichern", on_click=add_entry_save) ui.space() ui.button("Abbrechen", on_click=add_dialog.close) add_dialog.open() add_dialog.move(calendar_card) def add_absence(absence_type, day): with ui.dialog() as dialog, ui.card().classes('w-[350px]'): ui.markdown(f'Für welchen Zeitraum soll *{absence_entries[absence_type]["name"]}* eingetragen werden?').classes('w-full') absence_dates = ui.date().props('range today-btn').classes('w-full justify-center') absence_dates._props['locale'] = {'daysShort': ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'], 'months': ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], 'monthsShort': ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']} absence_dates._props['title'] = absence_entries[absence_type]["name"] absence_dates._props['minimal'] = True if day < 10: day = f"0{str(day)}" else: day = str(day) if int(select_month.value) < 10: month = f"0{select_month.value}" else: month = select_month.value absence_dates.value = f"{select_year.value}-{month}-{day}" def add_absence_save(): # Bei nur einem Datum, direkt schreiben if isinstance(absence_dates.value, str): absence_date = absence_dates.value.split("-") current_user.update_absence(absence_date[0], absence_date[1], absence_date[2], absence_type) current_sel_month = select_month.value current_sel_year = select_year.value update_user() update_months() select_year.value = current_sel_year select_month.value = current_sel_month calendar_card.clear() update_month_and_year() # Bei Zeitbereich, aufteilen elif isinstance(absence_dates.value, dict): start_date = absence_dates.value["from"] end_date = absence_dates.value["to"] start_date = start_date.split("-") end_date = end_date.split("-") start_year = int(start_date[0]) end_year = int(end_date[0]) start_month = int(start_date[1]) end_month = int(end_date[1]) start_day = int(start_date[2]) end_day = int(end_date[2]) start_date = datetime.datetime(start_year, start_month, start_day) end_date = datetime.datetime(end_year, end_month, end_day) actual_date = start_date while actual_date <= end_date: absences = current_user.get_absence(actual_date.year, actual_date.month) if str(actual_date.day) in list(absences): current_user.del_absence(actual_date.year, actual_date.month, actual_date.day) ui.notify(f"Eintrag {absence_entries[absences[str(actual_date.day)]]['name']} am {actual_date.day}.{actual_date.month}.{actual_date.year} überschrieben.") current_user.update_absence(actual_date.year, actual_date.month, actual_date.day, absence_type) actual_date = actual_date + datetime.timedelta(days=1) clear_card() ui.notify("Einträge vorgenomomen") dialog.close() with ui.grid(columns=3).classes('w-full justify-center'): ui.button("Speichern", on_click=add_absence_save) ui.space() ui.button("Abbrechen", on_click=dialog.close) dialog.open() dialog.move(calendar_card) def edit_notes(day): notes = current_user.get_day_notes(select_year.value, select_month.value, day) def del_note_entry(user): try: del(notes[user]) username_labels[user].delete() note_labels[user].delete() del_buttons[user].delete() except KeyError: ui.notify("Kann nicht gelöscht werden. Eintrag wurde noch nicht gespeichert.") def save_notes(): if not note_labels["admin"].is_deleted: notes["admin"] = note_labels["admin"].value current_user.write_notes(select_year.value, select_month.value, day, notes) timetable.refresh() dialog.close() with ui.dialog() as dialog, ui.card(): # Notizen username_labels = { } note_labels = { } del_buttons = { } ui.markdown(f'**Notizen für {day}.{current_month}.{current_year}**') with ui.grid(columns='auto auto auto'): admin_settings = load_adminsettings() # Beschreibungsfeld für Admin username_labels["admin"] = ui.markdown("Administrator:") # Textarea für Admin note_labels["admin"] = ui.textarea() del_buttons["admin"] = ui.button(icon='remove', on_click=lambda user="admin": del_note_entry(user)) for name, text in notes.items(): if name != "admin": username_labels["user"] = ui.markdown(current_user.fullname) note_labels["user"] = ui.markdown(text) del_buttons["user"] = ui.button(icon='remove', on_click=lambda user="user": del_note_entry(user)) elif name == "admin": note_labels["admin"].value = text with ui.row(): ui.button("OK", on_click=save_notes) ui.button("Abbrechen", on_click=dialog.close) dialog.open() dialog.move(calendar_card) with ui.button(icon='menu') as menu_button: with ui.menu() as menu: menu_item = ui.menu_item("Zeiteintrag hinzufügen", lambda day=day: add_entry(day)) if archive_status: menu_item.disable() if datetime.datetime.now().day < day: menu_item.disable() menu_item.tooltip("Kann keine Zeiteinträge für die Zukunft vornehmen.") ui.separator() menu_item = ui.menu_item("Notizen bearbeiten", lambda day=day: edit_notes(day)) if archive_status: menu_item.disable() ui.separator() for i in list(absence_entries): menu_item = ui.menu_item(f"{absence_entries[i]['name']} eintragen", lambda absence_type=i, day=day: add_absence(absence_type, day)) if archive_status: menu_item.disable() if str(day) in list(user_absent): menu_item.disable() if archive_status: menu_button.disable() #ui.button("Eintrag hinzufügen", on_click=lambda day=day: add_entry(day)) #4x leer und dann Gesamtsaldo ui.space().classes('col-span-5') ui.markdown(f"{convert_seconds_to_hours(general_saldo)}").classes('text-right') ui.markdown("Stunden aus Vormonat").classes('col-span-5 text-right') last_months_overtime = current_user.get_last_months_overtime(select_year.value, select_month.value) ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)}").classes('text-right') ui.markdown("Gesamtsaldo").classes('col-span-5 text-right') ui.markdown(f"**{convert_seconds_to_hours(general_saldo + last_months_overtime)}**").classes('text-right') table_grid.move(calendar_card) update_month_and_year() def clear_card(): calendar_card.clear() button_update.delete() update_month_and_year() current_user = user(time_user.value) month_header.set_content(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**") month_header.set_content(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**") timetable() button_update = ui.button("Aktualisieren", on_click=timetable.refresh) button_update.move(timetable_header) with ui.tab_panel(settings): with ui.grid(columns='auto auto'): with ui.card(): ui.markdown("**Administrationsbenutzer:**") with ui.grid(columns=2): def save_admin_settings(): output_dict = { } output_dict["admin_user"] = admin_user.value if admin_password.value != "": output_dict["admin_password"] = hash_password(admin_password.value) else: output_dict["admin_password"] = data["admin_password"] output_dict["port"] = port.value output_dict["secret"] = secret output_dict["touchscreen"] = touchscreen_switch.value output_dict["times_on_touchscreen"] = timestamp_switch.value output_dict["photos_on_touchscreen"] = photo_switch.value output_dict["picture_height"] = picture_height_input.value output_dict["button_height"] = button_height_input.value output_dict["user_notes"] = notes_switch.value output_dict["holidays"] = data["holidays"] json_dict = json.dumps(output_dict, indent=4) with open(os.path.join(scriptpath, usersettingsfilename), "w") as outputfile: outputfile.write(json_dict) if int(old_port) != int(port.value): with ui.dialog() as dialog, ui.card(): ui.markdown("Damit die Porteinstellungen wirksam werden, muss der Server neu gestartet werden.") ui.button("OK", on_click=lambda: dialog.close()) dialog.open() ui.notify("Einstellungen gespeichert") timetable.refresh() ui.markdown("Benutzername des Adminstrators") admin_user = ui.input().tooltip("Geben Sie hier den Benutzernamen für den Adminstationsnutzer ein") admin_user.value = data["admin_user"] ui.markdown("Passwort des Administrators") admin_password = ui.input(password=True).tooltip("Geben Sie hier das Passwort für den Administationsnutzer ein. Merken Sie sich dieses Passwort gut. Es kann nicht über das Webinterface zurückgesetzt werden.") secret = data["secret"] with ui.card(): ui.markdown("**Systemeinstellungen:**") with ui.grid(columns=2): def check_is_number(number): try: number = int(number) return True except: return False ui.markdown("Port:") port = ui.input(validation={"Nur ganzzahlige Portnummern erlaubt": lambda value: check_is_number(value), "Portnummer zu klein": lambda value: len(value)>=2}).tooltip("Geben Sie hier die Portnummer ein, unter der die Zeiterfassung erreichbar ist.").props('size=5') old_port = data["port"] port.value = old_port with ui.card(): ui.markdown("**Einstellungen für das Touchscreenterminal:**") with ui.column(): touchscreen_switch = ui.switch("Touchscreenterminal aktivieren") touchscreen_switch.value = data["touchscreen"] timestamp_switch = ui.switch("Stempelzeiten anzeigen").bind_visibility_from(touchscreen_switch, 'value') photo_switch = ui.switch("Fotos anzeigen").bind_visibility_from(touchscreen_switch, 'value') timestamp_switch.value = bool(data["times_on_touchscreen"]) with ui.row().bind_visibility_from(touchscreen_switch, 'value'): photo_switch.value = bool(data["photos_on_touchscreen"]) with ui.row().bind_visibility_from(photo_switch, 'value'): ui.markdown("Maximale Bilderöhe") picture_height_input = ui.input(validation={"Größe muss eine Ganzzahl sein.": lambda value: check_is_number(value), "Größe muss größer 0 sein": lambda value: int(value)>0}).props('size=5') picture_height_input.value = data["picture_height"] ui.markdown('px') with ui.row().bind_visibility_from(touchscreen_switch, 'value'): ui.markdown("Minimale Buttonhöhe") def compare_button_height(height): if not photo_switch.value: return True elif int(height) < int(picture_height_input.value): return False else: return True button_height_input = ui.input(validation={"Größe muss eine Ganzzahl sein.": lambda value: check_is_number(value), "Größe muss größer 0 sein": lambda value: int(value)>0, "Buttons dürfen nicht kleiner als die Fotos sein": lambda value: compare_button_height(value)}).props('size=5') button_height_input.value = data["button_height"] photo_switch.on_value_change(button_height_input.validate) picture_height_input.on_value_change(button_height_input.validate) ui.markdown('px') with ui.card(): ui.markdown("**Einstellungen für Benutzerfrontend**") notes_switch = ui.switch("Notizfunktion aktiviert", value=data["user_notes"]) def holiday_section(): with ui.card(): ui.markdown('**Feiertage:**') reset_visibility = ValueBinder() reset_visibility.value = False def new_holiday_entry(): def add_holiday_to_dict(): year_from_picker = int(datepicker.value.split("-")[0]) month_from_picker = int(datepicker.value.split("-")[1]) day_from_picker = int(datepicker.value.split("-")[2]) for i in range(0, int(repetition.value)): repetition_date_dt = datetime.datetime(year_from_picker + i, month_from_picker, day_from_picker) date_entry = repetition_date_dt.strftime('%Y-%m-%d') data["holidays"][date_entry] = description.value holiday_buttons_grid.refresh() reset_visibility.value = True dialog.close() with ui.dialog() as dialog, ui.card(): with ui.grid(columns='auto auto'): ui.markdown('Geben Sie den neuen Feiertag ein:').classes('col-span-2') datepicker = ui.date(value=datetime.datetime.now().strftime('%Y-%m-%d')).classes('col-span-2') description = ui.input('Beschreibung').classes('col-span-2') repetition = ui.number('Für Jahre wiederholen', value=1, min=1, precision=0).classes('col-span-2') ui.button("OK", on_click=add_holiday_to_dict) ui.button('Abbrechen', on_click=dialog.close) dialog.open() @ui.refreshable def holiday_buttons_grid(): holidays = list(data["holidays"]) holidays.sort() year_list = [] # Jahresliste erzeugen for i in holidays: i_split = i.split("-") year = int(i_split[0]) year_list.append(year) year_list = list(set(year_list)) year_dict = {} # Jahresdictionary konstruieren for i in year_list: year_dict[i] = [] for i in holidays: i_split = i.split("-") year = int(i_split[0]) month = int(i_split[1]) day = int(i_split[2]) date_dt = datetime.datetime(year, month, day) year_dict[year].append(date_dt) def del_holiday_entry(entry): del(data['holidays'][entry.strftime('%Y-%m-%d')]) reset_visibility.value = True holiday_buttons_grid.refresh() def defined_holidays(): with ui.dialog() as dialog, ui.card(): ui.markdown("Bitte wählen Sie aus, welche Feiertage eingetragen werden sollen. Vom Osterdatum abhängige Feiertage werden für die verschiedenen Jahre berechnet.:") with ui.grid(columns='auto auto'): with ui.column().classes('gap-0'): # Auswahlen für Feiertage checkbox_classes = 'py-0' new_year = ui.checkbox("Neujahr (1. Januar)").classes(checkbox_classes) heilige_drei_koenige = ui.checkbox("Heilige Drei Könige (6. Januar)").classes(checkbox_classes) womens_day = ui.checkbox("Internationaler Frauentag (8. März)").classes(checkbox_classes) gruendonnerstag = ui.checkbox("Gründonnerstag (berechnet)").classes(checkbox_classes) karfreitag = ui.checkbox("Karfreitag (berechnet").classes(checkbox_classes) easter_sunday = ui.checkbox("Ostersonntag (berechnet)").classes(checkbox_classes) easter_monday = ui.checkbox("Ostermontag (berechnet)").classes(checkbox_classes) first_of_may = ui.checkbox("Tag der Arbeit (1. Mai)").classes(checkbox_classes) liberation_day = ui.checkbox("Tag der Befreiung (8. Mai)").classes(checkbox_classes) ascension_day = ui.checkbox("Christi Himmelfahrt (berechnet)").classes(checkbox_classes) whitsun_sunday = ui.checkbox("Pfingssonntag (berechnet)").classes(checkbox_classes) whitsun_monday = ui.checkbox("Pfingsmontag (berechnet)").classes(checkbox_classes) fronleichnam = ui.checkbox("Fronleichnam (berechnet)").classes(checkbox_classes) peace_party = ui.checkbox("Friedensfest (Augsburg - 8. August)").classes(checkbox_classes) mary_ascension = ui.checkbox("Mariä Himmelfahrt (15. August)").classes(checkbox_classes) childrens_day = ui.checkbox("Weltkindertag (20. September)").classes(checkbox_classes) unity_day = ui.checkbox("Tag der deutschen Einheit (3. Oktober)").classes(checkbox_classes) reformation_day = ui.checkbox("Reformationstag (30. Oktober)").classes(checkbox_classes) all_hallows = ui.checkbox("Allerheiligen (1. November)").classes(checkbox_classes) praying_day = ui.checkbox("Buß- und Bettag (berechnet)").classes(checkbox_classes) christmas_day = ui.checkbox("1. Weihnachtsfeiertag (25. Dezember)").classes(checkbox_classes) boxing_day = ui.checkbox("2. Weihnachtsfeiertag (26. Dezember)").classes(checkbox_classes) def enter_holidays(): for year in range (int(starting_year.value), int(end_year.value) + 1): ostersonntag = dateutil.easter.easter(year) if new_year.value: data["holidays"][f"{year}-01-01"] = f"Neujahr" if heilige_drei_koenige.value: data["holidays"][f"{year}-01-06"] = f"Hl. Drei Könige" if womens_day.value: data["holidays"][f"{year}-03-08"] = f"Intern. Frauentag" if gruendonnerstag.value: datum_dt = ostersonntag - datetime.timedelta(days=3) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Gründonnerstag" if karfreitag.value: datum_dt = ostersonntag - datetime.timedelta(days=2) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Karfreitag" if easter_sunday.value: datum_dt = ostersonntag datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = "Ostersonntag" if easter_monday.value: datum_dt = ostersonntag + datetime.timedelta(days=1) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = "Ostermontag" if first_of_may.value: data["holidays"][f"{year}-05-01"] = f"Tag der Arbeit" if liberation_day.value: data["holidays"][f"{year}-05-08"] = f"Tag der Befreiung" if ascension_day.value: datum_dt = ostersonntag + datetime.timedelta(days=39) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Christi Himmelfahrt" if whitsun_sunday.value: datum_dt = ostersonntag + datetime.timedelta(days=49) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Pfingssonntag" if whitsun_monday.value: datum_dt = ostersonntag + datetime.timedelta(days=49) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Pfingstmontag" if fronleichnam.value: datum_dt = ostersonntag + datetime.timedelta(days=60) datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Fronleichnam" if peace_party.value: data["holidays"][f"{year}-08-08"] = f"Friedensfest" if mary_ascension.value: data["holidays"][f"{year}-08-15"] = f"Mariä Himmelfahrt" if childrens_day.value: data["holidays"][f"{year}-09-20"] = f"Intern. Kindertag" if unity_day.value: data["holidays"][f"{year}-10-03"] = f"Tag der deutschen Einheit" if reformation_day.value: data["holidays"][f"{year}-10-30"] = f"Reformationstag" if all_hallows.value: data["holidays"][f"{year}-11-01"] = f"Allerheiligen" if praying_day.value: starting_day = datetime.datetime(year, 11 ,23) for i in range(1, 8): test_day = starting_day - datetime.timedelta(days=-i) if test_day.weekday() == 2: datum_dt = test_day break datum = datum_dt.strftime("%Y-%m-%d") data["holidays"][f"{datum}"] = f"Bu0- und Bettag" if christmas_day.value: data["holidays"][f"{year}-12-25"] = f"1. Weihnachtsfeiertag" if boxing_day.value: data["holidays"][f"{year}-12-26"] = f"2. Weihnachtsfeiertag" reset_visibility.value = True dialog.close() holiday_buttons_grid.refresh() with ui.column(): starting_year = ui.number(value=datetime.datetime.now().year, label="Startjahr") end_year = ui.number(value=starting_year.value, label="Endjahr") with ui.row(): ui.button("Anwenden", on_click=enter_holidays) ui.button("Abbrechen", on_click=dialog.close) dialog.open() def reset_holidays(): old_data = load_adminsettings() data["holidays"] = old_data["holidays"] reset_visibility.value = False holiday_buttons_grid.refresh() with ui.grid(columns='auto auto'): ui.space() with ui.row(): ui.button("Gesetzliche Feiertage eintragen", on_click=defined_holidays).tooltip("Hier können Sie automatisiert gesetzliche Feiertage in Deutschland eintragen.") ui.button("Eigener Eintrag", on_click=new_holiday_entry).tooltip("Hier können Sie einen eigenen Feiertag definieren.") ui.button("Zurücksetzen", icon="undo", on_click=reset_holidays).bind_visibility_from(reset_visibility, 'value').classes('bg-red').tooltip("Hier können Sie ungespeicherte Änderungen zurücknehmen.") ui.separator().classes('col-span-2') for year_entry in year_list: ui.markdown(f"{str(year_entry)}:") with ui.row(): for entry in year_dict[year_entry]: date_label = entry.strftime("%d.%m.") ui.button(f"{data['holidays'][entry.strftime('%Y-%m-%d')]} ({date_label})", color='cyan-300', on_click=lambda entry=entry: del_holiday_entry(entry)).classes('text-sm') holiday_buttons_grid() holiday_section() ui.button("Speichern", on_click=save_admin_settings).tooltip("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.") with ui.tab_panel(users): ui.markdown("###Benutzerverwaltung") workhours = [ ] with ui.row(): def user_selection_changed(): try: if user_selection.value != None: current_user = user(user_selection.value) username_input.value = current_user.username fullname_input.value = current_user.fullname #password_input.value = current_user.password usersettingscard.visible = True api_key_input.value = current_user.api_key api_link_column.clear() for i in app.urls: with ui.row() as link_row: link_string = f'{i}/api/stamp/{api_key_input.value}' link = ui.link(f'{i}/api/stamp/"API-Schlüssel"', link_string).tooltip(("ACHTUNG: Klick auf den Link löst Stempelaktion aus!")) qr = segno.make_qr(link_string).svg_data_uri() with ui.image(qr).classes('w-1/3'): with ui.tooltip().classes('bg-white border'): ui.image(qr).classes('w-64') link_row.move(api_link_column) workhours_select.clear() workhour_list = list(current_user.workhours) workhour_list.sort() workhours_select.set_options(workhour_list) workhours_select.value = workhour_list[0] workinghourscard.visible = True user_photo.set_source(current_user.photofile) user_photo.force_reload() user_photo.set_visibility(os.path.exists(current_user.photofile)) delete_button.set_visibility(os.path.exists(current_user.photofile)) except: pass # workhours_selection_changed(list(current_user.workhours)[0]) def workhours_selection_changed(): if workhours_select.value != None: current_user = user(user_selection.value) selected_workhours = current_user.workhours[workhours_select.value] for key, hours in selected_workhours.items(): try: days[int(key)-1].value = hours except: if key == 0: days[6].value = hours elif key == "vacation": vacation_input.value = hours def save_user_settings(): def save_settings(): current_user = user(user_selection.value) current_user.username = username_input.value current_user.fullname = fullname_input.value current_user.password = hash_password(password_input.value) current_user.api_key = api_key_input.value current_user.write_settings() password_input.value = "" userlist = list_users() userlist.sort() user_selection.clear() user_selection.set_options(userlist) user_selection.value = current_user.username user_selection_changed() dialog.close() ui.notify("Einstellungen gespeichert") with ui.dialog() as dialog, ui.card(): if user_selection.value != username_input.value: ui.markdown("**Benutzername wurde geändert.**") ui.markdown(f"Benutzerdaten werden in den neuen Ordner {username_input.value}") ui.markdown("Sollen die Einstellungen gespeichert werden?") with ui.row(): ui.button("Speichern", on_click=save_settings) ui.button("Abbrechen", on_click=dialog.close) dialog.open() def del_user(): current_user = user(user_selection.value) def del_definitely(): if current_user.username == time_user.value: if userlist.index(current_user.username) == 0: time_user.value = userlist[1] else: time_user.value = userlist[0] current_user.del_user() #userlist = list_users() #userlist.sort() #user_selection.clear() #user_selection.set_options(userlist) #user_selection.value = userlist[0] update_userlist() time_user.set_options(userlist) user_ui.refresh() dialog.close() ui.notify("Benutzer gelöscht") with ui.dialog() as dialog, ui.card(): ui.markdown(f"Soll der Benutzer *{current_user.username}* gelöscht werden?") ui.markdown("**Dies kann nicht rückgängig gemacht werden?**") with ui.row(): ui.button("Löschen", on_click=del_definitely) ui.button("Abbrechen", on_click=dialog.close) dialog.open() def save_workhours(): def save_settings(): current_user = user(user_selection.value) construct_dict = { } for i in range(7): if i < 7: construct_dict[i+1] = days[i].value elif i == 7: construct_dict[0] = days[i].value construct_dict["vacation"] = vacation_input.value current_user.workhours[workhours_select.value] = construct_dict current_user.write_settings() dialog.close() ui.notify("Einstellungen gespeichert") with ui.dialog() as dialog, ui.card(): ui.markdown("Sollen die Änderungen an den Arbeitsstunden und/oder Urlaubstagen gespeichert werden?") with ui.row(): ui.button("Speichern", on_click=save_settings) ui.button("Abrrechen", on_click=dialog.close) dialog.open() def delete_workhour_entry(): def delete_entry(): current_user = user(user_selection.value) del current_user.workhours[workhours_select.value] current_user.write_settings() workhour_list = list(current_user.workhours) workhours_select.clear() workhours_select.set_options(workhour_list) workhours_select.set_value(workhour_list[-1]) #workhours_selection_changed(current_user.workhours[0]) dialog.close() ui.notify("Eintrag gelöscht" "") with ui.dialog() as dialog, ui.card(): current_user = user(user_selection.value) if len(current_user.workhours) > 1: ui.markdown(f"Soll der Eintrag *{workhours_select.value}* wirklich gelöscht werden?") ui.markdown("**Dies kann nicht rückgängig gemacht werden.**") with ui.row(): ui.button("Löschen", on_click=delete_entry) ui.button("Abbrechen", on_click=dialog.close) else: ui.markdown("Es gibt nur einen Eintrag. Dieser kann nicht gelöscht werden.") ui.button("OK", on_click=dialog.close) dialog.open() def dialog_new_user(): def create_new_user(): if user_name_input.validate(): new_user(user_name_input.value) update_userlist() time_user.set_options(userlist) user_ui.refresh() dialog.close() else: ui.notify("Ungültiger Benutzername") with ui.dialog() as dialog, ui.card(): ui.markdown("Geben Sie den Benutzernamen für das neue Konto an:") user_name_input = ui.input(label="Benutzername", validation={'Leerer Benutzername nicht erlaubt': lambda value: len(value) != 0, 'Leerzeichen im Benutzername nicht erlaubt': lambda value: " " not in value, 'Benutzername schon vergeben': lambda value: value not in userlist}).on('keypress.enter', create_new_user) with ui.row(): ui.button("OK", on_click=create_new_user) ui.button("Abbrechen", on_click=dialog.close) dialog.open() @ui.refreshable def user_ui(): userlist = list_users() userlist.sort() with ui.column(): global user_selection user_selection = ui.select(options=userlist, with_input=True, on_change=user_selection_changed) user_selection.value = userlist[0] ui.button("Neu", on_click=dialog_new_user) user_ui() with ui.column(): @ui.refreshable def usersettings_card(): global usersettingscard with ui.card() as usersettingscard: ui.markdown("**Benutzereinstellungen**") with ui.grid(columns="auto 1fr") as usersettingsgrid: ui.markdown("Benutzername:") global username_input username_input = ui.input() ui.markdown("Voller Name:") global fullname_input fullname_input = ui.input() ui.markdown("Passwort") global password_input password_input = ui.input(password=True) password_input.value = "" ui.markdown("API-Schlüssel:") with ui.row(): global api_key_input api_key_input = ui.input().props('size=37') def new_api_key(): api_key_input.value = hashlib.shake_256(bytes(f'{username_input.value}_{datetime.datetime.now().timestamp()}', 'utf-8')).hexdigest(20) ui.button("Neu", on_click=new_api_key).tooltip("Neuen API-Schlüssel erzeugen. Wird erst beim Klick auf Speichern übernommen und entsprechende Links und QR-Codes aktualisiert") ui.markdown('Aufruf zum Stempeln:') global api_link_column with ui.column().classes('gap-0') as api_link_column: global stamp_link stamp_link = [ ] for i in app.urls: stamp_link.append(ui.link(f'{i}/api/stamp/"API-Schüssel"')) with ui.grid(columns=2): ui.button("Speichern", on_click=save_user_settings).tooltip("Klicken Sie hier um die Änderungen zu speichern.") ui.button("Löschen", on_click=del_user) usersettings_card() with ui.card() as photocard: ui.markdown('**Foto**') current_user = user(user_selection.value) user_photo = ui.image(current_user.photofile) def handle_upload(e: events.UploadEventArguments): picture = e.content.read() current_user = user(user_selection.value) with open(current_user.photofile, 'wb') as outoutfile: outoutfile.write(picture) uploader.reset() user_selection_changed() def del_photo(): current_user = user(user_selection.value) current_user.delete_photo() user_selection_changed() uploader = ui.upload(label="Foto hochladen", on_upload=handle_upload).props('accept=.jpg|.jpeg').classes('max-w-full') delete_button = ui.button("Löschen", on_click=del_photo) with ui.card() as workinghourscard: workhours = [] ui.markdown("**Arbeitszeiten**") with ui.card(): def calculate_weekhours(): sum = 0 for i in range(7): try: sum = float(days[i].value) + sum except: pass workhours_sum.set_content(str(sum)) with ui.grid(columns='auto auto auto'): ui.markdown("gültig ab:") workhours_select = ui.select(options=workhours, on_change=workhours_selection_changed).classes('col-span-2') days = [ ] weekdays = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"] counter = 0 for day in weekdays: ui.markdown(f"{day}:") days.append(ui.number(on_change=calculate_weekhours).props('size=3')) ui.markdown('Stunden') counter = counter + 1 ui.separator().classes('col-span-full') ui.markdown("**Summe:**") workhours_sum = ui.markdown() ui.markdown("Stunden") with ui.card(): with ui.grid(columns='auto auto auto'): ui.markdown("Urlaubstage") vacation_input = ui.number().props('size=3') ui.markdown("Tage") with ui.row(): ui.button("Speichern", on_click=save_workhours) ui.button("Löschen", on_click=delete_workhour_entry) def new_workhours_entry(): current_user = user(user_selection.value) def add_workhours_entry(): workhours_dict = { } for i in range(7): workhours_dict[i] = 0 workhours_dict["vacation"] = 0 current_user.workhours[date_picker.value] = workhours_dict current_user.write_settings() workhours_select.clear() workhours_list = list(current_user.workhours) workhours_list.sort() workhours_select.set_options(workhours_list) workhours_select.value = date_picker.value dialog.close() ui.notify("Eintrag angelegt") with ui.dialog() as dialog, ui.card(): ui.markdown("Geben Sie das Gültigkeitsdatum an, ab wann die Einträge gültig sein sollen.") date_picker = ui.date() with ui.row(): ui.button("OK", on_click=add_workhours_entry) ui.button("Abbrechen", on_click=dialog.close) dialog.open() ui.button("Neu", on_click=new_workhours_entry) user_selection_changed() # Alternativ zur Loginseite navigieren else: login = login_mask() #ui.navigate.to("/login")