Archivhandling erweitert

Archivstatus hervorgehoben
Archivstatus aufhebbar für Admin
Favicon eingefügt
This commit is contained in:
Alexander Malzkuhn 2025-05-05 12:53:08 +02:00
parent 446e588d70
commit 327b4f1666
6 changed files with 500 additions and 305 deletions

View File

@ -112,10 +112,47 @@ def page_admin():
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
@ui.refreshable
def timetable():
with ui.card() as calendar_card:
def update_month_and_year():
current_user = user(time_user.value)
# Archivstatus
with ui.grid(columns='auto auto 1fr 1fr 1fr 1fr') as table_grid:
if int(select_month.value) > 1:
archive_status = current_user.get_archive_status(int(select_year.value),
int(select_month.value))
else:
archive_status = current_user.get_archive_status(int(select_year.value) - 1, 12)
def revoke_archive_status():
def revoke_status():
filename = f"{current_user.userfolder}/{int(select_year.value)}-{int(select_month.value)}.json"
with open(filename, 'r') as json_file:
data = json.load(json_file)
data["archived"] = 0
json_dict = json.dumps(data)
with open(filename, "w") as outputfile:
outputfile.write(json_dict)
timetable.refresh()
dialog.close()
with ui.dialog() as dialog, ui.card():
ui.label("Soll der Archivstatus für den aktuellen Monat aufgehoben werden, damit Änderungen vorgenommen werden können?")
with ui.grid(columns=2):
ui.button("Ja", on_click=revoke_status)
ui.button("Nein", on_click=dialog.close)
dialog.open()
if archive_status:
with ui.row().classes('text-right col-span-6 justify-center'):
ui.button("Archiviert", on_click=revoke_archive_status).classes('bg-transparent text-black')
ui.separator()
calendar_card.classes('bg-yellow')
# Überschriften
ui.markdown("**Datum**")
ui.markdown("**Buchungen**")
ui.markdown("**Ist**")
@ -123,8 +160,6 @@ 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)
# Dictionary für sortierte Timestamps
@ -172,7 +207,9 @@ Dies kann nicht rückgägig gemacht werden!''')
try:
for i in list(user_absent):
if int(i) == day:
ui.button(absence_entries[user_absent[i]]["name"], on_click=lambda i=i, day=day: delete_absence(day, absence_entries[user_absent[i]]["name"])).props(f'color={absence_entries[user_absent[i]]["color"]}')
absence_button = ui.button(absence_entries[user_absent[i]]["name"], on_click=lambda i=i, day=day: delete_absence(day, absence_entries[user_absent[i]]["name"])).props(f'color={absence_entries[user_absent[i]]["color"]}')
if archive_status:
absence_button.disable()
except:
pass
@ -240,14 +277,18 @@ Dies kann nicht rückgägig gemacht werden!''')
for i in range(len(timestamps_dict[day])):
try:
temp_pair = [ timestamps_dict[day][i] , timestamps_dict[day][i+1] ]
with ui.card():
with ui.card().classes('bg-inherit'):
with ui.row():
for j in temp_pair:
ui.button(datetime.datetime.fromtimestamp(int(j)).strftime('%H:%M'), on_click=lambda t_stamp=j, day=day: edit_entry(t_stamp, day))
timestamp_button = ui.button(datetime.datetime.fromtimestamp(int(j)).strftime('%H:%M'), on_click=lambda t_stamp=j, day=day: edit_entry(t_stamp, day))
if archive_status:
timestamp_button.disable()
except:
if len(timestamps_dict[day]) % 2 != 0:
with ui.card():
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))
with ui.card().classes('bg-inherit'):
timestamp_button = ui.button(datetime.datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M'), on_click=lambda t_stamp=timestamps_dict[day][i], day=day: edit_entry(t_stamp, day))
if archive_status:
timestamp_button.disable()
# Arbeitszeit Ist bestimmen
@ -422,10 +463,14 @@ Dies kann nicht rückgägig gemacht werden!''')
with ui.button(icon='menu'):
with ui.menu() as menu:
ui.menu_item("Zeiteintrag hinzufügen", lambda day=day: add_entry(day))
menu_item = ui.menu_item("Zeiteintrag hinzufügen", lambda day=day: add_entry(day))
if archive_status:
menu_item.disable()
ui.separator()
for i in list(absence_entries):
menu_item = ui.menu_item(f"{absence_entries[i]['name']} eintragen", lambda absence_type=i, day=day: add_absence(absence_type, day))
if archive_status:
menu_item.disable()
if str(day) in list(user_absent):
menu_item.disable()
@ -439,7 +484,9 @@ Dies kann nicht rückgägig gemacht werden!''')
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"**<ins>{convert_seconds_to_hours(general_saldo + last_months_overtime)}</ins>**").classes('text-right')
table_grid.move(calendar_card)
update_month_and_year()
def clear_card():
@ -450,6 +497,7 @@ Dies kann nicht rückgägig gemacht werden!''')
button_update = ui.button("Aktualisieren", on_click=clear_card)
button_update.move(timetable_header)
timetable()
with ui.tab_panel(settings):
with ui.card():

16
api.py
View File

@ -22,8 +22,14 @@ def page_overview_month(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()
if current_user.get_archive_status(year, month):
with ui.column().classes('w-full items-end gap-0'):
ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
ui.label('Archiviert').classes('italic').classes('text-red text-bold text-xl')
#ui.add_head_html('<style>body {background-color: #FFF7B1; }</style>')
else:
with ui.column().classes('w-full items-end gap-0'):
ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}')
pad_x = 4
@ -55,7 +61,11 @@ def page_overview_month(username: str, year: int, month: int):
general_saldo = 0
with ui.grid(columns='auto auto 1fr 1fr 1fr').classes(f'gap-0 border px-0 py-0'):
bg_color = ''
if current_user.get_archive_status(year, month):
bg_color = ' bg-yellow-100'
with ui.grid(columns='auto auto 1fr 1fr 1fr').classes(f'gap-0 border px-0 py-0{bg_color}'):
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}')

118
favicon.svg Normal file
View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ns1="http://sozi.baierouge.fr"
xmlns:cc="http://web.resource.org/cc/"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:dc="http://purl.org/dc/elements/1.1/"
id="svg1"
sodipodi:docname="timeedit.svg"
viewBox="0 0 60 60"
sodipodi:version="0.32"
_SVGFile__filename="oldscale/actions/todo.svg"
version="1.0"
y="0"
x="0"
inkscape:version="0.40"
sodipodi:docbase="/home/danny/work/flat/SVG/mono/EXTRA/kexi"
>
<sodipodi:namedview
id="base"
bordercolor="#666666"
inkscape:pageshadow="2"
inkscape:window-y="0"
pagecolor="#ffffff"
inkscape:window-height="698"
inkscape:zoom="5.5342875"
inkscape:window-x="0"
borderopacity="1.0"
inkscape:current-layer="svg1"
inkscape:cx="36.490405"
inkscape:cy="19.560172"
inkscape:window-width="1024"
inkscape:pageopacity="0.0"
/>
<g
id="g1113"
transform="matrix(1.8141 0 0 1.8141 -24.352 -32.241)"
>
<path
id="path1894"
style="stroke-linejoin:round;stroke:#ffffff;stroke-linecap:round;stroke-width:5.5124;fill:none"
d="m43.399 34.31c0 7.418-6.02 13.438-13.438 13.438s-13.438-6.02-13.438-13.438 6.02-13.438 13.438-13.438 13.438 6.02 13.438 13.438z"
/>
<path
id="path741"
style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:2.7562;fill:#ffffff"
d="m43.399 34.31c0 7.418-6.02 13.438-13.438 13.438s-13.438-6.02-13.438-13.438 6.02-13.438 13.438-13.438 13.438 6.02 13.438 13.438z"
/>
<path
id="path743"
sodipodi:nodetypes="cc"
style="stroke-linejoin:round;stroke:#000000;stroke-linecap:round;stroke-width:2.7562;fill:none"
d="m29.961 34.169v-8.723"
/>
<path
id="path744"
style="stroke-linejoin:round;stroke:#000000;stroke-linecap:round;stroke-width:2.7562;fill:none"
d="m30.185 34.066l5.869 3.388"
/>
<path
id="path742"
style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:1.7226;fill:#000000"
d="m31.178 34.31c0 0.672-0.545 1.217-1.217 1.217s-1.217-0.545-1.217-1.217 0.545-1.218 1.217-1.218 1.217 0.546 1.217 1.218z"
/>
</g
>
<metadata
>
<rdf:RDF
>
<cc:Work
>
<dc:format
>image/svg+xml</dc:format
>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
/>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
/>
<dc:publisher
>
<cc:Agent
rdf:about="http://openclipart.org/"
>
<dc:title
>Openclipart</dc:title
>
</cc:Agent
>
</dc:publisher
>
</cc:Work
>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/"
>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
/>
</cc:License
>
</rdf:RDF
>
</metadata
>
</svg
>

After

Width:  |  Height:  |  Size: 3.6 KiB

18
main.py
View File

@ -22,7 +22,23 @@ def main():
homepage()
ui.run(port=port, storage_secret=secret, language='de-DE')
def startup_message():
message_string = f"{app_title} {app_version}"
underline = ""
for i in range(len(message_string)):
underline += "-"
print(message_string)
print(underline)
url_string = ""
for i in list(app.urls):
url_string += f"{i}, "
url_string = url_string[0:-2]
print("Weboberfläche erreichbar unter: " + url_string)
app.on_startup(startup_message)
ui.run(favicon="favicon.svg", port=port, storage_secret=secret, language='de-DE', show_welcome_message=False)
if __name__ in ("__main__", "__mp_main__"):
main()

View File

@ -1,2 +1,2 @@
1746385124
1746385127
1746388680

View File

@ -85,6 +85,9 @@ def convert_seconds_to_hours(seconds):
remaining_seconds = remaining_seconds - minutes * 60
if remaining_seconds > 0 and sign != "-":
minutes = minutes + 1
if minutes == 60:
hours = hours + 1
minutes = 0
if hours < 10:
hours = "0" + str(hours)
else: