290 lines
14 KiB
Python
290 lines
14 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 == "total":
|
|
additional_time = yesterdays_overtime()
|
|
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.toggle({"day": "Tagesarbeitszeit", "total": "Gesamtzeit"}, value="day",
|
|
on_change=update_timer).classes('w-full justify-center col-span-2').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')
|
|
|
|
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():
|
|
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:
|
|
current_user.revoke_vacation_application(va_table.selected[0]["index"])
|
|
open_vacation_applications.refresh()
|
|
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) |