diff --git a/lib/admin.py b/lib/admin.py
index eb1e955..17cc02a 100644
--- a/lib/admin.py
+++ b/lib/admin.py
@@ -41,9 +41,12 @@ def page_admin():
ui.button("Logout", on_click=admin_logout)
+ updates_available = ValueBinder()
+ updates_available.value = False
+
with ui.tabs() as tabs:
- time_overview = ui.tab('Zeitübersichten')
+ time_overview = ui.tab('Zeitdaten')
settings = ui.tab('Einstellungen')
users = ui.tab('Benutzer')
backups = ui.tab('Backups')
@@ -56,554 +59,669 @@ def page_admin():
update_userlist()
- with (((ui.tab_panels(tabs, value=time_overview)))):
+ with ui.tab_panels(tabs, value=time_overview):
with ui.tab_panel(time_overview):
- ui.markdown("##Übersichten")
+ with ui.tabs() as overview_tabs:
+ user_month_overview = ui.tab('Monatsansicht')
+ user_summary = ui.tab("Zusammenfassung")
+ vacation_applications = ui.tab("Urlaubsanträge")
- # Tabelle konstruieren
- with ui.card().classes('w-full'):
+ with ui.tab_panels(overview_tabs, value = user_month_overview):
+ with ui.tab_panel(user_month_overview).classes('w-full'):
+ ui.markdown("##Übersichten")
- with ui.row() as timetable_header:
- year_binder = ValueBinder()
- month_binder = ValueBinder()
+ # Tabelle konstruieren
+ with ui.card().classes('w-full'):
- def update_months():
- current_user = user(time_user.value)
- available_months = current_user.get_months(year_binder.value)
- available_months_dict = { }
+ with ui.row() as timetable_header:
+ year_binder = ValueBinder()
+ month_binder = ValueBinder()
- for element in available_months:
- available_months_dict[element] = calendar.month_name[int(element)]
+ def update_months():
+ current_user = user(time_user.value)
+ available_months = current_user.get_months(year_binder.value)
+ available_months_dict = { }
- select_month.clear()
- select_month.set_options(available_months_dict)
- select_month.value = list(available_months)[0]
+ for element in available_months:
+ available_months_dict[element] = calendar.month_name[int(element)]
- 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.clear()
+ select_month.set_options(available_months_dict)
select_month.value = list(available_months)[0]
- except NameError:
- pass
- ui.markdown("Benutzer:")
+ def update_user():
+ current_user = user(time_user.value)
+ available_years = current_user.get_years()
- time_user = ui.select(options=userlist, on_change=update_user)
- time_user.value = userlist[0]
- user_to_select_for_start = userlist[0]
+ try:
+ select_year.clear()
+ select_year.set_options(available_years)
- 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 = { }
+ try:
+ select_year.value = str(datetime.datetime.now().year)
+ except:
+ select_year.value = list(available_years)[0]
+ update_months()
+ try:
+ select_month.value = datetime.datetime.now().month
+ except:
+ select_month.value = list(available_months)[0]
+ except NameError:
+ pass
- for element in available_months:
- available_months_dict[element] = calendar.month_name[int(element)]
+ ui.markdown("Benutzer:")
- if current_month in available_months:
- set_month = current_month
- else:
- set_month = available_months[0]
+ time_user = ui.select(options=userlist, on_change=update_user)
+ time_user.value = userlist[0]
+ user_to_select_for_start = userlist[0]
- if str(current_year) in available_years:
- set_year = str(current_year)
- else:
- set_year = (available_years[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 = { }
- 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')
+ for element in available_months:
+ available_months_dict[element] = calendar.month_name[int(element)]
- month_header = ui.markdown(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
+ if current_month in available_months:
+ set_month = current_month
+ else:
+ set_month = available_months[0]
- # 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)
+ if str(current_year) in available_years:
+ set_year = str(current_year)
+ else:
+ set_year = (available_years[0])
- 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
+ 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')
- json_dict = json.dumps(data)
+ month_header = ui.markdown(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
- with open(filename, "w") as outputfile:
- outputfile.write(json_dict)
- timetable.refresh()
- dialog.close()
+ # 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)
- 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()
+ 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
- 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()
+ json_dict = json.dumps(data)
- 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()
+ with open(filename, "w") as outputfile:
+ outputfile.write(json_dict)
+ timetable.refresh()
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.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()
- 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)
+ 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:
- 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:
+ 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()
- 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")]}**'
+ 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] = [ ]
- # Saldo für den Tag berechnen
+ # 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))
- if time.time() > day_in_list.timestamp():
+ general_saldo = 0
- 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
+ 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)
- 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
+ # Buchungen
with ui.row():
- ui.button("OK", on_click=save_notes)
- ui.button("Abbrechen", on_click=dialog.close)
- dialog.open()
- dialog.move(calendar_card)
+ 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()
- 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))
+ 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_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()
+ menu_button.disable()
- #ui.button("Eintrag hinzufügen", on_click=lambda day=day: add_entry(day))
+ #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')
+ #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)
+ table_grid.move(calendar_card)
- update_month_and_year()
+ 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}**")
+ 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)
+ 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(user_summary):
+
+ global overview_table
+ @ui.refreshable
+ def overview_table():
+ ov_columns = [ {'label': 'Benutzername', 'name': 'username', 'field': 'username'},
+ {'label': 'Name', 'name': 'name', 'field': 'name'},
+ {'label': 'Zeitsaldo', 'name': 'time', 'field': 'time'},
+ {'label': 'Urlaub', 'name': 'vacation', 'field': 'vacation'},
+ {'label': 'Resturlaub', 'name': 'vacation_remaining', 'field': 'vacation_remaining'},
+ {'label': 'Krankheit', 'name': 'illness', 'field': 'illness'}]
+ ov_rows = [ ]
+ for username in userlist:
+ actual_user = user(username)
+ ov_rows.append(
+ {'username': username, 'name': actual_user.fullname, 'time': f'{convert_seconds_to_hours(actual_user.get_last_months_overtime() + actual_user.get_worked_time()[0])} h', 'vacation': f'{actual_user.count_absence_days("U")} Tage',
+ 'vacation_remaining': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U")} Tage', 'illness': f'{actual_user.count_absence_days("K")} Tage'})
+ ui.table(columns=ov_columns, rows=ov_rows, row_key='username', column_defaults={'align': 'left', 'sortable': True})
+
+ overview_table()
+ ui.button("Aktualisieren", on_click=overview_table.refresh)
+
+ with ui.tab_panel(vacation_applications):
+ date_string = '%d.%m.%Y'
+
+ @ui.refreshable
+ def va_table():
+ va_columns = [ {'label': 'Benutzername', 'name': 'username', 'field': 'username'},
+ {'label': 'Name', 'name': 'name', 'field': 'name'},
+ {'label': 'key', 'name': 'key', 'field': 'key', 'classes': 'hidden', 'headerClasses': 'hidden'},
+ {'label': 'Anfang', 'name': 'start', 'field': 'start'},
+ {'label': 'Ende', 'name': 'end', 'field': 'end'},
+ {'label': 'Resturlaub', 'name': 'vacation_remaining', 'field': 'vacation_remaining'},
+ {'label': 'Resturlaub nach Genehmigung', 'name': 'vacation_remaining_after_submission', 'field': 'vacation_remaining_after_submission'}
+ ]
+ va_rows = [ ]
+
+ for username in userlist:
+ actual_user = user(username)
+ open_va = actual_user.get_open_vacation_applications()
+ for i, dates in open_va.items():
+ startdate_dt = datetime.datetime.strptime(dates[0], '%Y-%m-%d')
+ enddate_dt = datetime.datetime.strptime(dates[1], '%Y-%m-%d')
+ vacation_start_to_end = (enddate_dt-startdate_dt).days
+ vacation_duration = 0
+ for day_counter in range(0, vacation_start_to_end):
+ new_date = startdate_dt + datetime.timedelta(days=day_counter)
+ if int(actual_user.get_day_workhours(new_date.year, new_date.month, new_date.day)) > 0:
+ if not datetime.datetime(new_date.year, new_date.month, new_date.day).strftime('%Y-%m-%d') in list(load_adminsettings()["holidays"]):
+ vacation_duration += 1
+
+ va_rows.append({'username': username,
+ 'name': actual_user.fullname,
+ 'key': f'{username}-{i}',
+ 'start': startdate_dt.strftime(date_string),
+ 'end': enddate_dt.strftime(date_string),
+ 'vacation_remaining': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U", startdate_dt.year)} Tage',
+ 'vacation_remaining_after_submission': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U", startdate_dt.year) - vacation_duration } Tage'})
+ global vacation_table
+ vacation_table = ui.table(columns=va_columns, rows=va_rows, row_key='key', selection='single', column_defaults={'align': 'left', 'sortable': True})
+ va_table()
+
+ def approve_vacation():
+ global vacation_table
+ for selection in vacation_table.selected:
+ key = selection["key"]
+ username = key.split("-")[0]
+ index = key.split("-")[1]
+ actual_user = user(username)
+ startdate_dt = datetime.datetime.strptime(selection["start"], date_string)
+ enddate_dt = datetime.datetime.strptime(selection["end"], date_string)
+ delta = (enddate_dt - startdate_dt).days
+ for i in range(0, delta):
+ new_date = startdate_dt + datetime.timedelta(days=i)
+ if int(actual_user.get_day_workhours(new_date.year, new_date.month, new_date.day)) > 0:
+ if not datetime.datetime(new_date.year, new_date.month, new_date.day).strftime('%Y-%m-%d') in list(load_adminsettings()["holidays"]):
+ actual_user.update_absence(new_date.year, new_date.month, new_date.day, "U")
+ ui.notify(f"Urlaub vom {selection['start']} bis {selection['end']} für {actual_user.fullname} eingetragen")
+ try:
+ retract_result = actual_user.revoke_vacation_application(index)
+ except IndexError:
+ ui.notify("Fehler beim Entfernen des Urlaubsantrages nach dem Eintragen.")
+ va_table.refresh()
+ timetable.refresh()
+ overview_table.refresh()
+
+ def deny_vacation():
+ for selection in vacation_table.selected:
+ key = selection["key"]
+ username = key.split("-")[0]
+ index = key.split("-")[1]
+ actual_user = user(username)
+ try:
+ retract_result = actual_user.revoke_vacation_application(index)
+ ui.notify(f"Urlaubsantrag vom {selection['start']} bis {selection['end']} für {actual_user.fullname} entfernt.")
+ except IndexError:
+ ui.notify("Fehler beim Entfernen des Urlaubsantrages. Ggf. wurde dieser zurückgezogen.")
+ va_table.refresh()
+ overview_table.refresh()
+
+ ui.button("Aktualisieren", on_click=va_table.refresh)
+ ui.separator()
+ with ui.grid(columns=2):
+ ui.button("Genehmigen", on_click=approve_vacation)
+ ui.button("Ablehnen", on_click=deny_vacation)
with ui.tab_panel(settings):
with ui.grid(columns='auto auto'):
@@ -1240,6 +1358,7 @@ Dies kann nicht rückgängig gemacht werden!''')
ui.button("Speichern", on_click=save_workhours)
ui.button("Löschen", on_click=delete_workhour_entry)
user_selection_changed()
+
with ui.tab_panel(backups):
try:
diff --git a/lib/api.py b/lib/api.py
index e047134..58676d9 100644
--- a/lib/api.py
+++ b/lib/api.py
@@ -525,7 +525,7 @@ def json_info(api_key: str):
data["time"]["overall"] = time_saldo
data["vacation"] = { }
data["vacation"]["claim"] = current_user.get_vacation_claim(now_dt.year, now_dt.month, now_dt.day)
- data["vacation"]["used"] = current_user.count_vacation_days(now_dt.year)
+ data["vacation"]["used"] = current_user.count_absence_days("U", now_dt.year)
data["vacation"]["remaining"] = data["vacation"]["claim"] - data["vacation"]["used"]
return data
break
diff --git a/lib/definitions.py b/lib/definitions.py
index 2c54691..ffa40f0 100644
--- a/lib/definitions.py
+++ b/lib/definitions.py
@@ -17,6 +17,7 @@ backupfolder = str(os.path.join(scriptpath, "backup"))
usersettingsfilename = "settings.json"
photofilename = "photo.jpg"
+va_file = "vacation_application.json"
# Status
diff --git a/lib/homepage.py b/lib/homepage.py
index a5f00a3..4e7d12a 100644
--- a/lib/homepage.py
+++ b/lib/homepage.py
@@ -187,38 +187,92 @@ def homepage():
ui.separator()
- with ui.grid(columns='1fr auto 1fr').classes('w-full justify-center'):
+ with ui.tabs().classes('w-full items-center') as tabs:
+
+ overviews = ui.tab('Übersichten')
+ absence = ui.tab('Urlaubsantrag')
+
+ with ui.grid(columns='1fr auto 1fr').classes('w-full items-center'):
ui.space()
+ with ui.tab_panels(tabs, value=overviews):
+ with ui.tab_panel(overviews):
- def activate_vacation():
- binder_vacation.value = True
+ def activate_vacation():
+ binder_vacation.value = True
- def activate_absence():
- binder_absence.value = True
+ def activate_absence():
+ binder_absence.value = True
- with ui.grid(columns='1fr 1fr'):
+ with ui.grid(columns='1fr 1fr'):
- ui.markdown("**Monatsübersicht:**").classes('col-span-2')
+ ui.markdown("**Monatsübersicht:**").classes('col-span-2')
- month_year_select = ui.select(list(reversed(available_years)), label="Jahr", on_change=update_month).bind_value_to(binder_available_years, 'value')
- month_month_select = ui.select(available_months, label="Monat", on_change=enable_month)
- month_month_select.disable()
+ month_year_select = ui.select(list(reversed(available_years)), label="Jahr", on_change=update_month).bind_value_to(binder_available_years, 'value')
+ month_month_select = ui.select(available_months, label="Monat", on_change=enable_month)
+ month_month_select.disable()
- ui.space()
- month_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/month/{current_user.username}/{month_year_select.value}-{month_month_select.value}", new_tab=True)).bind_enabled_from(binder_month_button, 'value')
- ui.markdown("**Urlaubsanspruch**").classes('col-span-2')
- vacation_select = ui.select(list(reversed(available_years)), on_change=activate_vacation)
- vacation_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/vacation/{current_user.username}/{vacation_select.value}", new_tab=True)).bind_enabled_from(binder_vacation, 'value')
- ui.markdown("**Fehlzeitenübersicht**").classes('col-span-2')
- absences_select = ui.select(list(reversed(available_years)), on_change=activate_absence)
- absences_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"api/absence/{current_user.username}/{absences_select.value}", new_tab=True)).bind_enabled_from(binder_absence, 'value')
- ui.separator().classes('col-span-2')
+ ui.space()
+ month_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/month/{current_user.username}/{month_year_select.value}-{month_month_select.value}", new_tab=True)).bind_enabled_from(binder_month_button, 'value')
+ ui.markdown("**Urlaubsanspruch**").classes('col-span-2')
+ vacation_select = ui.select(list(reversed(available_years)), on_change=activate_vacation)
+ vacation_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/vacation/{current_user.username}/{vacation_select.value}", new_tab=True)).bind_enabled_from(binder_vacation, 'value')
+ ui.markdown("**Fehlzeitenübersicht**").classes('col-span-2')
+ absences_select = ui.select(list(reversed(available_years)), on_change=activate_absence)
+ absences_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"api/absence/{current_user.username}/{absences_select.value}", new_tab=True)).bind_enabled_from(binder_absence, 'value')
+ ui.separator().classes('col-span-2')
- def logout():
- app.storage.user.pop("active_user", None)
- ui.navigate.to("/")
+ def logout():
+ app.storage.user.pop("active_user", None)
+ ui.navigate.to("/")
- ui.button("Logout", on_click=logout).classes('col-span-2')
+ ui.button("Logout", on_click=logout).classes('col-span-2')
+ with ui.tab_panel(absence):
+ ui.label("Urlaub für folgenden Zeitraum beantragen:")
+ vacation_date = ui.date().props('range today-btn')
+ def vacation_submission():
+ if vacation_date.value == None:
+ return None
+ try:
+ current_user.vacation_application(vacation_date.value["from"], vacation_date.value["to"])
+ except TypeError:
+ current_user.vacation_application(vacation_date.value, vacation_date.value)
+ vacation_date.value = ""
+ with ui.dialog() as dialog, ui.card():
+ ui.label("Urlaubsantrag wurde abgeschickt")
+ ui.button("OK", on_click=dialog.close)
+ open_vacation_applications.refresh()
+ dialog.open()
+
+ ui.button("Einreichen", on_click=vacation_submission).classes('w-full items-center').tooltip("Hiermit reichen Sie einen Urlaubsantrag für den oben markierten Zeitraum ein.")
+
+ @ui.refreshable
+ def open_vacation_applications():
+ open_applications = current_user.get_open_vacation_applications()
+ if len(list(open_applications)) > 0:
+ ui.separator()
+ ui.label("Offene Urlaubsanträge:").classes('font-bold')
+ va_columns = [ {'label': 'Index', 'name': 'index', 'field': 'index', 'classes': 'hidden', 'headerClasses': 'hidden'},
+ {'label': 'Start', 'name': 'start', 'field': 'start'},
+ {'label': 'Ende', 'name': 'end', 'field': 'end'}]
+ va_rows = [ ]
+ date_string = '%d.%m.%Y'
+ for i, dates in open_applications.items():
+ startdate_dt = datetime.datetime.strptime(dates[0], '%Y-%m-%d')
+ enddate_dt = datetime.datetime.strptime(dates[1], '%Y-%m-%d')
+ va_rows.append({'index': i, 'start': startdate_dt.strftime(date_string), 'end': enddate_dt.strftime(date_string)})
+
+ va_table = ui.table(columns=va_columns, rows=va_rows, selection="single", row_key="index").classes('w-full')
+ def retract_va():
+ try:
+ retract_result = current_user.revoke_vacation_application(va_table.selected[0]["index"])
+ open_vacation_applications.refresh()
+ if retract_result == 0:
+ ui.notify("Urlaubsantrag zurückgezogen")
+ except IndexError:
+ ui.notify("Kein Urlaubsanstrag ausgewählt")
+ ui.button("Zurückziehen", on_click=retract_va).tooltip("Hiermit wird der oben gewählte Urlaubsantrag zurückgezogen.").classes('w-full')
+
+ open_vacation_applications()
ui.space()
else:
diff --git a/lib/users.py b/lib/users.py
index a8a0f98..3d37674 100644
--- a/lib/users.py
+++ b/lib/users.py
@@ -5,13 +5,17 @@ import hashlib
import os
from calendar import monthrange
from stat import S_IREAD, S_IWUSR
+from nicegui import ui
+
import datetime
import time
import json
import shutil
import re
-from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
+from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, \
+ standard_adminsettings, standard_usersettings, va_file
+
# Benutzerklasse
@@ -59,7 +63,6 @@ class user:
with open(filename, 'a') as file:
# Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
file.write(f"{timestamp}\n")
- file.close()
except FileNotFoundError:
# Fehlende Verzeichnisse anlegen
folder_path = os.path.dirname(filename)
@@ -261,7 +264,7 @@ class user:
filename_txt = os.path.join(self.userfolder, f"{year}-{month}.txt")
os.chmod(filename_txt, S_IREAD)
- def get_last_months_overtime(self, year, month):
+ def get_last_months_overtime(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month):
try:
if int(month) == 1:
year = str(int(year) - 1)
@@ -385,7 +388,7 @@ class user:
hours_to_work = -1
return hours_to_work
- def get_vacation_claim(self, year, month, day):
+ def get_vacation_claim(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month, day=datetime.datetime.now().day):
workhour_entries = list(self.workhours)
workhour_entries.sort()
day_to_check = datetime.datetime(int(year), int(month), int(day))
@@ -403,18 +406,18 @@ class user:
return int(claim)
- def count_vacation_days(self, year):
- vacation_used = 0
+ def count_absence_days(self, absence_code: str, year=datetime.datetime.now().year):
+ absence_days = 0
for month in range(0, 13):
try:
absence_dict = self.get_absence(year, month)
for entry, absence_type in absence_dict.items():
- if absence_type == "U":
- vacation_used += 1
+ if absence_type == absence_code:
+ absence_days += 1
except:
pass
- return vacation_used
+ return absence_days
def delete_photo(self):
os.remove(self.photofile)
@@ -451,6 +454,44 @@ class user:
return [total_time, in_time_stamp]
+ def vacation_application(self, startdate, enddate):
+ application_file = os.path.join(self.userfolder, va_file)
+ try:
+ with open(application_file, 'r') as json_file:
+ applications = json.load(json_file)
+ except FileNotFoundError:
+ applications = { }
+ applications[str(len(list(applications)))] = (startdate, enddate)
+ with open(application_file, 'w') as json_file:
+ json_file.write(json.dumps(applications, indent=4))
+
+ def get_open_vacation_applications(self):
+ application_file = os.path.join(self.userfolder, va_file)
+ try:
+ with open(application_file, 'r') as json_file:
+ applications = json.load(json_file)
+ except FileNotFoundError:
+ applications = { }
+ return applications
+
+ def revoke_vacation_application(self, index):
+ application_file = os.path.join(self.userfolder, va_file)
+ with open(application_file, 'r') as json_file:
+ applications = json.load(json_file)
+ try:
+ del(applications[index])
+ new_applications = { }
+ new_index = 0
+ for index, dates in applications.items():
+ new_applications[new_index] = dates
+ new_index += 1
+ with open(application_file, 'w') as json_file:
+ json_file.write(json.dumps(new_applications, indent=4))
+ return 0
+ except KeyError:
+ ui.notify("Urlaubsantrag wurde schon bearbeitet")
+ return -1
+
# Benutzer auflisten
def list_users():
diff --git a/settings.json b/settings.json
index a900a48..084b85c 100644
--- a/settings.json
+++ b/settings.json
@@ -71,6 +71,7 @@
"2030-10-03": "Tag der deutschen Einheit",
"2030-10-30": "Reformationstag",
"2030-12-25": "1. Weihnachtsfeiertag",
- "2030-12-26": "2. Weihnachtsfeiertag"
+ "2030-12-26": "2. Weihnachtsfeiertag",
+ "2025-06-11": "Testeintrag"
}
}
\ No newline at end of file
diff --git a/users/testuser1/vacation_application.json b/users/testuser1/vacation_application.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/users/testuser1/vacation_application.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/users/testuser10/settings.json b/users/testuser10/settings.json
index 4630aa5..9734a44 100644
--- a/users/testuser10/settings.json
+++ b/users/testuser10/settings.json
@@ -1,8 +1,7 @@
{
"username": "testuser10",
"fullname": "Diego Dieci",
- "password": "123456789",
- "api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f",
+ "password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"workhours": {
"2024-04-01": {
"1": "1",
@@ -14,5 +13,6 @@
"7": "7",
"vacation": "30"
}
- }
+ },
+ "api_key": "807518cd5bd85c1e4855d340f9b77b23eac21b7f"
}
\ No newline at end of file
diff --git a/users/testuser10/vacation_application.json b/users/testuser10/vacation_application.json
index 91923e2..45adac7 100644
--- a/users/testuser10/vacation_application.json
+++ b/users/testuser10/vacation_application.json
@@ -1,14 +1,6 @@
{
"0": [
- "2025-05-05",
- "2025-05-05"
- ],
- "1": [
- "2025-05-06",
- "2025-05-14"
- ],
- "2": [
- "2025-05-19",
- "2025-05-22"
+ "2025-06-09",
+ "2025-06-19"
]
}
\ No newline at end of file
diff --git a/users/testuser2/settings.json b/users/testuser2/settings.json
new file mode 100644
index 0000000..f852870
--- /dev/null
+++ b/users/testuser2/settings.json
@@ -0,0 +1,18 @@
+{
+ "username": "testuser2",
+ "fullname": "testuser2",
+ "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
+ "api_key": "84799b1cbb92514f047bc2186cb4b4aafb352d69",
+ "workhours": {
+ "2025-05-27": {
+ "1": 0,
+ "2": 0,
+ "3": 0,
+ "4": 0,
+ "5": 0,
+ "6": 0,
+ "7": 0,
+ "vacation": 0
+ }
+ }
+}
\ No newline at end of file