# Zeiterfassung import datetime from nicegui import ui, app, Client from nicegui.page import page from lib.users import * from lib.definitions import * from calendar import monthrange, month_name import hashlib import calendar import locale from lib.web_ui import * @ui.page('/') def homepage(): ui.page_title(f'{app_title} {app_version}') if login_is_valid(): try: current_user = user(app.storage.user["active_user"]) except: del(app.storage.user["active_user"]) ui.navigate.reload() pageheader(f"Willkommen, {current_user.fullname}") today = datetime.datetime.now() def yesterdays_overtime(): last_months_overtime = current_user.get_last_months_overtime(today.year, today.month) overtime_this_month = 0 for i in range(1, today.day): overtime_this_month += (int(current_user.get_worked_time(today.year, today.month, i)[0]) - int(current_user.get_day_workhours(today.year, today.month, i))) return last_months_overtime + overtime_this_month @ui.refreshable def stamp_interface(): time_so_far = current_user.get_worked_time(today.year, today.month, today.day)[0] def stamp_and_refresh(): current_user.timestamp() stamp_interface.refresh() with ui.grid(columns='20% auto 20%').classes('w-full justify-center'): ui.space() def update_timer(): additional_time = 0 if time_toggle.value: additional_time = yesterdays_overtime() time_toggle.set_text("Gesamtzeit") if not time_toggle.value: time_toggle.set_text("Tageszeit") if current_user.get_worked_time(today.year, today.month, today.day)[1] > 0: time_in_total = additional_time + time_so_far + int((datetime.datetime.now().timestamp() - current_user.get_worked_time(today.year, today.month, today.day)[1])) else: time_in_total = additional_time + time_so_far working_hours.set_content(convert_seconds_to_hours(time_in_total)) with ui.grid(columns='1fr 1fr'): if current_user.stamp_status() == status_in: bg_color = 'green' else: bg_color = 'red' working_hours = ui.markdown(convert_seconds_to_hours(time_so_far)).classes(f'col-span-2 rounded-3xl text-center text-white text-bold text-2xl border-4 border-gray-600 bg-{bg_color}') in_button = ui.button("Einstempeln", on_click=stamp_and_refresh).classes('bg-green') out_button = ui.button("Ausstempeln", on_click=stamp_and_refresh).classes('bg-red') time_toggle = ui.switch("Tageszeit",on_change=update_timer).classes('w-full justify-center col-span-2 normal-case') #time_toggle = ui.toggle({"day": "Tagesarbeitszeit", "total": "Gesamtzeit"}, value="day", # on_change=update_timer).classes('w-full justify-center col-span-2 normal-case').tooltip("Hier lässt sich die Anzeige oben zwischen heute geleisteter Arbeitszeit und summierter Arbeitszeit umschalten.") working_timer = ui.timer(30.0, update_timer) working_timer.active = False if current_user.stamp_status() == status_in: in_button.set_enabled(False) out_button.set_enabled(True) working_timer.active = True else: in_button.set_enabled(True) out_button.set_enabled(False) working_timer.active = False stamp_interface() available_years = current_user.get_years() available_months = [ ] binder_month_button = ValueBinder() binder_month_button.value = False binder_available_years = ValueBinder() binder_vacation = ValueBinder() binder_vacation.value = False binder_absence = ValueBinder() binder_absence.value = False def enable_month(): binder_month_button.value = True def update_month(): month_dict = { } for i in current_user.get_months(month_year_select.value): month_dict[i] = month_name[i] month_month_select.set_options(month_dict) month_month_select.enable() if load_adminsettings()["user_notes"]: with ui.grid(columns='1fr auto 1fr').classes('w-full justify-center'): ui.space() with ui.expansion("Tagesnotizen", icon='o_description'): with ui.grid(columns=2): last_selection = 0 @ui.refreshable def day_note_ui(): day_notes = { } options = { } options[0] = "Heute" for i in range(1, monthrange(today.year, today.month)[1] + 1): notes_of_i = current_user.get_day_notes(today.year, today.month, i) if len(notes_of_i) > 0: try: day_notes[i] = notes_of_i["user"] options[i] = f"{i}.{today.month}.{today.year}" except KeyError: pass select_value = last_selection try: day_notes[today.day] del(options[0]) select_value = today.day except KeyError: select_value = 0 day_selector = ui.select(options=options, value=select_value).classes('col-span-2') #except ValueError: # day_selector = ui.select(options=options, value=0).classes('col-span-2') daynote = ui.textarea().classes('col-span-2') try: if last_selection == 0: daynote.value = current_user.get_day_notes(today.year, today.month, today.day)["user"] else: daynote.value = day_notes[day_selector.value] except: daynote.value = "" def call_note(): if day_selector.value == 0: daynote.value = current_user.get_day_notes(today.year, today.month, today.day)["user"] else: daynote.value = day_notes[day_selector.value] day_selector.on_value_change(call_note) def save_note(): note_dict = { } note_dict["user"] = daynote.value nonlocal last_selection last_selection = day_selector.value if day_selector.value == 0: day_to_write = today.day else: day_to_write = day_selector.value current_user.write_notes(today.year, today.month, day_to_write, note_dict) day_note_ui.refresh() save_button = ui.button("Speichern", on_click=save_note) def del_text(): daynote.value = "" delete_button = ui.button("Löschen", on_click=del_text) notes = current_user.get_day_notes(today.year, today.month, today.day) try: daynote.value = notes[current_user.username] except: pass day_note_ui() ui.separator() with ui.tabs().classes('w-full items-center') as tabs: overviews = ui.tab('Übersichten') absence = ui.tab('Urlaubsantrag') absence.set_visibility(load_adminsettings()["vacation_application"]) pw_change = ui.tab("Passwort") 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_absence(): binder_absence.value = True with ui.grid(columns='1fr 1fr').classes('items-end'): ui.label("Monatsübersicht:").classes('col-span-2 font-bold') 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.label("Urlaubsanspruch").classes('col-span-2 font-bold') 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.label("Fehlzeitenübersicht").classes('col-span-2 font-bold') 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') 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() with ui.tab_panel(pw_change): ui.label("Passwort ändern").classes('font-bold') with ui.grid(columns='auto auto').classes('items-end'): ui.label("Altes Passwort:") old_pw_input = ui.input(password=True) ui.label("Neues Passwort:") new_pw_input = ui.input(password=True) ui.label("Neues Passwort bestätigen:") new_pw_confirm_input = ui.input(password=True) def revert_pw_inputs(): old_pw_input.value = "" new_pw_input.value = "" new_pw_confirm_input.value = "" def save_new_password(): if hash_password(old_pw_input.value) == current_user.password: if new_pw_input.value == new_pw_confirm_input.value: current_user.password = hash_password(new_pw_input.value) current_user.write_settings() ui.notify("Neues Passwort gespeichert") else: ui.notify("Passwortbestätigung stimmt nicht überein") else: ui.notify("Altes Passwort nicht korrekt") ui.button("Speichern", on_click=save_new_password) ui.button("Zurücksetzen", on_click=revert_pw_inputs) ui.space() ui.space() with ui.column(): ui.separator() def logout(): app.storage.user.pop("active_user", None) ui.navigate.to("/") ui.button("Logout", on_click=logout).classes('w-full') ui.space() else: login_mask() # 404 Fehlerseite @app.exception_handler(404) async def exception_handler_404(request, exception: Exception): with Client(page(''), request=request) as client: pageheader("Fehler 404") ui.label("Diese Seite existiert nicht.") ui.label("Was möchten Sie tun?") with ui.list().props('dense'): with ui.item(): ui.link("zur Startseite", "/") with ui.item(): ui.link("zum Administratrionsbereich", "/admin") return client.build_response(request, 404)