zeiterfassung/lib/homepage.py

301 lines
15 KiB
Python

# 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(1.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
print(f"Last selection from save: {last_selection}")
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"])
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'):
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()
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("/")
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:
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)