Ordnerstruktur sortiert

imports angespasst
Stylinganpassungen im Adminbereich (Responsive, Farben für die Feiertagsbuttons)
This commit is contained in:
Alexander Malzkuhn 2025-05-23 10:43:03 +02:00
parent e71f423c81
commit 9e8fa9ad62
27 changed files with 212 additions and 276 deletions

View File

@ -7,10 +7,10 @@ from dateutil.easter import *
from nicegui import ui, app, events
from nicegui.html import button
from users import *
from definitions import *
from lib.users import *
from lib.definitions import *
from calendar import monthrange
from web_ui import *
from lib.web_ui import *
import os.path
import os
@ -60,7 +60,7 @@ def page_admin():
ui.markdown("##Übersichten")
# Tabelle konstruieren
with ui.card():
with ui.card().classes('w-full'):
with ui.row() as timetable_header:
year_binder = ValueBinder()
@ -131,12 +131,12 @@ def page_admin():
@ui.refreshable
def timetable():
current_user = user(time_user.value)
with ui.card() as calendar_card:
with ui.card().classes('w-full') as calendar_card:
def update_month_and_year():
#current_user = user(time_user.value)
# Archivstatus
days_with_errors = current_user.archiving_validity_check(int(select_year.value), int(select_month.value))
with ui.grid(columns='auto auto auto 1fr 1fr 1fr 1fr') as table_grid:
with ui.grid(columns='auto auto auto 1fr 1fr 1fr 1fr').classes('w-full md:min-w-[600px] lg:min-w-[800px]') as table_grid:
if int(select_month.value) > 1:
archive_status = current_user.get_archive_status(int(select_year.value),
int(select_month.value))
@ -894,7 +894,7 @@ Dies kann nicht rückgängig gemacht werden!''')
with ui.row():
for entry in year_dict[year_entry]:
date_label = entry.strftime("%d.%m.")
ui.button(f"{data['holidays'][entry.strftime('%Y-%m-%d')]} ({date_label})", on_click=lambda entry=entry: del_holiday_entry(entry)).classes('text-sm')
ui.button(f"{data['holidays'][entry.strftime('%Y-%m-%d')]} ({date_label})", color='cyan-300', on_click=lambda entry=entry: del_holiday_entry(entry)).classes('text-sm')
holiday_buttons_grid()
holiday_section()

View File

@ -4,10 +4,9 @@ from logging import exception
from nicegui import *
import ui
from definitions import *
from web_ui import *
from users import *
from lib.definitions import *
from lib.web_ui import *
from lib.users import *
from datetime import datetime
import calendar

View File

@ -2,12 +2,13 @@
# Quasi-Konstanten
import os
from pathlib import Path
app_title = "Zeiterfassung"
app_version = ("0.0.0")
# Standardpfade
scriptpath = os.path.dirname(os.path.abspath(__file__))
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
userfolder = "users"
# Dateinamen

View File

@ -5,15 +5,15 @@ from nicegui import ui, app, Client
from nicegui.page import page
from users import *
from definitions import *
from lib.users import *
from lib.definitions import *
from calendar import monthrange, month_name
import hashlib
import calendar
import locale
from web_ui import *
from lib.web_ui import *
@ui.page('/')
def homepage():
@ -24,7 +24,7 @@ def homepage():
current_user = user(app.storage.user["active_user"])
except:
del(app.storage.user["active_user"])
ui.navigate.to('/')
ui.navigate.reload()
pageheader(f"Willkommen, {current_user.fullname}")
today = datetime.datetime.now()

View File

@ -1,10 +1,10 @@
from datetime import datetime
from nicegui import ui, app
from web_ui import *
from lib.web_ui import *
from users import *
from definitions import *
from lib.users import *
from lib.definitions import *
from calendar import monthrange
import hashlib

13
lib/settings.json Normal file
View File

@ -0,0 +1,13 @@
{
"admin_user": "admin",
"admin_password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
"port": "8090",
"secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
"times_on_touchscreen": true,
"photos_on_touchscreen": true,
"touchscreen": true,
"picure_height": 200,
"button_height": 300,
"user_notes": true,
"holidays": {}
}

View File

@ -2,9 +2,9 @@ from datetime import datetime
from nicegui import ui, app
from users import *
from definitions import *
from web_ui import *
from lib.users import *
from lib.definitions import *
from lib.web_ui import *
from calendar import monthrange
import hashlib

View File

@ -11,7 +11,7 @@ import json
import shutil
import re
from definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
# Benutzerklasse

View File

@ -2,8 +2,8 @@ from datetime import datetime
from nicegui import ui, app
from users import *
from definitions import *
from lib.users import *
from lib.definitions import *
from calendar import monthrange
import hashlib

BIN
photo.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

22
qr_scanner_example.py Normal file
View File

@ -0,0 +1,22 @@
import cv2
import webbrowser
cap = cv2.VideoCapture(0)
# initialize the cv2 QRCode detector
detector = cv2.QRCodeDetector()
while True:
_, img = cap.read()
# detect and decode
data, bbox, _ = detector.detectAndDecode(img)
# check if there is a QRCode in the image
if data:
a = data
break
cv2.imshow("QRCODEscanner", img)
if cv2.waitKey(1) == ord("q"):
break
b = webbrowser.open(str(a))
cap.release()
cv2.destroyAllWindows()

BIN
sounds/3beeps.mp3 Normal file

Binary file not shown.

BIN
sounds/beep.mp3 Normal file

Binary file not shown.

BIN
sounds/power-on.mp3 Normal file

Binary file not shown.

BIN
sounds/store_beep.mp3 Normal file

Binary file not shown.

BIN
sounds/success.mp3 Normal file

Binary file not shown.

BIN
sounds/ui-off.mp3 Normal file

Binary file not shown.

BIN
sounds/ui-on.mp3 Normal file

Binary file not shown.

BIN
sounds/ui-sound.mp3 Normal file

Binary file not shown.

217
ui.py
View File

@ -1,217 +0,0 @@
# Zeiterfassung
# UI Definitionen
import tkinter as tk
import locale
locale.setlocale(locale.LC_ALL, '')
from time import strftime
from definitions import app_title, app_version, status_in
from users import user as uo
from users import list_users
# Pinpad
class win_pinpad(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
def update_time():
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
nonlocal digital_clock
digital_clock.config(text=string_time)
digital_clock.after(1000, update_time)
self.title(app_title + " " + app_version)
# Digital clock label configuration
digital_clock = tk.Label(self)
digital_clock.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
# Initial call to update_time function
update_time()
# Benutzernummer
def usernr_changed(UserNr):
nonlocal usernr
if len(str(usernr.get())) > 0:
buttons["OK"].configure(state="active")
else:
buttons["OK"].configure(state="disabled")
tk.Label(self, text="Benutzernummer:").grid(row=1, column=0)
UserNr = tk.StringVar()
UserNr.trace("w", lambda name, index, mode, UserNr=UserNr: usernr_changed(UserNr))
usernr = tk.Entry(self, width=10, textvariable=UserNr)
usernr.grid(row=1,column=1)
# Pinpad
def buttonPress(key):
nonlocal usernr
if type(key) is int:
if key < 10:
usernr.insert('end', str(key))
if key =="OK":
print("OK pressed")
if key == "<-":
usernr.delete(usernr.index("end") - 1 )
if len(usernr.get()) > 0:
buttons["OK"].configure(state="active")
else:
buttons["OK"].configure(state="disabled")
# Buttons definieren
button_width = 7
button_height = 3
pinframe = tk.Frame(self)
pinframe.grid(row=2, column=0, columnspan=3, padx=10, pady=10)
buttons = { }
keys = [
[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[ "<-", 0, "OK"]
]
for y, row in enumerate(keys, 1):
for x, key in enumerate(row):
button = tk.Button(pinframe, width=button_width, height=button_height, text=key, command=lambda key=key: buttonPress(key))
button.grid(row=y, column=x)
buttons[key] = button
buttons["OK"].configure(state="disabled")
usernr.focus_set()
class win_userlist(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
def update_time():
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
nonlocal digital_clock
digital_clock.config(text=string_time)
digital_clock.after(1000, update_time)
self.title(app_title + " " + app_version)
# Digital clock label configuration
digital_clock = tk.Label(self)
digital_clock.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
# Initial call to update_time function
update_time()
tk.Label(self, text="Benutzer auswählen").grid(row=1, column=0, columnspan=2)
# Button Frame
button_frame = tk.Frame(self)
button_frame.grid(row=2, column=0, columnspan=2, padx=0, pady=10)
userlist = list_users()
# Button Dictionary
buttons = { }
button_row_index = 0
for name in userlist:
button = tk.Button(button_frame, text=name)
button.grid(row=button_row_index, column=0, pady=5, sticky="ew")
buttons[name] = button
button_row_index = button_row_index + 1
class win_stamping(tk.Toplevel):
def __init__(self, parent, user):
super().__init__(parent)
def update_time():
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
nonlocal digital_clock
digital_clock.config(text=string_time)
digital_clock.after(1000, update_time)
self.title(app_title + " " + app_version)
# Benutzer feststellen
current_user = uo(user)
# Digital clock label configuration
digital_clock = tk.Label(self)
digital_clock.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
# Initial call to update_time function
update_time()
# Benutzer anzeigen
tk.Label(self, text=current_user.fullname).grid(row=1, column=0, pady=10, columnspan=3)
todays_hours = tk.Label(self, text="Arbeitsstunden erscheinen hier")
todays_hours.grid(row=2, column=0, pady=10, columnspan=3)
in_button = tk.Button(self, text="Einstempeln", bg="green")
out_button = tk.Button(self, text="Ausstempeln", bg="red")
if current_user.stamp_status() == status_in:
in_button.configure(state="disabled")
out_button.configure(state="active")
out_button.focus_set()
else:
in_button.configure(state="active")
out_button.configure(state="disabled")
in_button.focus_set()
in_button.grid(row=3, column = 0)
out_button.grid(row=3, column=2)
button_frame = tk.Frame(self, relief="groove")
button_frame.grid(row=4, column=0, columnspan=3, pady=10)
overview_workinghours = tk.Button(button_frame, text="Übersicht Arbeitszeiten")
overview_missed = tk.Button(button_frame, text="Übersicht Fehlzeiten")
overview_data = tk.Button(button_frame, text="Stammdaten")
overview_workinghours.grid(row=0, column=0, sticky="ew")
overview_missed.grid(row=1, column=0, sticky="ew")
overview_data.grid(row=2, column=0, sticky="ew")
button_close = tk.Button(self, text="Schließen")
button_close.grid(row=5, column=1)
#========================================================
class mainwindow(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('300x200')
self.title('Main Window')
# place a button on the root window
tk.Button(self,
text='PinPad Window',
command=self.open_pinpad).pack(expand=True)
tk.Button(self,
text='Userlist Window',
command=self.open_userlist).pack(expand=True)
tk.Button(self,
text='Stamping Window',
command=self.open_stamping).pack(expand=True)
def open_pinpad(self):
window = win_pinpad(self)
window.grab_set()
def open_userlist(self):
window = win_userlist(self)
window.grab_set()
def open_stamping(self):
window = win_stamping(self, user="testuser")
window.grab_set()
if __name__ == "__main__":
app = mainwindow()
app.mainloop()

View File

@ -0,0 +1,4 @@
{
"archived": 0,
"total_hours": 0
}

View File

@ -0,0 +1,10 @@
1747642816
1747642898
1747642972
1747642976
1747643508
1747643521
1747643564
1747643566
1747643603
1747644615

View File

@ -1,8 +1,7 @@
{
"username": "filler2",
"fullname": "filler2",
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
"api_key": "0f36286bf8c96de1922ab41e2682ba5a81793525",
"password": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"workhours": {
"2025-05-16": {
"1": 0,
@ -14,5 +13,6 @@
"7": 0,
"vacation": 0
}
}
},
"api_key": "43ec918e7d773cb23ab3113d18059a83fee389ac"
}

View File

@ -2,17 +2,17 @@
"username": "filler3",
"fullname": "filler3",
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
"api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa",
"workhours": {
"2025-05-16": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"1": "6",
"2": "6",
"3": "6",
"4": "6",
"5": "6",
"6": 0,
"7": 0,
"vacation": 0
}
}
},
"api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa"
}

View File

@ -11,22 +11,13 @@
"13": "U"
},
"notes": {
"5": {
"user": "Jo, das ging echt ab.",
"admin": "Streik\n\nUnd anderes"
},
"4": {
"admin": "Testeintrag\n\nZusatzeintrag"
},
"5": {},
"4": {},
"2": {},
"1": {},
"9": {
"user": "Dieses ist ein Testeintrag.",
"admin": "Das sollte der Testuser nicht sehen"
},
"12": {
"user": "Testtext"
},
"14": {}
"9": {},
"12": {},
"14": {},
"22": {}
}
}

113
webcam_example.py Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env python3
import base64
import signal
import time
import webbrowser
import cv2
import numpy as np
from fastapi import Response
from nicegui import Client, app, core, run, ui
# In case you don't have a webcam, this will provide a black placeholder image.
black_1px = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBg+A8AAQQBAHAgZQsAAAAASUVORK5CYII='
placeholder = Response(content=base64.b64decode(black_1px.encode('ascii')), media_type='image/png')
def convert(frame: np.ndarray) -> bytes:
"""Converts a frame from OpenCV to a JPEG image.
This is a free function (not in a class or inner-function),
to allow run.cpu_bound to pickle it and send it to a separate process.
"""
_, imencode_image = cv2.imencode('.jpg', frame)
return imencode_image.tobytes()
def setup() -> None:
# OpenCV is used to access the webcam.
video_capture = cv2.VideoCapture(0)
detector = cv2.QRCodeDetector()
blocker = False
blockset = 0
@app.get('/video/frame')
# Thanks to FastAPI's `app.get` it is easy to create a web route which always provides the latest image from OpenCV.
async def grab_video_frame() -> Response:
nonlocal blocker
if time.time() - blockset > 5:
blocker = False
if not video_capture.isOpened():
return placeholder
# The `video_capture.read` call is a blocking function.
# So we run it in a separate thread (default executor) to avoid blocking the event loop.
_, frame = await run.io_bound(video_capture.read)
if frame is None:
return placeholder
# `convert` is a CPU-intensive function, so we run it in a separate process to avoid blocking the event loop and GIL.
jpeg = await run.cpu_bound(convert, frame)
# QR-Handling
def function_call():
b = webbrowser.open(str(a))
print('\a')
nonlocal blocker
nonlocal blockset
blocker = True
blockset = time.time()
if not blocker:
_, img = video_capture.read()
# detect and decode
data, bbox, _ = detector.detectAndDecode(img)
# check if there is a QRCode in the image
if data:
a = data
function_call()
# cv2.imshow("QRCODEscanner", img)
if cv2.waitKey(1) == ord("q"):
function_call()
return Response(content=jpeg, media_type='image/jpeg')
# For non-flickering image updates and automatic bandwidth adaptation an interactive image is much better than `ui.image()`.
video_image = ui.interactive_image().classes('w-full h-full')
# A timer constantly updates the source of the image.
# Because data from same paths is cached by the browser,
# we must force an update by adding the current timestamp to the source.
ui.timer(interval=0.1, callback=lambda: video_image.set_source(f'/video/frame?{time.time()}'))
async def disconnect() -> None:
"""Disconnect all clients from current running server."""
for client_id in Client.instances:
await core.sio.disconnect(client_id)
def handle_sigint(signum, frame) -> None:
# `disconnect` is async, so it must be called from the event loop; we use `ui.timer` to do so.
ui.timer(0.1, disconnect, once=True)
# Delay the default handler to allow the disconnect to complete.
ui.timer(1, lambda: signal.default_int_handler(signum, frame), once=True)
async def cleanup() -> None:
# This prevents ugly stack traces when auto-reloading on code change,
# because otherwise disconnected clients try to reconnect to the newly started server.
await disconnect()
# Release the webcam hardware so it can be used by other applications again.
video_capture.release()
app.on_shutdown(cleanup)
# We also need to disconnect clients when the app is stopped with Ctrl+C,
# because otherwise they will keep requesting images which lead to unfinished subprocesses blocking the shutdown.
signal.signal(signal.SIGINT, handle_sigint)
# All the setup is only done when the server starts. This avoids the webcam being accessed
# by the auto-reload main process (see https://github.com/zauberzeug/nicegui/discussions/2321).
app.on_startup(setup)
ui.run(port=9005)

View File

@ -1,19 +1,19 @@
#!/usr/bin/env python3
# Zeiterfassung
from web_ui import *
from admin import *
from login import *
from users import *
from touchscreen import *
from definitions import *
from api import *
from homepage import *
from lib.web_ui import *
from lib.admin import *
from lib.login import *
from lib.users import *
from lib.touchscreen import *
from lib.definitions import *
from lib.api import *
from lib.homepage import *
import json
import argparse
from web_ui import hash_password
from lib.web_ui import hash_password
class Commandline_Header: