From 2b9bad6ad3f0e26e2fd8d23b9b91b6f379f5ae11 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Sun, 27 Apr 2025 23:07:26 +0200 Subject: [PATCH 01/12] Beginn API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beginn Übersichtsseite für Benutzer --- admin.py | 17 ++++++--- api.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 4 +- playgound.py | 15 +++++++- 4 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 api.py diff --git a/admin.py b/admin.py index 86d97d6..aa0c66f 100644 --- a/admin.py +++ b/admin.py @@ -106,7 +106,6 @@ def page_admin(): 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 @@ -180,7 +179,7 @@ Dies kann nicht rückgägig gemacht werden!''') def edit_entry(t_stamp, day): with ui.dialog() as edit_dialog, ui.card(): - ui.markdown("###Eintrag bearbeiten") + ui.markdown("**Eintrag bearbeiten**") timestamp = datetime.datetime.fromtimestamp(int(t_stamp)) input_time = ui.time().props('format24h now-btn').classes('w-full justify-center') @@ -338,9 +337,15 @@ Dies kann nicht rückgägig gemacht werden!''') add_dialog.move(calendar_card) def add_absence(absence_type, day): - with ui.dialog() as dialog, ui.card(): - ui.markdown(f'Für welchen Zeitraum soll *{absence_entries[absence_type]["name"]}* eingetragen werden?') - absence_dates = ui.date().props('range') + 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: @@ -398,7 +403,7 @@ Dies kann nicht rückgägig gemacht werden!''') dialog.close() - with ui.grid(columns=3): + 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) diff --git a/api.py b/api.py new file mode 100644 index 0000000..7bae042 --- /dev/null +++ b/api.py @@ -0,0 +1,102 @@ +import sys +from logging import exception + +from nicegui import * + +from definitions import * +from web_ui import * +from users import * +from datetime import datetime + +import calendar + + +# Überblicksseite zum Ausdrucken oder als PDF speichern +@ui.page('/api/overview/month/{username}-{year}-{month}') +def page(username: str, year: int, month: int): + + #try: + current_user = user(username) + ui.page_title(f"Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}") + ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5') + ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}') + + columns = [ + {'name': 'date', 'label': 'Datum', 'field': 'date', 'required': True, 'align': 'left'}, + {'name:': 'bookings', 'label': 'Buchungen', 'field': 'bookings', 'align': 'left'}, + {'name:': 'is_time', 'label': 'Ist', 'field': 'is_time', 'align': 'left'}, + {'name:': 'target_time', 'label': 'Soll', 'field': 'target_time', 'align': 'left'}, + {'name:': 'total', 'label': 'Saldo', 'field': 'total', 'align': 'left'} + ] + + rows = [ ] + + # Timestamp in ein Array schreiben + timestamps = current_user.get_timestamps(year, month) + timestamps.sort() + + # Abwesenheitsdaten in ein Dict schreiben + user_absent = current_user.get_absence(year, month) + + # Dictionary für sortierte Timestamps + timestamps_dict = { } + + # Dictionary mit zunächst leeren Tageinträgen befüllen + for day in range(1, monthrange(year, month)[1] + 1): + # Jeder Tag bekommt eine leere Liste + timestamps_dict[day] = [ ] + + # Timestamps den Monatstagen zuordnen + for stamp in timestamps: + day_of_month_of_timestamp = datetime.fromtimestamp(int(stamp)).day + timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) + timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) + + general_saldo = 0 + + # Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch + for day in list(timestamps_dict): + booking_text = "" + current_day_date = f"{day}.{month}.{year}" + + # Abwesenheitseinträge + try: + # Abwesenheitszeiten behandeln + for i in list(user_absent): + if int(i) == day: + booking_text += absence_entries[user_absent[i]]["name"] + " " + + # Buchungen behandeln + for i in range(len(timestamps_dict[day])): + try: + temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]] + booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + "-" + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "\n" + + except: + if len(timestamps_dict[day]) % 2 != 0: + booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + + except Exception as e: + print(e) + pass + + rows.append({'date': current_day_date, 'bookings': booking_text, 'is_time': 0, 'target_time': 0, 'total': 0}) + + + + + #rows = [ + # {'date': '1.1.2024', 'bookings': '12345', 'is_time': '12345', 'target_time': '98765', 'total': 123} + #] + + overview_table = ui.table(columns=columns, rows=rows).classes('w-120') + + + overview_table.add_slot('body-cell', r''' + {{ props.value }} + ''') + + #except Exception as e: +# print(e) +# ui.markdown('#Fehler') +# ui.markdown('Benutzer existiert nicht') \ No newline at end of file diff --git a/main.py b/main.py index d2d56cb..d2201b0 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,8 @@ from login import * from users import * from touchscreen import * from definitions import * +from api import * + import json @@ -19,7 +21,7 @@ def main(): port = int(data["port"]) secret = data["secret"] - ui.run(port=port, storage_secret=secret) + ui.run(port=port, storage_secret=secret, language='de-DE') if __name__ in ("__main__", "__mp_main__"): main() diff --git a/playgound.py b/playgound.py index 3e1af86..092d37e 100644 --- a/playgound.py +++ b/playgound.py @@ -1,5 +1,16 @@ from nicegui import ui -ui.time().props('now-btn format24h') +columns=[ + {'name': 'name', 'label': 'Name', 'field': 'name', 'align': 'left', 'style': 'text-wrap: wrap'}, + {'name': 'age', 'label': 'Age', 'field': 'age'}, + ] +rows=[ + {'name': 'Alice', 'age': 18}, + {'name': 'Bob', 'age': 21}, + {'name': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc ultricies'}, + ] -ui.run(port=9000) \ No newline at end of file +ui.table(columns=columns, rows=rows).classes('w-80') + + +ui.run(language="de-DE", port=9000) \ No newline at end of file From df81a05a7f660cec8893561ae6fce7c501ced6b3 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Mon, 28 Apr 2025 09:04:33 +0200 Subject: [PATCH 02/12] =?UTF-8?q?API=20Tabelle=20vervollst=C3=A4ndigt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++--------- users.py | 2 ++ 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/api.py b/api.py index 7bae042..c073871 100644 --- a/api.py +++ b/api.py @@ -57,7 +57,7 @@ def page(username: str, year: int, month: int): # Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch for day in list(timestamps_dict): booking_text = "" - current_day_date = f"{day}.{month}.{year}" + current_day_date = f"{datetime(year, month, day).strftime('%a')}, {day}.{month}.{year}" # Abwesenheitseinträge try: @@ -65,22 +65,87 @@ def page(username: str, year: int, month: int): for i in list(user_absent): if int(i) == day: booking_text += absence_entries[user_absent[i]]["name"] + " " - - # Buchungen behandeln - for i in range(len(timestamps_dict[day])): - try: - temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]] - booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + "-" + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "\n" - - except: - if len(timestamps_dict[day]) % 2 != 0: - booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') - except Exception as e: print(e) pass - rows.append({'date': current_day_date, 'bookings': booking_text, 'is_time': 0, 'target_time': 0, 'total': 0}) + # Buchungen behandeln + for i in range(len(timestamps_dict[day])): + try: + temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]] + booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + "-" + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "\n" + + except: + if len(timestamps_dict[day]) % 2 != 0: + booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + + # Ist-Zeiten berechnen + timestamps_of_this_day = [] + + # Suche mir alle timestamps für diesen Tag + for i in timestamps: + actual_timestamp = datetime.fromtimestamp(int(i)) + timestamp_day = actual_timestamp.strftime('%-d') + + 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 + + is_time = convert_seconds_to_hours(time_sum) + " h" + else: + is_time = "Kein" + + # Sollzeit bestimmen + + hours_to_work = int(current_user.get_day_workhours(year, month, day)) + + if hours_to_work < 0: + target_time = "" + else: + target_time = f"{convert_seconds_to_hours(int(hours_to_work) * 3600)} h" + if int(hours_to_work) == 0: + booking_text = "Kein Arbeitstag" + + # Saldo für den Tag berechnen + day_in_list = datetime(year, month, day) + if time.time() > day_in_list.timestamp(): + + time_duty = int(current_user.get_day_workhours(year, month, day)) * 3600 + if time_duty < 0: + saldo = 0 + total = "" + 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 + total = f"{convert_seconds_to_hours(saldo)} h" + else: + total = "-" + + rows.append({'date': current_day_date, 'bookings': booking_text, 'is_time': is_time, 'target_time': target_time, 'total': total}) @@ -90,8 +155,7 @@ def page(username: str, year: int, month: int): #] overview_table = ui.table(columns=columns, rows=rows).classes('w-120') - - + # Zeilenumbruch umsetzen overview_table.add_slot('body-cell', r''' {{ props.value }} ''') diff --git a/users.py b/users.py index 73b507e..70e549c 100644 --- a/users.py +++ b/users.py @@ -276,8 +276,10 @@ class user: day_to_check = datetime.datetime(int(year), int(month), int(day)) for entry in reversed(workhour_entries): + entry_split = entry.split("-") entry_dt = datetime.datetime(int(entry_split[0]), int(entry_split[1]), int(entry_split[2])) + if entry_dt <= day_to_check: weekday = day_to_check.strftime("%w") if int(weekday) == 0: From 005d5be68630ce7f9a55851bb39acee8af9f2c7e Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Mon, 28 Apr 2025 14:04:18 +0200 Subject: [PATCH 03/12] =?UTF-8?q?Summenabschnitt=20API=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zeitsollbestimmung hat noch Fehler --- api.py | 38 ++++++++++++++++++++++------------- users/testuser1/settings.json | 20 +++++++++--------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/api.py b/api.py index c073871..66610f2 100644 --- a/api.py +++ b/api.py @@ -15,7 +15,7 @@ import calendar @ui.page('/api/overview/month/{username}-{year}-{month}') def page(username: str, year: int, month: int): - #try: + try: current_user = user(username) ui.page_title(f"Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}") ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5') @@ -65,8 +65,7 @@ def page(username: str, year: int, month: int): for i in list(user_absent): if int(i) == day: booking_text += absence_entries[user_absent[i]]["name"] + " " - except Exception as e: - print(e) + except: pass # Buchungen behandeln @@ -130,6 +129,7 @@ def page(username: str, year: int, month: int): if time_duty < 0: saldo = 0 total = "" + booking_text = "Kein Arbeitsverhältnis" else: saldo = int(time_sum) - int(time_duty) # Nach Abwesenheitseinträgen suchen @@ -147,20 +147,30 @@ def page(username: str, year: int, month: int): rows.append({'date': current_day_date, 'bookings': booking_text, 'is_time': is_time, 'target_time': target_time, 'total': total}) - - - - #rows = [ - # {'date': '1.1.2024', 'bookings': '12345', 'is_time': '12345', 'target_time': '98765', 'total': 123} - #] - overview_table = ui.table(columns=columns, rows=rows).classes('w-120') # Zeilenumbruch umsetzen overview_table.add_slot('body-cell', r''' {{ props.value }} ''') - #except Exception as e: -# print(e) -# ui.markdown('#Fehler') -# ui.markdown('Benutzer existiert nicht') \ No newline at end of file + # Überstundenzusammenfassung + + with ui.grid(columns=2).classes('w-full gap-0'): + ui.markdown("Überstunden aus Vormonat:") + last_months_overtime = current_user.get_last_months_overtime(year, month) + ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)} h") + ui.markdown("Überstunden diesen Monat:") + ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h") + ui.markdown("**Überstunden Gesamt:**") + overtime_overall = last_months_overtime + general_saldo + ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**") + + except Exception as e: + print(str(type(e).__name__) + " " + str(e)) + if type(e) == UnboundLocalError: + ui.markdown('#Fehler') + ui.markdown('Benutzer existiert nicht') + else: + ui.markdown('#Fehler') + ui.markdown(str(type(e))) + ui.markdown(str(e)) \ No newline at end of file diff --git a/users/testuser1/settings.json b/users/testuser1/settings.json index 83a6dee..e0141a6 100644 --- a/users/testuser1/settings.json +++ b/users/testuser1/settings.json @@ -3,16 +3,6 @@ "fullname": "Pia Paulina", "password": "123456789", "workhours": { - "2025-04-03": { - "1": "4", - "2": "8", - "3": "8", - "4": "8", - "5": "8", - "6": "0", - "7": "0", - "vacation": "35" - }, "2025-05-13": { "1": "0", "2": "0", @@ -32,6 +22,16 @@ "6": "6", "7": "7", "vacation": "0" + }, + "2025-03-01": { + "1": "4", + "2": "8", + "3": "8", + "4": "8", + "5": "8", + "6": 0, + "7": 0, + "vacation": "30" } } } \ No newline at end of file From 4c7e8dfadcea2af67472ddf145d627bd3e3b7d55 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Mon, 28 Apr 2025 16:39:08 +0200 Subject: [PATCH 04/12] =?UTF-8?q?Fehlerbehebung=20Sollstunden,=20=C3=9Cber?= =?UTF-8?q?sicht=20Fehltage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin.py | 2 ++ api.py | 37 ++++++++++++++++++++++++++++++++++--- users.py | 6 ++---- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/admin.py b/admin.py index aa0c66f..82a21f8 100644 --- a/admin.py +++ b/admin.py @@ -243,6 +243,7 @@ Dies kann nicht rückgägig gemacht werden!''') 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)) # Arbeitszeit Ist bestimmen + timestamps_of_this_day = [] # Suche mir alle timestamps für diesen Tag @@ -254,6 +255,7 @@ Dies kann nicht rückgägig gemacht werden!''') timestamps_of_this_day.append(i) timestamps_of_this_day.sort() + time_sum = 0 if len(timestamps_of_this_day) > 1: diff --git a/api.py b/api.py index 66610f2..88debe9 100644 --- a/api.py +++ b/api.py @@ -12,13 +12,14 @@ import calendar # Überblicksseite zum Ausdrucken oder als PDF speichern -@ui.page('/api/overview/month/{username}-{year}-{month}') +@ui.page('/api/overview/month/{username}/{year}-{month}') def page(username: str, year: int, month: int): try: current_user = user(username) ui.page_title(f"Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}") ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5') + ui.space() ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}') columns = [ @@ -147,12 +148,12 @@ def page(username: str, year: int, month: int): rows.append({'date': current_day_date, 'bookings': booking_text, 'is_time': is_time, 'target_time': target_time, 'total': total}) - overview_table = ui.table(columns=columns, rows=rows).classes('w-120') + overview_table = ui.table(columns=columns, rows=rows, row_key='date').classes('w-full') + # Zeilenumbruch umsetzen overview_table.add_slot('body-cell', r''' {{ props.value }} ''') - # Überstundenzusammenfassung with ui.grid(columns=2).classes('w-full gap-0'): @@ -165,6 +166,36 @@ def page(username: str, year: int, month: int): overtime_overall = last_months_overtime + general_saldo ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**") + absences_this_month = current_user.get_absence(year, month) + absence_dict = { } + + for abbr in list(absence_entries): + absence_dict[abbr] = 0 + + for key, value in absences_this_month.items(): + if value in list(absence_dict): + absence_dict[value] += 1 + + total_absence_days = 0 + for key, value in absence_dict.items(): + total_absence_days += absence_dict[key] + + if total_absence_days > 0: + ui.markdown("###Abwesenheitstage diesen Monat:") + a_columns = [ + {'name': 'type', 'label': 'Typ', 'field': 'type', 'required': True, 'align': 'left'}, + {'name': 'sum', 'label': 'Tage', 'field': 'sum', 'required': True, 'align': 'left'}, + ] + + a_row = [ ] + + for key,value in absence_dict.items(): + if value > 0: + a_row.append({'type': absence_entries[key]['name'], 'sum': value}) + + absence_table = ui.table(columns=a_columns, rows=a_row) + + except Exception as e: print(str(type(e).__name__) + " " + str(e)) if type(e) == UnboundLocalError: diff --git a/users.py b/users.py index 70e549c..8dae9f2 100644 --- a/users.py +++ b/users.py @@ -156,7 +156,6 @@ class user: years.append(year) years.sort() - print(years) return years def get_months(self, year): @@ -183,7 +182,7 @@ class user: elif int(year) < year_now: for i in range(1, 13): available_months.append(i) - print(available_months) + for file in os.listdir(self.userfolder): if re.match(r"\d{4}-\d{1,2}\.json", file): @@ -254,8 +253,6 @@ class user: except: json_data.update({ "absence": { str(int(day)): absence_type}}) json_dict = json.dumps(json_data, indent=4) - print(json_dict) - print(f"{self.userfolder}/{year}-{month}.json") with open(f"{self.userfolder}/{int(year)}-{int(month)}.json", "w") as json_file: json_file.write(json_dict) @@ -285,6 +282,7 @@ class user: if int(weekday) == 0: weekday = str(7) hours_to_work = self.workhours[entry][weekday] + break else: # Wenn vor Einstellungsdatum -1 ausgeben hours_to_work = -1 From 8e8109177be5f11862feba5a9e09bb382ec99e5e Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Tue, 29 Apr 2025 10:15:42 +0200 Subject: [PATCH 05/12] API auf Markdown umgestellt Erweiterungen der grafischen Eigenschaften auch im Admin Bereich --- admin.py | 25 ++- api.py | 305 +++++++++++++++++++----------------- definitions.py | 19 ++- playgound.py | 14 +- users.py | 18 +++ users/testuser1/2025-4.json | 8 +- 6 files changed, 211 insertions(+), 178 deletions(-) diff --git a/admin.py b/admin.py index 82a21f8..87c9b17 100644 --- a/admin.py +++ b/admin.py @@ -272,7 +272,7 @@ Dies kann nicht rückgägig gemacht werden!''') timestamps_of_this_day[i]) time_sum = time_sum + time_delta - ui.markdown(convert_seconds_to_hours(time_sum)) + ui.markdown(convert_seconds_to_hours(time_sum)).classes('text-right') else: ui.markdown("Kein") @@ -282,7 +282,7 @@ Dies kann nicht rückgägig gemacht werden!''') if hours_to_work < 0: ui.space() else: - ui.markdown(f"{convert_seconds_to_hours(int(hours_to_work) * 3600)}") + 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) @@ -307,9 +307,9 @@ Dies kann nicht rückgägig gemacht werden!''') pass general_saldo = general_saldo + saldo - ui.markdown(convert_seconds_to_hours(saldo)) + ui.markdown(convert_seconds_to_hours(saldo)).classes('text-right') else: - ui.markdown("-") + ui.markdown("-").classes('text-center') def add_entry(day): with ui.dialog() as add_dialog, ui.card(): @@ -425,18 +425,13 @@ Dies kann nicht rückgägig gemacht werden!''') #ui.button("Eintrag hinzufügen", on_click=lambda day=day: add_entry(day)) #4x leer und dann Gesamtsaldo - for i in range(4): - ui.space() - ui.markdown(f"{convert_seconds_to_hours(general_saldo)}") - for i in range(4): - ui.space() - ui.markdown("Stunden aus Vormonat") + ui.space().classes('col-span-4') + ui.markdown(f"{convert_seconds_to_hours(general_saldo)}").classes('text-right') + ui.markdown("Stunden aus Vormonat").classes('col-span-4 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)}") - for i in range(4): - ui.space() - ui.markdown("Gesamtsaldo") - ui.markdown(f"**{convert_seconds_to_hours(general_saldo + last_months_overtime)}**") + ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)}").classes('text-right') + ui.markdown("Gesamtsaldo").classes('col-span-4 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() diff --git a/api.py b/api.py index 88debe9..706ceb1 100644 --- a/api.py +++ b/api.py @@ -2,7 +2,9 @@ import sys from logging import exception from nicegui import * +from samba.graph import pad_char +import ui from definitions import * from web_ui import * from users import * @@ -22,179 +24,196 @@ def page(username: str, year: int, month: int): ui.space() ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}') - columns = [ - {'name': 'date', 'label': 'Datum', 'field': 'date', 'required': True, 'align': 'left'}, - {'name:': 'bookings', 'label': 'Buchungen', 'field': 'bookings', 'align': 'left'}, - {'name:': 'is_time', 'label': 'Ist', 'field': 'is_time', 'align': 'left'}, - {'name:': 'target_time', 'label': 'Soll', 'field': 'target_time', 'align': 'left'}, - {'name:': 'total', 'label': 'Saldo', 'field': 'total', 'align': 'left'} - ] + pad_x = 4 + pad_y = 0 - rows = [ ] + color_weekend = "gray-100" + color_holiday = "gray-100" - # Timestamp in ein Array schreiben - timestamps = current_user.get_timestamps(year, month) - timestamps.sort() + def overview_table(): + # Timestamp in ein Array schreiben + timestamps = current_user.get_timestamps(year, month) + timestamps.sort() - # Abwesenheitsdaten in ein Dict schreiben - user_absent = current_user.get_absence(year, month) + # Abwesenheitsdaten in ein Dict schreiben + user_absent = current_user.get_absence(year, month) - # Dictionary für sortierte Timestamps - timestamps_dict = { } + # Dictionary für sortierte Timestamps + timestamps_dict = { } - # Dictionary mit zunächst leeren Tageinträgen befüllen - for day in range(1, monthrange(year, month)[1] + 1): - # Jeder Tag bekommt eine leere Liste - timestamps_dict[day] = [ ] + # Dictionary mit zunächst leeren Tageinträgen befüllen + for day in range(1, monthrange(year, month)[1] + 1): + # Jeder Tag bekommt eine leere Liste + timestamps_dict[day] = [ ] - # Timestamps den Monatstagen zuordnen - for stamp in timestamps: - day_of_month_of_timestamp = datetime.fromtimestamp(int(stamp)).day - timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) - timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) + # Timestamps den Monatstagen zuordnen + for stamp in timestamps: + day_of_month_of_timestamp = datetime.fromtimestamp(int(stamp)).day + timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) + timestamps_dict[day_of_month_of_timestamp].append(int(stamp)) - general_saldo = 0 + general_saldo = 0 - # Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch - for day in list(timestamps_dict): - booking_text = "" - current_day_date = f"{datetime(year, month, day).strftime('%a')}, {day}.{month}.{year}" + with ui.grid(columns='auto auto 1fr 1fr 1fr').classes(f'gap-0 border px-0 py-0'): + ui.markdown("**Datum**").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown("**Buchungen**").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown("**Ist**").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown("**Soll**").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown("**Saldo**").classes(f'border px-{pad_x} py-{pad_y}') - # Abwesenheitseinträge - try: - # Abwesenheitszeiten behandeln - for i in list(user_absent): - if int(i) == day: - booking_text += absence_entries[user_absent[i]]["name"] + " " - except: - pass + # Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch + for day in list(timestamps_dict): + booking_text = "" + color_day = 'inherit' + if datetime(year, month, day).strftime('%w') in ["0", "6"]: + color_day = color_weekend - # Buchungen behandeln - for i in range(len(timestamps_dict[day])): - try: - temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]] - booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + "-" + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "\n" + current_day_date = f"{datetime(year, month, day).strftime('%a')}, {day}.{month}.{year}" + day_text_element = ui.markdown(current_day_date).classes(f'border px-{pad_x} py-{pad_y} bg-{color_day}') - except: - if len(timestamps_dict[day]) % 2 != 0: - booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') - # Ist-Zeiten berechnen - timestamps_of_this_day = [] - - # Suche mir alle timestamps für diesen Tag - for i in timestamps: - actual_timestamp = datetime.fromtimestamp(int(i)) - timestamp_day = actual_timestamp.strftime('%-d') - - 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 - - is_time = convert_seconds_to_hours(time_sum) + " h" - else: - is_time = "Kein" - - # Sollzeit bestimmen - - hours_to_work = int(current_user.get_day_workhours(year, month, day)) - - if hours_to_work < 0: - target_time = "" - else: - target_time = f"{convert_seconds_to_hours(int(hours_to_work) * 3600)} h" - if int(hours_to_work) == 0: - booking_text = "Kein Arbeitstag" - - # Saldo für den Tag berechnen - day_in_list = datetime(year, month, day) - if time.time() > day_in_list.timestamp(): - - time_duty = int(current_user.get_day_workhours(year, month, day)) * 3600 - if time_duty < 0: - saldo = 0 - total = "" - booking_text = "Kein Arbeitsverhältnis" - else: - saldo = int(time_sum) - int(time_duty) - # Nach Abwesenheitseinträgen suchen + # Abwesenheitseinträge + booking_color = "inherit" + booking_text_color = "inherit" try: + # Abwesenheitszeiten behandeln for i in list(user_absent): - if int(i) == day and user_absent[i] != "UU": - saldo = 0 + if int(i) == day: + booking_text += absence_entries[user_absent[i]]["name"] + "
" + booking_color = absence_entries[user_absent[i]]["color"] + booking_text_color = absence_entries[user_absent[i]]["text-color"] except: pass - general_saldo = general_saldo + saldo - total = f"{convert_seconds_to_hours(saldo)} h" - else: - total = "-" + # Buchungen behandeln + for i in range(len(timestamps_dict[day])): + try: + temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]] + booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + "-" + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "
" - rows.append({'date': current_day_date, 'bookings': booking_text, 'is_time': is_time, 'target_time': target_time, 'total': total}) + except: + if len(timestamps_dict[day]) % 2 != 0: + booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + print(booking_text) - overview_table = ui.table(columns=columns, rows=rows, row_key='date').classes('w-full') + booking_text_element = ui.markdown(booking_text).classes(f'border px-{pad_x} py-{pad_y} bg-{booking_color} text-{booking_text_color}') - # Zeilenumbruch umsetzen - overview_table.add_slot('body-cell', r''' - {{ props.value }} - ''') - # Überstundenzusammenfassung + # Ist-Zeiten berechnen + timestamps_of_this_day = [] - with ui.grid(columns=2).classes('w-full gap-0'): - ui.markdown("Überstunden aus Vormonat:") - last_months_overtime = current_user.get_last_months_overtime(year, month) - ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)} h") - ui.markdown("Überstunden diesen Monat:") - ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h") - ui.markdown("**Überstunden Gesamt:**") - overtime_overall = last_months_overtime + general_saldo - ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**") + # Suche mir alle timestamps für diesen Tag + for i in timestamps: + actual_timestamp = datetime.fromtimestamp(int(i)) + timestamp_day = actual_timestamp.strftime('%-d') - absences_this_month = current_user.get_absence(year, month) - absence_dict = { } + if int(timestamp_day) == int(day): + timestamps_of_this_day.append(i) - for abbr in list(absence_entries): - absence_dict[abbr] = 0 + timestamps_of_this_day.sort() + time_sum = 0 + if len(timestamps_of_this_day) > 1: - for key, value in absences_this_month.items(): - if value in list(absence_dict): - absence_dict[value] += 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 - total_absence_days = 0 - for key, value in absence_dict.items(): - total_absence_days += absence_dict[key] + is_time = convert_seconds_to_hours(time_sum) + " h" + else: + is_time = "Kein" - if total_absence_days > 0: - ui.markdown("###Abwesenheitstage diesen Monat:") - a_columns = [ - {'name': 'type', 'label': 'Typ', 'field': 'type', 'required': True, 'align': 'left'}, - {'name': 'sum', 'label': 'Tage', 'field': 'sum', 'required': True, 'align': 'left'}, - ] + ui.markdown(is_time).classes(f'border px-{pad_x} py-{pad_y} text-center') + # Sollzeit bestimmen - a_row = [ ] + hours_to_work = int(current_user.get_day_workhours(year, month, day)) - for key,value in absence_dict.items(): - if value > 0: - a_row.append({'type': absence_entries[key]['name'], 'sum': value}) + if hours_to_work < 0: + target_time = "" + else: + target_time = f"{convert_seconds_to_hours(int(hours_to_work) * 3600)} h" + if int(hours_to_work) == 0: + booking_text = "Kein Arbeitstag" + booking_text_element.set_content(booking_text) - absence_table = ui.table(columns=a_columns, rows=a_row) + ui.markdown(target_time).classes(f'border px-{pad_x} py-{pad_y} text-center') + # Saldo für den Tag berechnen + day_in_list = datetime(year, month, day) + if time.time() > day_in_list.timestamp(): + + time_duty = int(current_user.get_day_workhours(year, month, day)) * 3600 + if time_duty < 0: + saldo = 0 + total = "" + booking_text = "Kein Arbeitsverhältnis" + booking_text_element.set_content(booking_text) + 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 + total = f"{convert_seconds_to_hours(saldo)} h" + + else: + total = "-" + if total == "-": + total_class = 'text-center' + else: + total_class = 'text-right' + ui.markdown(total).classes(total_class).classes(f'border px-{pad_x} py-{pad_y}') + + # Überstundenzusammenfassung + ui.markdown("Überstunden aus Vormonat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}') + last_months_overtime = current_user.get_last_months_overtime(year, month) + ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)} h").classes(f'text-right border px-{pad_x} py-{pad_y}') + ui.markdown("Überstunden diesen Monat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}') + ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h").classes(f'text-right border px-{pad_x} py-{pad_y}') + ui.markdown("**Überstunden Gesamt:**").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}') + overtime_overall = last_months_overtime + general_saldo + ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**").classes(f'text-right border px-{pad_x} py-{pad_y}') + + overview_table() + + def absence_table(): + absences_this_month = current_user.get_absence(year, month) + absence_dict = { } + + for abbr in list(absence_entries): + absence_dict[abbr] = 0 + + for key, value in absences_this_month.items(): + if value in list(absence_dict): + absence_dict[value] += 1 + + total_absence_days = 0 + for key, value in absence_dict.items(): + total_absence_days += absence_dict[key] + + if total_absence_days > 0: + ui.markdown("###Abwesenheitstage diesen Monat:") + + with ui.grid(columns='auto 20%').classes(f'gap-0 border px-0 py-0'): + + for key,value in absence_dict.items(): + if value > 0: + ui.markdown(absence_entries[key]['name']).classes(f"border px-{pad_x} py-{pad_y}") + ui.markdown(str(value)).classes(f'border px-{pad_x} py-{pad_y} text-center') + + absence_table() except Exception as e: print(str(type(e).__name__) + " " + str(e)) diff --git a/definitions.py b/definitions.py index 7cee06d..679c89b 100644 --- a/definitions.py +++ b/definitions.py @@ -23,14 +23,21 @@ status_out = "ausgestempelt" # Abesenheiten absence_entries = {"U": { "name": "Urlaub", - "color": "green"}, + "color": "green", + "text-color": "black"}, "K": { "name": "Krankheit", - "color": "red"}, + "color": "red", + "text-color": "white"}, "KK": { "name": "Krankheit Kind", - "color": "orange"}, + "color": "orange", + "text-color": "black"}, "UU": { "name": "Urlaub aus Überstunden", - "color": "green"}, + "color": "green", + "text-color": "black"}, "F": { "name": "Fortbildung", - "color": "black"}, + "color": "black", + "text-color": "white"}, "EZ": { "name": "Elternzeit", - "color": "purple"}} + "color": "purple", + "text-color": "white"} + } diff --git a/playgound.py b/playgound.py index 092d37e..6428d72 100644 --- a/playgound.py +++ b/playgound.py @@ -1,16 +1,8 @@ from nicegui import ui -columns=[ - {'name': 'name', 'label': 'Name', 'field': 'name', 'align': 'left', 'style': 'text-wrap: wrap'}, - {'name': 'age', 'label': 'Age', 'field': 'age'}, - ] -rows=[ - {'name': 'Alice', 'age': 18}, - {'name': 'Bob', 'age': 21}, - {'name': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc ultricies'}, - ] - -ui.table(columns=columns, rows=rows).classes('w-80') +test = ('Eintrag 1') +ui.markdown(test) +ui.markdown('Nächstes Element') ui.run(language="de-DE", port=9000) \ No newline at end of file diff --git a/users.py b/users.py index 8dae9f2..0d5ef84 100644 --- a/users.py +++ b/users.py @@ -288,6 +288,24 @@ class user: hours_to_work = -1 return hours_to_work + def get_vacation_claim(self, year, month, day): + workhour_entries = list(self.workhours) + workhour_entries.sort() + day_to_check = datetime.datetime(int(year), int(month), int(day)) + + claim = -1 + + for entry in reversed(workhour_entries): + + entry_split = entry.split("-") + entry_dt = datetime.datetime(int(entry_split[0]), int(entry_split[1]), int(entry_split[2])) + + if entry_dt <= day_to_check: + claim = self.workhours[entry]["vacation"] + break + + return claim + def delete_photo(self): os.remove(self.photofile) diff --git a/users/testuser1/2025-4.json b/users/testuser1/2025-4.json index 0eb538b..1346aed 100644 --- a/users/testuser1/2025-4.json +++ b/users/testuser1/2025-4.json @@ -3,8 +3,10 @@ "overtime": 0, "absence": { "7": "U", - "8": "U", - "9": "U", - "10": "U" + "8": "K", + "9": "KK", + "10": "UU", + "11": "F", + "14": "EZ" } } \ No newline at end of file From 3e3537c8b88175177f27049e87972fad4c575ad8 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Tue, 29 Apr 2025 10:55:21 +0200 Subject: [PATCH 06/12] =?UTF-8?q?Urlaubs=C3=BCbersicht=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- main.py | 2 -- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/api.py b/api.py index 706ceb1..b48315a 100644 --- a/api.py +++ b/api.py @@ -15,7 +15,7 @@ import calendar # Überblicksseite zum Ausdrucken oder als PDF speichern @ui.page('/api/overview/month/{username}/{year}-{month}') -def page(username: str, year: int, month: int): +def page_overview_month(username: str, year: int, month: int): try: current_user = user(username) @@ -215,6 +215,49 @@ def page(username: str, year: int, month: int): absence_table() + except Exception as e: + print(str(type(e).__name__) + " " + str(e)) + if type(e) == UnboundLocalError: + ui.markdown('#Fehler') + ui.markdown('Benutzer existiert nicht') + else: + ui.markdown('#Fehler') + ui.markdown(str(type(e))) + ui.markdown(str(e)) + +@ui.page('/api/overview/vacation/{username}/{year}-{month}-{day}') +def page_overview_vacation(username: str, year: int, month: int, day: int): + try: + current_user = user(username) + + ui.page_title(f"Urlaubsanspruch für {current_user.fullname} für {year}") + ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5') + ui.space() + ui.markdown(f'#Urlaubsanspruch für {current_user.fullname} für {year}') + + pad_x = 4 + pad_y = 0 + + with ui.grid(columns='auto auto').classes(f'gap-0 border px-0 py-0'): + ui.markdown(f"Urlaubsübersicht für {year}:").classes(f'border px-{pad_x} py-{pad_y}') + vacationclaim = int(current_user.get_vacation_claim(year, month, day)) + ui.markdown(f"{vacationclaim} Tage").classes(f'text-right border px-{pad_x} py-{pad_y}') + ui.markdown("Registrierte Urlaubstage").classes(f'border px-{pad_x} py-{pad_y} col-span-2') + vacation_counter = 0 + try: + for i in range(1, 13): + absence_entries = current_user.get_absence(year, i) + for day, absence_type in absence_entries.items(): + print(day + "." + str(i) + " " + absence_type) + if absence_type == "U": + day_in_list = datetime(int(year), int(i), int(day)).strftime("%d.%m.%Y") + ui.markdown(day_in_list).classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown("-1 Tag").classes(f'border px-{pad_x} py-{pad_y} text-center') + vacation_counter += 1 + except Exception as e: + print(str(type(e).__name__) + " " + str(e)) + ui.markdown("**Resturlaub:**").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown(f'**{str(vacationclaim - vacation_counter)} Tage**').classes(f'border px-{pad_x} py-{pad_y} text-center') except Exception as e: print(str(type(e).__name__) + " " + str(e)) if type(e) == UnboundLocalError: diff --git a/main.py b/main.py index d2201b0..cd71488 100644 --- a/main.py +++ b/main.py @@ -10,8 +10,6 @@ from api import * import json - - def main(): # Einstellungen einlesen From 085b0f112910cc847f1aadb3fbee4bedfaf7e804 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Tue, 29 Apr 2025 12:29:53 +0200 Subject: [PATCH 07/12] =?UTF-8?q?Datumshervorhebung=20im=20Adminbereich=20?= =?UTF-8?q?f=C3=BCr=20heutigen=20Tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/admin.py b/admin.py index 87c9b17..8bc6ca0 100644 --- a/admin.py +++ b/admin.py @@ -120,6 +120,7 @@ def page_admin(): ui.markdown("**Saldo**") ui.space() + current_user = user(time_user.value) 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) @@ -140,7 +141,10 @@ def page_admin(): 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) - ui.markdown(f"{day_in_list.strftime('%a')}., {day}. {calendar.month_name[int(select_month.value)]}") + 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 From 7fe82c1767877642890bcf4e2eef00ee405be0af Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Wed, 30 Apr 2025 22:54:08 +0200 Subject: [PATCH 08/12] Commit nach git-Error Reichlich Zusatzfunktionen Homepage API Aufrufe Passwort Hashing --- admin.py | 34 +++--- api.py | 192 +++++++++++++++++++++++++++------- homepage.py | 126 ++++++++++++++++++++++ login.py | 6 +- main.py | 3 + settings.json | 2 +- users.py | 52 ++++++++- users/testuser1/2025-12.json | 8 -- users/testuser1/2025-3.json | 5 +- users/testuser1/2025-4.txt | 12 +++ users/testuser1/settings.json | 16 +-- web_ui.py | 76 +++++++++++--- 12 files changed, 438 insertions(+), 94 deletions(-) create mode 100644 homepage.py diff --git a/admin.py b/admin.py index 8bc6ca0..227bbf5 100644 --- a/admin.py +++ b/admin.py @@ -16,19 +16,19 @@ import locale def page_admin(): ui.page_title(f"{app_title} {app_version}") data = load_adminsettings() - active_login = cookie_hash(data["admin_user"], data["admin_password"]) + try: - browser_cookie = app.storage.user['secret'] + browser_cookie = app.storage.user['admin_authenticated'] except: - browser_cookie = "" + browser_cookie = False # Adminseite - if browser_cookie == active_login: + if browser_cookie: pageheader("Administration") def admin_logout(): - app.storage.user['secret'] = "" - ui.navigate.to("/login") + app.storage.user.pop("admin_authenticated", None) + ui.navigate.to("/") ui.button("Logout", on_click=admin_logout) @@ -455,7 +455,12 @@ Dies kann nicht rückgägig gemacht werden!''') def save_admin_settings(): output_dict = { } output_dict["admin_user"] = admin_user.value - output_dict["admin_password"] = admin_password.value + print(admin_password.value) + if admin_password.value != "": + print("Passwort neu gesetzt") + 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 json_dict = json.dumps(output_dict, indent=4) @@ -473,9 +478,8 @@ Dies kann nicht rückgägig gemacht werden!''') ui.markdown("Benutzername des Adminstrators") admin_user = ui.input() admin_user.value = data["admin_user"] - ui.markdown("Passwort des Adminsistrators") + ui.markdown("Passwort des Administrators") admin_password = ui.input(password=True) - admin_password.value = data["admin_password"] secret = data["secret"] @@ -503,7 +507,7 @@ Dies kann nicht rückgägig gemacht werden!''') current_user = user(user_selection.value) username_input.value = current_user.username fullname_input.value = current_user.fullname - password_input.value = current_user.password + #password_input.value = current_user.password usersettingscard.visible = True workhours_select.clear() @@ -519,7 +523,6 @@ Dies kann nicht rückgägig gemacht werden!''') user_photo.set_visibility(os.path.exists(current_user.photofile)) delete_button.set_visibility(os.path.exists(current_user.photofile)) - except: pass @@ -544,8 +547,9 @@ Dies kann nicht rückgägig gemacht werden!''') current_user = user(user_selection.value) current_user.username = username_input.value current_user.fullname = fullname_input.value - current_user.password = password_input.value + current_user.password = hash_password(password_input.value) current_user.write_settings() + password_input.value = "" userlist = list_users() userlist.sort() user_selection.clear() @@ -594,7 +598,7 @@ Dies kann nicht rückgägig gemacht werden!''') if i < 7: construct_dict[i+1] = days[i].value elif i == 7: - conctruct_dict[0] = days[i].value + construct_dict[0] = days[i].value construct_dict["vacation"] = vacation_input.value current_user.workhours[workhours_select.value] = construct_dict @@ -652,6 +656,7 @@ Dies kann nicht rückgägig gemacht werden!''') fullname_input = ui.input() ui.markdown("Passwort") password_input = ui.input(password=True) + password_input.value = "" with ui.grid(columns=2): ui.button("Speichern", on_click=save_user_settings) @@ -751,4 +756,5 @@ Dies kann nicht rückgägig gemacht werden!''') # Alternativ zur Loginseite navigieren else: - ui.navigate.to("/login") \ No newline at end of file + login = login_mask() + #ui.navigate.to("/login") \ No newline at end of file diff --git a/api.py b/api.py index b48315a..f7799e2 100644 --- a/api.py +++ b/api.py @@ -1,8 +1,8 @@ import sys +from calendar import month_name from logging import exception from nicegui import * -from samba.graph import pad_char import ui from definitions import * @@ -14,7 +14,7 @@ import calendar # Überblicksseite zum Ausdrucken oder als PDF speichern -@ui.page('/api/overview/month/{username}/{year}-{month}') +@ui.page('/api/month/{username}/{year}-{month}') def page_overview_month(username: str, year: int, month: int): try: @@ -94,7 +94,6 @@ def page_overview_month(username: str, year: int, month: int): except: if len(timestamps_dict[day]) % 2 != 0: booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') - print(booking_text) booking_text_element = ui.markdown(booking_text).classes(f'border px-{pad_x} py-{pad_y} bg-{booking_color} text-{booking_text_color}') @@ -183,6 +182,7 @@ def page_overview_month(username: str, year: int, month: int): ui.markdown("Überstunden diesen Monat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}') ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h").classes(f'text-right border px-{pad_x} py-{pad_y}') ui.markdown("**Überstunden Gesamt:**").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}') + global overtime_overall overtime_overall = last_months_overtime + general_saldo ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**").classes(f'text-right border px-{pad_x} py-{pad_y}') @@ -215,6 +215,39 @@ def page_overview_month(username: str, year: int, month: int): absence_table() + def archive(): + current_year = datetime.now().year + current_month = datetime.now().month + archivable = False + + if current_year > year: + if current_user.get_archive_status(year, month) == False: + archivable = True + if current_year == year: + if current_month > month: + if current_user.get_archive_status(year, month) == False: + archivable = True + + def archive_dialog(): + def do_archiving(): + global overtime_overall + current_user.archive_hours(year, month, overtime_overall) + dialog.close() + ui.navigate.to(f'/api/month/{username}/{year}-{month}') + + with ui.dialog() as dialog, ui.card(): + with ui.grid(columns='1fr 1fr'): + ui.markdown("Hiermit bestätigen Sie, dass die Zeitbuchungen im Montagsjournal korrekt sind.
Sollte dies nicht der Fall sein, wenden Sie sich für eine Korrektur an den Administrator.").classes('col-span-2') + ui.button("Archivieren", on_click=do_archiving) + ui.button("Abbrechen", on_click=dialog.close) + + dialog.open() + + if archivable == True: + ui.button("Archivieren", on_click=archive_dialog) + + archive() + except Exception as e: print(str(type(e).__name__) + " " + str(e)) if type(e) == UnboundLocalError: @@ -225,45 +258,126 @@ def page_overview_month(username: str, year: int, month: int): ui.markdown(str(type(e))) ui.markdown(str(e)) -@ui.page('/api/overview/vacation/{username}/{year}-{month}-{day}') -def page_overview_vacation(username: str, year: int, month: int, day: int): - try: - current_user = user(username) +@ui.page('/api/vacation/{username}/{year}') +def page_overview_vacation(username: str, year: int): - ui.page_title(f"Urlaubsanspruch für {current_user.fullname} für {year}") + if login_is_valid(username): + + try: + current_user = user(username) + + month = datetime.now().month + day = datetime.now().day + + ui.page_title(f"Urlaubsanspruch für {current_user.fullname} für {year}") + ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5') + ui.space() + ui.markdown(f'#Urlaubsanspruch für {current_user.fullname} für {year}') + + pad_x = 4 + pad_y = 0 + + vacationclaim = int(current_user.get_vacation_claim(year, month, day)) + if vacationclaim == -1: + ui.markdown(f"###Kein Urlaubsanspruch für {year}") + else: + + with ui.grid(columns='auto auto').classes(f'gap-0 border px-0 py-0'): + ui.markdown(f"Urlaubsanspruch für {year}:").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown(f"{vacationclaim} Tage").classes(f'text-right border px-{pad_x} py-{pad_y}') + ui.markdown("Registrierte Urlaubstage").classes(f'border px-{pad_x} py-{pad_y} col-span-2') + vacation_counter = 0 + try: + for i in range(1, 13): + absence_entries = current_user.get_absence(year, i) + for day, absence_type in absence_entries.items(): + # print(day + "." + str(i) + " " + absence_type) + if absence_type == "U": + day_in_list = datetime(int(year), int(i), int(day)).strftime("%d.%m.%Y") + ui.markdown(day_in_list).classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown("-1 Tag").classes(f'border px-{pad_x} py-{pad_y} text-center') + vacation_counter += 1 + except Exception as e: + print(str(type(e).__name__) + " " + str(e)) + ui.markdown("**Resturlaub:**").classes(f'border px-{pad_x} py-{pad_y}') + ui.markdown(f'**{str(vacationclaim - vacation_counter)} Tage**').classes(f'border px-{pad_x} py-{pad_y} text-center') + + + except Exception as e: + print(str(type(e).__name__) + " " + str(e)) + if type(e) == UnboundLocalError: + ui.markdown('#Fehler') + ui.markdown('Benutzer existiert nicht') + else: + ui.markdown('#Fehler') + ui.markdown(str(type(e))) + ui.markdown(str(e)) + else: + login = login_mask(target=f'/api/vacation/{username}/{year}') + +@ui.page('/api/absence/{username}/{year}') +def page_overview_absence(username: str, year: int): + + if login_is_valid(username): + current_user = user(username) + ui.page_title(f"Abwesenheitsübersicht für {current_user.fullname} für {year}") ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5') ui.space() - ui.markdown(f'#Urlaubsanspruch für {current_user.fullname} für {year}') + pageheader(f"Abwesenheitsübersicht für {current_user.fullname} für {year}") - pad_x = 4 + pad_x = 2 pad_y = 0 - with ui.grid(columns='auto auto').classes(f'gap-0 border px-0 py-0'): - ui.markdown(f"Urlaubsübersicht für {year}:").classes(f'border px-{pad_x} py-{pad_y}') - vacationclaim = int(current_user.get_vacation_claim(year, month, day)) - ui.markdown(f"{vacationclaim} Tage").classes(f'text-right border px-{pad_x} py-{pad_y}') - ui.markdown("Registrierte Urlaubstage").classes(f'border px-{pad_x} py-{pad_y} col-span-2') - vacation_counter = 0 - try: - for i in range(1, 13): - absence_entries = current_user.get_absence(year, i) - for day, absence_type in absence_entries.items(): - print(day + "." + str(i) + " " + absence_type) - if absence_type == "U": - day_in_list = datetime(int(year), int(i), int(day)).strftime("%d.%m.%Y") - ui.markdown(day_in_list).classes(f'border px-{pad_x} py-{pad_y}') - ui.markdown("-1 Tag").classes(f'border px-{pad_x} py-{pad_y} text-center') - vacation_counter += 1 - except Exception as e: - print(str(type(e).__name__) + " " + str(e)) - ui.markdown("**Resturlaub:**").classes(f'border px-{pad_x} py-{pad_y}') - ui.markdown(f'**{str(vacationclaim - vacation_counter)} Tage**').classes(f'border px-{pad_x} py-{pad_y} text-center') - except Exception as e: - print(str(type(e).__name__) + " " + str(e)) - if type(e) == UnboundLocalError: - ui.markdown('#Fehler') - ui.markdown('Benutzer existiert nicht') - else: - ui.markdown('#Fehler') - ui.markdown(str(type(e))) - ui.markdown(str(e)) \ No newline at end of file + def absence_calender(): + + column_constructor = 'auto ' + for j in range(1, 31): + column_constructor += "1fr " + column_constructor += 'auto' + + with ui.grid(columns=column_constructor).classes(f'gap-0 border px-0 py-0') as calendar_grid: + # Erste Zeile + ui.space() + for i in range(1, 32): + ui.markdown(str(i)).classes(f'border px-{pad_x} py-{pad_y} text-center') + # Monate durchgehen + for month in range(1, 13): + for column in range(0, 32): + if column == 0: + ui.markdown(month_name[month]).classes(f'border px-{pad_x} py-{pad_y} text.center') + else: + absences = current_user.get_absence(year, month) + if str(column) in list(absences): + bg_color = absence_entries[absences[str(column)]]['color'] + text_color = absence_entries[absences[str(column)]]['text-color'] + ui.markdown(absences[str(column)]).classes(f'border px-{pad_x} py-{pad_y} bg-{bg_color} text-{text_color} text-center') + else: + if column > monthrange(year, month)[1]: + bg_color = 'gray-500' + elif int(current_user.get_day_workhours(year, month, column)) == 0: + bg_color = 'gray-300' + elif int(current_user.get_day_workhours(year, month, column)) == -1: + bg_color = 'gray-400' + else: + bg_color = 'inherit' + ui.space().classes(f'border px-{pad_x} py-{pad_y} bg-{bg_color}') + + absence_calender() + + def absence_table(): + + with ui.grid(columns='auto auto').classes(f'gap-0 px-0 py-0'): + ui.markdown('**Summen**').classes('col-span-2 px-2') + for type in list(absence_entries): + number_of_days = 0 + ui.markdown(absence_entries[type]["name"]).classes(f'border px-{pad_x} py-{pad_y}') + for month in range(1, 13): + absences_of_month = current_user.get_absence(year, month) + for i in list(absences_of_month): + if absences_of_month[i] == type: + number_of_days += 1 + ui.markdown(str(number_of_days)).classes(f'border px-{pad_x} py-{pad_y} text-center') + absence_table() + + else: + login = login_mask(target=f'/api/absence/{username}/{year}') \ No newline at end of file diff --git a/homepage.py b/homepage.py new file mode 100644 index 0000000..98d2d9d --- /dev/null +++ b/homepage.py @@ -0,0 +1,126 @@ +# Zeiterfassung +import datetime + +from nicegui import ui, app + +from users import * +from definitions import * +from calendar import monthrange, month_name + +import hashlib +import calendar +import locale + +from web_ui import * + +@ui.page('/') +def homepage(): + if login_is_valid(): + + ui.page_title("Zeiterfassung") + current_user = user(app.storage.user["active_user"]) + pageheader(f"Willkommen, {current_user.fullname}") + + today = datetime.datetime.now() + + @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() + 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') + + def update_timer(): + time_in_total = time_so_far + int((datetime.datetime.now().timestamp() - current_user.get_worked_time(today.year, today.month, today.day)[1])) + print(time_in_total) + working_hours.set_content(convert_seconds_to_hours(time_in_total)) + + 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() + + ui.separator() + + with ui.grid(columns='1fr auto 1fr').classes('w-full justify-center'): + ui.space() + + def activate_vacation(): + binder_vacation.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/overview/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/overview/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))) + absences_button = ui.button("Anzeigen").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') + ui.space() + + else: + login_mask() \ No newline at end of file diff --git a/login.py b/login.py index 9b37f38..3a351d7 100644 --- a/login.py +++ b/login.py @@ -21,9 +21,9 @@ def page_login(): nonlocal data if username.value == data["admin_user"]: - if password.value == data["admin_password"]: - active_login = cookie_hash(data["admin_user"], data["admin_password"]) - app.storage.user['secret'] = active_login + print(f"Input Hash: {hash_password(password.value)} gespeichert: {data['admin_password']}") + if hash_password(password.value) == data["admin_password"]: + app.storage.user['authenticated'] = True ui.navigate.to("/admin") else: ui.notify("Login fehlgeschlagen") diff --git a/main.py b/main.py index cd71488..3da3f94 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ from users import * from touchscreen import * from definitions import * from api import * +from homepage import * import json @@ -19,6 +20,8 @@ def main(): port = int(data["port"]) secret = data["secret"] + homepage() + ui.run(port=port, storage_secret=secret, language='de-DE') if __name__ in ("__main__", "__mp_main__"): diff --git a/settings.json b/settings.json index 7f64e97..22c8ed7 100644 --- a/settings.json +++ b/settings.json @@ -1,6 +1,6 @@ { "admin_user": "admin", - "admin_password": "123456", + "admin_password": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", "port": "8090", "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise" } \ No newline at end of file diff --git a/users.py b/users.py index 0d5ef84..f8c1466 100644 --- a/users.py +++ b/users.py @@ -85,7 +85,7 @@ class user: lines = file.readlines() file.close() except FileNotFoundError: - print(f"Die Datei {filename} wurde nicht gefunden.") + print(f"Die Datei {file} wurde nicht gefunden.") if len(lines)== 0: print(f"Keine Einträge") elif len(lines) % 2 == 0: @@ -207,8 +207,26 @@ class user: with open(f"{self.userfolder}/{year}-{month}.txt", "w") as file: file.write(''.join(timestamps)) - def archive_hours(self, year, month): - pass + def get_archive_status(self, year, month): + try: + with open(f"{self.userfolder}/{year}-{month}.json", 'r') as json_file: + data = json.load(json_file) + return (data["archived"]) + except: + return(-1) + + def archive_hours(self, year, month, overtime: int): + + filename = f"{self.userfolder}/{year}-{month}.json" + with open(filename, 'r') as json_file: + data = json.load(json_file) + data["archived"] = 1 + data["overtime"] = overtime + + json_dict = json.dumps(data) + + with open(filename, "w") as outputfile: + outputfile.write(json_dict) def get_last_months_overtime(self, year, month): try: @@ -309,6 +327,34 @@ class user: def delete_photo(self): os.remove(self.photofile) + def get_worked_time(self, year, month, day): + timestamps = self.get_timestamps(year, month) + check_day_dt = datetime.datetime(year, month, day) + todays_timestamps = [ ] + + for i in timestamps: + i_dt = datetime.datetime.fromtimestamp(int(i)) + if i_dt.date() == check_day_dt.date(): + todays_timestamps.append(int(i)) + + todays_timestamps.sort() + if len(todays_timestamps) % 2 == 0: + workrange = len(todays_timestamps) + in_time_stamp = -1 + else: + workrange = len(todays_timestamps) - 1 + in_time_stamp = int(todays_timestamps[-1]) + total_time = 0 + + for i in range(0, workrange, 2): + time_worked = todays_timestamps[i + 1] - todays_timestamps[i] + total_time += time_worked + + print(total_time) + print(in_time_stamp) + + return([total_time, in_time_stamp]) + # Benutzer auflisten def list_users(): users = [d for d in os.listdir(userfolder) if os.path.isdir(os.path.join(userfolder, d))] diff --git a/users/testuser1/2025-12.json b/users/testuser1/2025-12.json index fe93723..bae53bc 100644 --- a/users/testuser1/2025-12.json +++ b/users/testuser1/2025-12.json @@ -7,29 +7,21 @@ "3": "EZ", "4": "EZ", "5": "EZ", - "6": "EZ", - "7": "EZ", "8": "EZ", "9": "EZ", "10": "EZ", "11": "EZ", "12": "EZ", - "13": "EZ", - "14": "EZ", "15": "EZ", "16": "EZ", "17": "EZ", "18": "EZ", "19": "EZ", - "20": "EZ", - "21": "EZ", "22": "EZ", "23": "EZ", "24": "EZ", "25": "EZ", "26": "EZ", - "27": "EZ", - "28": "EZ", "29": "EZ", "30": "EZ", "31": "EZ" diff --git a/users/testuser1/2025-3.json b/users/testuser1/2025-3.json index 8727377..27c5b37 100644 --- a/users/testuser1/2025-3.json +++ b/users/testuser1/2025-3.json @@ -1,4 +1 @@ -{ - "archived": 1, - "overtime": 3950 -} \ No newline at end of file +{"archived": 1, "overtime": -528928} \ No newline at end of file diff --git a/users/testuser1/2025-4.txt b/users/testuser1/2025-4.txt index 0a59752..ad67cca 100644 --- a/users/testuser1/2025-4.txt +++ b/users/testuser1/2025-4.txt @@ -20,3 +20,15 @@ 1745390894 1745390894 1745391029 +1746006467 +1746006593 +1746006933 +1746006937 +1746007004 +1746007012 +1746007119 +1746007383 +1746010855 +1746010861 +1746011089 +1746011092 diff --git a/users/testuser1/settings.json b/users/testuser1/settings.json index e0141a6..50a4132 100644 --- a/users/testuser1/settings.json +++ b/users/testuser1/settings.json @@ -1,17 +1,17 @@ { "username": "testuser1", "fullname": "Pia Paulina", - "password": "123456789", + "password": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "workhours": { "2025-05-13": { - "1": "0", - "2": "0", - "3": "0", - "4": "0", - "5": "0", + "1": "4", + "2": "5", + "3": "6", + "4": "7", + "5": "8", "6": "0", "7": "0", - "vacation": "0" + "vacation": "30" }, "2025-04-22": { "1": "1", @@ -21,7 +21,7 @@ "5": "5", "6": "6", "7": "7", - "vacation": "0" + "vacation": "30" }, "2025-03-01": { "1": "4", diff --git a/web_ui.py b/web_ui.py index 1420d52..d12227a 100644 --- a/web_ui.py +++ b/web_ui.py @@ -26,6 +26,9 @@ class ValueBinder: def cookie_hash(user, password): return hashlib.sha256(b"{user}{app.storage.user['id']}{password}").hexdigest() +def hash_password(password): + return hashlib.sha256(bytes(password, 'utf-8')).hexdigest() + def load_adminsettings(): # Settingsdatei einlesen try: @@ -36,6 +39,50 @@ def load_adminsettings(): except: return(-1) +class login_mask: + def __init__(self, target="/"): + data = load_adminsettings() + self.target = target + + def login(): + nonlocal data + + if username.value == data["admin_user"]: + if hash_password(password.value) == data["admin_password"]: + app.storage.user['admin_authenticated'] = True + ui.navigate.to("/admin") + else: + ui.notify("Login fehlgeschlagen") + else: + userlist = list_users() + + if username.value in userlist: + current_user = user(username.value) + + if hash_password(password.value) == current_user.password: + app.storage.user['active_user'] = current_user.username + ui.navigate.to(self.target) + else: + ui.notify("Login fehlgeschlagen") + else: + ui.notify("Login fehlgeschlagen") + + # ui.markdown(f"## {app_title} {app_version}") + # ui.markdown("Bitte einloggen") + + pageheader("Bitte einloggen:") + + with ui.grid(columns='20% auto 20%').classes('w-full justify-center'): + + ui.space() + with ui.grid(columns=2): + ui.markdown("Benutzer:") + username = ui.input('Benutzername') + ui.markdown("Passwort:") + password = ui.input('Passwort', password=True) + ui.button(text="Login", on_click=lambda: login()) + ui.space() + def convert_seconds_to_hours(seconds): if seconds < 0: sign = "-" @@ -62,19 +109,20 @@ def convert_seconds_to_hours(seconds): else: return(f"{hours}:{minutes}") -@ui.page('/userlist') -def page_userlist(): - - def click_button(button): - ui.notify(button) - - ui.markdown(f"#{app_title} {app_version}") - - userlist = list_users() - buttons = { } - - for name in userlist: - button = ui.button(text=name, on_click=lambda name=name:click_button(name) ) - buttons[name] = button +def login_is_valid(user = -1): + if user == -1: + try: + app.storage.user['active_user'] + return True + except: + return False + else: + try: + if app.storage.user['active_user'] == user: + return True + else: + return False + except: + return False From 797cf7d52979fb6facbdff0fa2b21fd1d2b69ae1 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Wed, 30 Apr 2025 22:55:38 +0200 Subject: [PATCH 09/12] =?UTF-8?q?Git=20Ignore=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc From 7805b98ec0dcb30758e76fe46f87b4146bb45ddd Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Thu, 1 May 2025 11:42:33 +0200 Subject: [PATCH 10/12] Feiertage in Adminauswertung --- admin.py | 43 ++++++++++++++++++++++++++++++++++---- settings.json | 3 ++- users.py | 37 ++++++++++++++++++++++++++++++-- users/testuser1/2025-5.txt | 2 ++ web_ui.py | 10 --------- 5 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 users/testuser1/2025-5.txt diff --git a/admin.py b/admin.py index 227bbf5..bf48431 100644 --- a/admin.py +++ b/admin.py @@ -455,9 +455,7 @@ Dies kann nicht rückgägig gemacht werden!''') def save_admin_settings(): output_dict = { } output_dict["admin_user"] = admin_user.value - print(admin_password.value) if admin_password.value != "": - print("Passwort neu gesetzt") output_dict["admin_password"] = hash_password(admin_password.value) else: output_dict["admin_password"] = data["admin_password"] @@ -466,8 +464,6 @@ Dies kann nicht rückgägig gemacht werden!''') json_dict = json.dumps(output_dict, indent=4) with open(f"{scriptpath}/{usersettingsfilename}", "w") as outputfile: outputfile.write(json_dict) - print(old_port) - print(int(port.value)) if 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.") @@ -491,6 +487,45 @@ Dies kann nicht rückgägig gemacht werden!''') port = ui.input() old_port = data["port"] port.value = old_port + + @ui.refreshable + def holiday_section(): + with ui.card(): + ui.markdown('**Feiertage:**') + holidays = 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) + + for year_entry in year_list: + with ui.row(): + ui.markdown(f"{str(year_entry)}:") + for entry in year_dict[year_entry]: + date_label = entry.strftime("%d.%m.%Y") + ui.button(date_label) + holiday_section() + ui.button("Speichern", on_click=save_admin_settings) with ui.tab_panel(users): diff --git a/settings.json b/settings.json index 22c8ed7..1097177 100644 --- a/settings.json +++ b/settings.json @@ -2,5 +2,6 @@ "admin_user": "admin", "admin_password": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", "port": "8090", - "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise" + "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise", + "holidays": [ "2024-01-01", "2024-12-25", "2025-01-01", "2025-05-01"] } \ No newline at end of file diff --git a/users.py b/users.py index f8c1466..5f9c953 100644 --- a/users.py +++ b/users.py @@ -83,9 +83,14 @@ class user: with open(f"{self.get_stamp_file()}.txt", 'r') as file: # Zähle die Zeilen lines = file.readlines() - file.close() except FileNotFoundError: - print(f"Die Datei {file} wurde nicht gefunden.") + print(f"Die Datei {self.get_stamp_file()} wurde nicht gefunden.") + print("Lege die Datei an.") + with open(f'{self.get_stamp_file()}.txt', 'w') as file: + file.write("") + with open(f"{self.get_stamp_file()}.txt", 'r') as file: + # Zähle die Zeilen + lines = file.readlines() if len(lines)== 0: print(f"Keine Einträge") elif len(lines) % 2 == 0: @@ -290,6 +295,23 @@ class user: workhour_entries.sort() day_to_check = datetime.datetime(int(year), int(month), int(day)) + # Fertage prüfen + settings = load_adminsettings() + holidays = settings["holidays"] + + today_dt = datetime.datetime(int(year), int(month), int(day)) + check_date_list = [ ] + for i in holidays: + i_split = i.split("-") + check_year = int(i_split[0]) + check_month = int(i_split[1]) + check_day = int(i_split[2]) + check_dt = datetime.datetime(check_year, check_month, check_day) + check_date_list.append(check_dt) + if today_dt in check_date_list: + return 0 + + # Wochenarbeitszeit durchsuchen for entry in reversed(workhour_entries): entry_split = entry.split("-") @@ -361,3 +383,14 @@ def list_users(): users.sort() return users +# Admineinstellungen auslesen +def load_adminsettings(): + # Settingsdatei einlesen + try: + with open(f"{scriptpath}/{usersettingsfilename}") as json_file: + data = json.load(json_file) + json_file.close() + return(data) + except: + return(-1) + diff --git a/users/testuser1/2025-5.txt b/users/testuser1/2025-5.txt new file mode 100644 index 0000000..36ee3bc --- /dev/null +++ b/users/testuser1/2025-5.txt @@ -0,0 +1,2 @@ +1746090493 +1746090500 diff --git a/web_ui.py b/web_ui.py index d12227a..c4b76e6 100644 --- a/web_ui.py +++ b/web_ui.py @@ -29,16 +29,6 @@ def cookie_hash(user, password): def hash_password(password): return hashlib.sha256(bytes(password, 'utf-8')).hexdigest() -def load_adminsettings(): - # Settingsdatei einlesen - try: - with open(f"{scriptpath}/{usersettingsfilename}") as json_file: - data = json.load(json_file) - json_file.close() - return(data) - except: - return(-1) - class login_mask: def __init__(self, target="/"): data = load_adminsettings() From b24c7912c4e673ed58972e070bd20188319813ba Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Thu, 1 May 2025 11:45:47 +0200 Subject: [PATCH 11/12] API-Links angepasst --- homepage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homepage.py b/homepage.py index 98d2d9d..2aa6540 100644 --- a/homepage.py +++ b/homepage.py @@ -106,10 +106,10 @@ def homepage(): month_month_select.disable() ui.space() - month_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"/api/overview/month/{current_user.username}/{month_year_select.value}-{month_month_select.value}", new_tab=True)).bind_enabled_from(binder_month_button, 'value') + 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/overview/vacation/{current_user.username}/{vacation_select.value}", new_tab=True)).bind_enabled_from(binder_vacation, 'value') + 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))) absences_button = ui.button("Anzeigen").bind_enabled_from(binder_absence, 'value') From aed20556ecc1d5bcc19597a0191a40eeb5a72cd1 Mon Sep 17 00:00:00 2001 From: Alexander Malzkuhn Date: Fri, 2 May 2025 07:25:06 +0200 Subject: [PATCH 12/12] Feiertage bearbeitet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beschreibung hinzugefügt Eingabemaske hinzugefügt Löschfunktion hinzugefügt --- admin.py | 99 ++++++++++++++++++++++++++------------ settings.json | 7 ++- users.py | 2 +- users/testuser1/2025-5.txt | 2 - 4 files changed, 76 insertions(+), 34 deletions(-) diff --git a/admin.py b/admin.py index bf48431..afb8c8c 100644 --- a/admin.py +++ b/admin.py @@ -38,7 +38,7 @@ def page_admin(): settings = ui.tab('Einstellungen') users = ui.tab('Benutzer') - 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") @@ -291,7 +291,8 @@ Dies kann nicht rückgägig gemacht werden!''') 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 @@ -461,10 +462,11 @@ Dies kann nicht rückgägig gemacht werden!''') output_dict["admin_password"] = data["admin_password"] output_dict["port"] = port.value output_dict["secret"] = secret + output_dict["holidays"] = data["holidays"] json_dict = json.dumps(output_dict, indent=4) with open(f"{scriptpath}/{usersettingsfilename}", "w") as outputfile: outputfile.write(json_dict) - if old_port != int(port.value): + 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()) @@ -488,42 +490,79 @@ Dies kann nicht rückgägig gemacht werden!''') old_port = data["port"] port.value = old_port - @ui.refreshable def holiday_section(): with ui.card(): ui.markdown('**Feiertage:**') - holidays = data["holidays"] - holidays.sort() - year_list = [ ] + 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 + print(data['holidays']) + holiday_buttons_grid.refresh() + dialog.close() - # Jahresliste erzeugen - for i in holidays: - i_split = i.split("-") - year = int(i_split[0]) - year_list.append(year) + 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() - year_list = list(set(year_list)) - year_dict = { } + @ui.refreshable + def holiday_buttons_grid(): - # Jahresdictionary konstruieren - for i in year_list: - year_dict[i] = [ ] + holidays = list(data["holidays"]) + holidays.sort() - 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) + 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')]) + holiday_buttons_grid.refresh() + + with ui.grid(columns='auto auto'): + ui.space() + with ui.row(): + ui.button("Neuer Eintrag", on_click=new_holiday_entry) + + 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.%Y") + ui.button(f"{data['holidays'][entry.strftime('%Y-%m-%d')]} ({date_label})", on_click=lambda entry=entry: del_holiday_entry(entry)).classes('bg-blue') + holiday_buttons_grid() - for year_entry in year_list: - with ui.row(): - ui.markdown(f"{str(year_entry)}:") - for entry in year_dict[year_entry]: - date_label = entry.strftime("%d.%m.%Y") - ui.button(date_label) holiday_section() ui.button("Speichern", on_click=save_admin_settings) diff --git a/settings.json b/settings.json index 1097177..c6d1e98 100644 --- a/settings.json +++ b/settings.json @@ -3,5 +3,10 @@ "admin_password": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", "port": "8090", "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise", - "holidays": [ "2024-01-01", "2024-12-25", "2025-01-01", "2025-05-01"] + "holidays": { + "2024-01-01": "Tag der Arbeit", + "2024-12-25": "1. Weihnachtsfeiertag", + "2025-01-01": "Neujahr", + "2025-05-01": "Tag der Arbeit" + } } \ No newline at end of file diff --git a/users.py b/users.py index 5f9c953..eca1707 100644 --- a/users.py +++ b/users.py @@ -297,7 +297,7 @@ class user: # Fertage prüfen settings = load_adminsettings() - holidays = settings["holidays"] + holidays = list(settings["holidays"]) today_dt = datetime.datetime(int(year), int(month), int(day)) check_date_list = [ ] diff --git a/users/testuser1/2025-5.txt b/users/testuser1/2025-5.txt index 36ee3bc..e69de29 100644 --- a/users/testuser1/2025-5.txt +++ b/users/testuser1/2025-5.txt @@ -1,2 +0,0 @@ -1746090493 -1746090500