From 3c282a0f3cc2de2ece4d2ebd66a898f373801aa9 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Thu, 5 Feb 2026 13:27:39 +0100 Subject: [PATCH] Icons, Footer, Stundenformel --- checkin-server.js | 1 + doc/Stunderfassung todo.txt | 6 ++-- public/apple-touch-icon.png | Bin 0 -> 14447 bytes public/css/style.css | 9 +++++ public/images/{ => icons}/favicon.png | Bin public/js/dashboard.js | 49 ++++++++++++++++++++------ public/manifest.json | 4 +-- routes/verwaltung-routes.js | 26 +++++++++++--- services/pdf-service.js | 12 +++++-- views/admin.ejs | 1 + views/checkin-result.ejs | 1 + views/dashboard.ejs | 1 + views/footer.ejs | 3 ++ views/header.ejs | 12 +++---- views/login.ejs | 1 + views/overtime-breakdown.ejs | 1 + views/verwaltung.ejs | 1 + 17 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 public/apple-touch-icon.png rename public/images/{ => icons}/favicon.png (100%) create mode 100644 views/footer.ejs diff --git a/checkin-server.js b/checkin-server.js index fddc806..2a4b9d4 100644 --- a/checkin-server.js +++ b/checkin-server.js @@ -14,6 +14,7 @@ checkinApp.set('views', path.join(__dirname, 'views')); // Middleware für Check-in-Server checkinApp.use(express.json()); +checkinApp.use(express.static('public')); /** Erkennt Browser-Aufruf: Accept-Header enthält text/html (z. B. beim Aufruf per QR/Link im Browser). */ function wantsHtml(req) { diff --git a/doc/Stunderfassung todo.txt b/doc/Stunderfassung todo.txt index 64f102d..bc6d3bd 100644 --- a/doc/Stunderfassung todo.txt +++ b/doc/Stunderfassung todo.txt @@ -3,7 +3,7 @@ - Offset für die Verwaltung für Urlaubstage -> DONE - Stunden pro Tag und wie viele Tage arbeit -> DONE - Reisen für Wochenende -> DONE -- LDAP Prüfung -> DONE GEHT?! +- LDAP Prüfung -> DONE - DSGVO Sicherheit -> DONE - Feiertage müssen als ausgefüllt zählen -> DONE - Mitarbeiter sollen PDF ansehen können. -> DONE @@ -12,6 +12,6 @@ - Feiertage im PDF anzeigen -> DONE - Oben wenn woche eingereicht anzeigen als hilfestellung -> DONE -- Ausgefüllte Tage anhand der Tage pro woche gültig setzten -> DONE Testen +- Ausgefüllte Tage anhand der Tage pro woche gültig setzten -> DONE - Überstunden müssen anhand der Tagesstunden auch auf gültig setzten (Tag ausgefüllt wenn weniger als 8h) -> DONE sollte passen -- Verplante Urlaubstage müssen auf abgezogen werden, wenn die Woche die gepalnt war eingereicht wurde. -> DONE Testen \ No newline at end of file +- Verplante Urlaubstage müssen auf abgezogen werden, wenn die Woche die gepalnt war eingereicht wurde. -> DONE \ No newline at end of file diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..399db4f46740c882d2912dfff555ef5d628a3008 GIT binary patch literal 14447 zcmdUW^LM08)NO1|GO=yjwryu(+qP|MV%y2YPA2Tw=ES=F-un;SAHQ#{uI{y-b$UIg zs!pA~Pt~p{B?U=DI2Z^1EhenynR}Jzk%>3b z`V^qPg~lnnk=8<6+cL?G5+;Vi0TpH05Qo?oILM6)z&fxA!0JF+SLB_F_9gU5S>qP=s-$KYZ9`If z@PDi!lREZ|N+uvk864~x`HrLQWF~a>`z}Cq+k|Q78VXO&5^>VBkr+}U=sBU5O{Tk^ zg4%G$v44SHW+dlNRp0%jz`garEAk<|IE-Z4m_k$hB1G&qxI2+m$|RR1;$UDYjV%h2 zYeH9vFm)VrXwa8pM;0Lnywx&UC5h>7=$Fdd{5}}F&inuwVXVPv=>5yJM zm9NO9mXqv{D#y3Z(&VSj^XeJJvj*}4$0R}FA@3Pi%Dp0emqt4D)hDi+B z3$)DH8ao*3CYvSr+hfBuK1Tpd)+&~B3d0*$xhZsI1HapBWv`^)D;%I`o2pl*BSOwb#wSlE-F)#{30Or{$G zoaCplLe}~Kr$Wx?hf}L4N$Uy@HDqJy2xPx^c=uN?I0@)~5Ej@l%J0=dkRXue94#Cv>KVg=?LmGUo!2w&hnGM5cWdzsJXT8v z%_-Osto4Q?O_&~NzoQCMz5VVbz^Q6qj}0|a)U#=4Gd0|gC8ghr(iSsS97psSU2c{D zuq$E>c;by2@{N1WsS+iyf5l$*w(1=L{T@Hq#FUdQKi>p9VT(%G+MWlbiIh-D;&xMj zV`y0CiRcpePNV15|JCkBr`wxjg5bwB;Z{2L}iW>lVrWa4eD@t3* z3^%%(Y!ezyBFGdwkn0wTb<~K1P?xOX7L*RI*P?i0O zgm@Vu)NJX zpom+=v5}p%%p7_2?pv>rwD;~U&~s@+6T=biPud)q*@iBz>1(H!z*pHwTE#X@WUumg zI@;l?^f`I$SW!`n*yug$(p)&+$l9T>!1uYAq5O`W*`s&cd>jemd*6xaJS!HpiiBEd zQ&n<-y?IE$bU*q2mN2TIs}Y0hrj1Eye7K_a?pYj{3Jj>VGQFBrP?Yt#w}!zA)>=Fc z(tPr~f5Kc#TQyloHQszD*WA8f%v7{n1CjihstG1CoX2?S9g^I~s;rVCQoE)7`nCd* z^GuFfe#tqY64-=4ijd&=jET9Zgb!w9;DTs+L6ZB{PYZ9&RmRR^bi3Jxl&l>gHSeiE zivUVh#^paMHHw(9nANt{#5ni>;MMQuwk(cWdkIH3L&Lt-UK*V)x7KulU;SW0so7ed zxPC%sneUfZfk{1L@-icT_BTddR`!rOiK{Lu;`y`AStEiaA&4BgSSUZis%yFyZ&N}m z6HwXH!q&n`k@G~MtX;8tsRtLN0Lb-w!NRE$Eu{;)vTTYsnYc{C8CT zJfEJ&k?kgkS{1b<9$3-y8g;ervlIw;pGM_-m^tvev>9WvkYlGY{ki;i!;XvAVC!aL z{=mm*DW9RG3@)D$@-%j)6Mj?$pZ(!pC;r$kf`hre45?zEmmLRTVm6-b9WA|jWNP4? zieAJg;O0sW`4S$3$w^GyKWo)3qO#V| zZg3$b!ez_?Q%UpZKid~Di>@|w#`aNZZ)EGa2rCZ@xE6-Bpzmzvy&uDmSWiYQUjzcvN)jg;T zp1L=cR73ZV>~XOhvoq~1B!k8OH*^XI7!sP_WmLQ&%5q`~JxPD?uV*6Hp|+JQdiPDO zo#ziuLkj1o4z0F3;ZP5uxkvHdXri@S+Fmx)&6c=Jr$U54@c;hnPAAz(A(aph3n>IZ zil-r}KMIv2S{A_xKVeE;>%;D)YIG_#}p( z)<5>tmnYQy+ZRKcEe=6ps-gfB*Nd(5B2tPMr91ML@aCu$IG@zDWw8mB({QjqY3i>( z$VQ$Xa(4AszYmFsHy-TR%M5p=EbAoX(>K*@zHfN3_8w}9iEMX$X+CT>K~H9Kk+}IL6xY)__Z9Dea2FRbd-jepyXD z+)-_Bf-bOxU4m>}-J|AUvp{fWdlo@hG_jRkyQ1B<*G$79zQ+MmCVF0^10HplHUy8= z7>ncrp~LoqDoz`FKivOx2S$P6yHd^`ue^T-4kBloYzJ?;;KFW^a%q8V3* z$Ws%%bEs)2&?w=_uaCzam$nnq^o@i zc+0UGAwHze_xARRPREam&I|PgW%N%gQ}6CYhcI@YXsszHu_l8p-oTXH?7*T=$R%T% z2_?ZcUXZ^w8hpbyS*v(OWYm#G?jSDJ5lY-Ck5}>hjd;co)_>DLaV(D)p4SA0`0ScG z3WJ4JM8wIsGZg%-J&HefO=8pmDoc(7CP)T^o58#R2)RMTlj$-VoPnmd`j1CJ$&yqG zC2SK5i*OeQy^%2o|9G*DQ9h~d*r46Mb!ZFMA|akK_M*VUHqpJ`5s88LD6P)_z@o|;s(5#p^4#_DGLk~)LpYYf3N5;Bi{VBc!-Yc zFHNsGWX%RI!=}sqLKiA&kr3W-ZAq8?X{hV($;Q~~7$b5PQ4%+XT>&Nh6v#~ko4bQPn9N-?k!dYZdae_Le!RkkJVR)>Pt7myQ`)`! z5(6lrlL>U>sdPo4a%aijgMWAk-bbB!Y*wUdW>a(A_(Rvbuc*Klh`JB372eNeR<2^Z zLq6KpGqZ!V3P>NFR3&fM%*UMm{oT{b%j!8XVOonPQ9?EG@rhRT?@FHWNzRWm5GZA^ z5($yDo?3{-$Jpy*WZb{7m=-d(#hW5Xerm!OC$ep7XB`&r_e-z2q5#FaB%Vzp#6mDZ zVI`3~k3#&(_ix$sWpR<3tCBAM$_PPyTPiWnr~Bp4O#vekpQAZXHlu>(J>0B=zWUI z$?fwT{z&FIiaVbw+hY}_t)#I=xTmK)?YDPhZ4E_~xv=H!q@GtwMVX7l{U1)uhopni z1J6p$5Qa=k&W-D1N<)`=kHl46JpKmmq!6}ncYbOJoenqtS-zH}iaswsLY=kPtu$e3 zelj#M%a@tS+Ux!)Grux2*sI@PQ|+Cx)`+F_;-c!2IfC9)0Uv*pVk5D*&VoMMWvpE1 z4=d@R*Sv_rsACl8w=Hv^_g$_lMDk-qnx4|yD;cJ13wVMJ11=q{!GBQlHqfeC#UbJF zHCE<5g}Tij`E@HLhazEJJKHxZauM~?{rnH}8uk|tGMT$@czgz z^;bb7a$nsBp(5rCe5f7{hMI)l{ZV`~{_oguDd=9?RDu&M6(iZ-NY4bLxji?6HA$Ul z;f-&tNUmyer#JSn5HL`3;o@LhhTe8gEu$J$4bwP3s5wDOEBxwObbTM^@7QCH?x5n7AIH)s`rTkqqdPvTlcw|BrT(T9G=pmT~ji|L2c6mPbOl1 zyF*PJ?5{e6P^JGAx=siadH05@?RtT0brR18ZSJWz)=Jx`@MLkPpOmnw{y#*{rQ6A+ z?C=3L2w_#{hv-#Le;3}SaT`OT0cEyTaV!H=*A#DlH(fMXrIWPVE5>@LB_kW3Wc({? zwb`^2er1t?w=I1m^f~iCC~Pa?z3!<3GU4;V*E%x?5#obqH(*Pi?&cf&>6C58Qk=Xa zi6|`Ob9Ql!A4mKQjc~tZByG&*jbub2Z%273)7-{&qT|idGu&P8XZmXoR0DiLQKH7?jVra?Psi)&6h!1Q!pR@8H*`rI?SbiLCL~tr&a&W&#Him{IpS0oa zP6g>fP||#b->sPtQDx^5TIYb`VGoRES-;w;vnQFs^qVHU)wMz zXyS|`{f#R{V8%D(EUSKri2i-3N)`BjRLX#*Ba@fzAxgQUggD9(>&meV>B3@e*iOuq7>WVBywo?lPf zZo=u)coT}(0HGp?nq2(c<=fW&p7Hi;|FA$}=|8MSGG@wNgN=CU)4wUrWZLI_u>YP= zVW)t1EhWWgGR1n)yPZx8O?87clSr*BkS%gZPz9ngMQac_v)2rc#S=Uewkl`;AT=VA zj|%;`|0l$9mO65h;7~+i-R0AJ$d6ao^WQ$gUuO2-z$n#og8aoMHTraj2%T82%w#-} z7h(;T1cJ%ndPEP-08TDpdD>RO$ZN&2(FUD6&NUe~kYW()!Yo*0C6z0djf~G74v7|F zT+P`hAEGOyf6|_W1p}&@9A}}$)EhL{?^6ueCU~A%?@TH@TqI9f(Do6^FfsIsQRihx zj6$1p-Ya&RwyPV&F=0}rEt5glxi)a!ddNc;{<8i$Hs{e ze*so9EL|Lz%fA>L<2^n(-zKbl&AXQ$uTn?eF=F?UWaG_0A|osG;W-UF(^MTgPnF{H zwH4b)!+c5aWrR8M%gc&_`vcrTfmHRC{K;8Z+TntN+^i|OeycAWH{EV@f(iy(YROUG z7;;uM@W66>t?(!148e%o3E@y_xaDA!(d?hjgU-C%$KIbxs5l~9bX&bB$?v){VoEGP z{>qFN;WDUvz~$6-v5aL2UE|x`+Wpg*&+cJauzs(6tSzus-z#qa-g-08zsbI3=j;Ku z^SZ|i$U>;Q@f!+5VG#)`w>td4dS5{V4co$?SFVgtg3z@TZkE~Od^Ees^??*O85V?1N9>(k9r@Wkh%=v32oGZf1}!V}F_Yw)H<< zTO@3hISk_ZsJui@3i+P3b`URP25X50Tt?`EZVLNC3B*Z8c1IkM-uR)jsGjjo z2Ir^3uemRF+U#hfX1Uni+}POJK^Yo%sQ;YtYBPon3GxGKjHui!wpsq!=9FxIEQ?;1 zMY`=YGqrm=J2#m7of?atDCm6-J@}r2-<EB7VZS!sRtwmqU*7&@>5wIedUMEH@WE9E}VCafkk>8UF`2L{uxVQk_pcrh; zC_}Mb?+J@x=t=ykSaT>O8S6?hwf*Ey;Q4|o@U)Q7R02AS(=&9vio=;2J}PcJ`YOu=fiq5AtceI-5HkXkStAYd!j>EugJr^%HJo5o$Go z8sg(1PW{-R+@H z4^@U9j-DC*hpqIT0;`lYjIF^Ida4b&^~z;+Qh$ey%!KOsmgFF`f?rP5BwM1-FRYK& zw`dc*sL>W0pn(;F-gp6^EGLFWfSnQcS&-nLn+h;gQxFz!2|hok2^Quw=0HdI$JPGw z=`Ddw!+6PIgoD-e(&p1?)Au~h`ueXzaM-FM&!ELm1cru&=6BV(eU8NQMr}Lg^rkcj zE~g!Y(cyTK-bY%!NjB8MSo`fj2y)YR;0*knpyJ$t4+le<0;rJ`IO|%Tc z!}nJpw@S;6gT=*4?b@WVttwiHL;}HlT~iZwX(|k;28V8osPpRTz?-H4@d|cDG09}h z+0s_WF6c3&#>!A5eXLNR5z)Z0e2(Q-bA;2(%&XT(`b>_MosFs90~byX4!nUPks}`N z5tl&@dzNc-^yDNcU~?VYVk&*ctQiCGkra#5-`Bsg-?2ZD6W-9^ndMFlb6Gu2E=K@A z6iZ2?486?`?RNC~tfGJd3xUOARgVNIp^2+&J#~lm3i-=&S)`~2zBPV@s322L?AsAzqrwaU)#EZY2X+{!0TR<&utq1 z^<1T{V^a$NAfG-LJR0!(tk8WPG5QPJ)V5wxT~XEIm$e>I)uyf(_P&_-9im2DAoqRy zb(N5ox2|{}jLLgYNK>AZA_>ZUU#IJ~tms(K^9Ixb|JJ&~`F)=8b}Nd!U^*JRATJ;1 zeO;sd_I6RXTs7C*lnylTer+=M{@UdB-e@`Ve*1Uky`8)MmfwAS?(vzcSKsr|~Lx&rWUOn7Cmyw^|lZO+)*IRkN z0o$20c>EblFH7-`ue*SqQKD30^A1Ou;fERL}L zzn9-o%@cMetmp(Brxg5sIG$cGd>cA>dlap+v$OO0mlQ!*hl_Lmm{9*3w4!qX`0}m( z&wIbkg_wUSihK(f1S}P(AmwuTq2}6a@xvK?FOc~LtWy3M#V_>IB`b#4b4{(U zKQh5ZC=vM1RB35xe68m@dd?H%rHfV$+8q_)9^YGS7K8wg%l*E0!o!V=y5U49&?T~z z*4If*#=UdG50GI56ok-75AxSLMSqdvYIxB}`<%t@{jbk=X;*S41aL|2OMAPKHXlto z6&YikWX$}4d|8H99Dw_EvC}`x)9$W4` zsZj1_3si(~&)X!)moJb0gY;ec^x$%I^|Z9j_j;es@4<*j4KmoLQ{9wCt zT0=#xATg4OR! zi@$1`s+yXTWI8%jr9ztxmu3}jm z@WYI;UR_F2dV1{f8S4YmEXjt%F%8CdZaPBdeBYMDN|aZCPcsbk4vw#XHavrYrbI_m zRmz3a)^|mBe|cf1P$-Dm$gebYTsBR0JYRQb2?j81;BNNX*%^d;Fcd48@J2fr_;Uk8 zo#y?yeM!l1uHfqh`uyUe_ci8}{3HaKPlzVPSj&!nRo{c(W77#*lE9y9P$@Hlz(3M< zWr(fo0n&wHa=X{Z>#6euCPJj=1_%%*Ti=I&RTYWR;qE@*8RxgRw|h@K2w{lFf6+_C z;f?fP_h!)c`*FK-$TFdry|>wkk8QEZ)6mk4o3Z+y=DK+920?ROCcaYDMtP7&jhMhF zuZdzKKt+j>K!Ar}b~)jRtE-a-^jari-zh9gp@DKt7AK!E^hN?=+m*3P1c zsHkZD;8YO_j<}38-S$LRt4-za-@85EZ%n|@C0Ht#pYm`=(yv^tuW_G$a!(bH1ccpZ|b1&k9zh!P|5Upm?XC@?OjQ%dV=c-tH(F)qa51~0i?m}Wp8j%UUO9?kxk z=82o9sU*vkaovxTX7c#)z+>Xei?c=B0l*bbTUWTY0sE<{3g)&htdjIR*>XHro%2uv z9#jOBWj~WFLmw|z{u@XmC@|mahk}yEN?3rq_xkp0ng}(7_SCPp>8wq6z?I(Dci)J< z$Bz6~gE3!|EjJ`LEV=zPjwH*}9DjdO`M9{2*0vu=5z-Z`W^^_KvwwAf8Kc2&HO^cHoDBjDH1$A0 z=~~R8Fb(Dz(Gt+s9T2l(#)3VRw4F0~QkQgjK7@dUcN%#&Qy-A$$Tj`ISXbUv6)i_0 zPmwmwj^v$*3cUfC_|*(U-TSqi>$o2U>>+l!F-uitrDZ_y`>r^Na;l116Lve+abdL} zSj#9ILaA{V63AX}7&N!b?|B(CSjmp(An4P`kt{(EB*Kq*Zi9Pq2W{_9Ml8oy{hj#% zM8R*!oxTremNRrWH#fe1Z}wF~AG^WT=7SF0tLc1x+`v3ukA*HDP9VO7iU=Ri<}LN( zh7Kr4kB&;zbphF0cqu8t$zr|Xs3v1yW}O)3EQq3mA{b)JzuHvi=K&7+FaHz4mj%Uu z$pVLO+9q-1T*>w zN3NmxyzaFgE0;bzQN_&82~`dor{$yXgeyQhv;8L5*ZV!~%i5UW;COhF?_+Ykvbz4v z0c`{+vY?N_@(mR^hNwksD~gj)y{Pv#h%XMPjRIY*JJ^H*6RVz zd2Bu_wh{=0!gLr13lRjx)wC09%%<3Y&D?~|Db7WoCLNFV`yrP(=DxBpz_&NAB%_{` zy82I#&j)+^{wqdc&ID%+L@ecm?YF4qr*Q1oOqmI>mAd#rI2}+4{mZFjS;M7vV;VKOhP%thGZ{tkoHbVXPZ$gGNI1tPqirW=S(U zTP)La+>=ZM5zT9cPZ)L0z$==1aJZE8&ez*s!~R6`dJN^nKKcl1`b``w``ZDm#E zuYae?Y_Ybd&*hhlHqYhaoi|V_RNU)61e9jq0yLq0&v>UDf!IV@a!tFxODwHzNo)Wg z4|Ux=JKX?EmpI@o@qYVSS655Bx;ZT|2F!F4elgmAjL4JyfE5V>Dfl?7*lLE#B%dw_ zh&)-<5F~WB^G`hRHG4a%=*KM~u*>ssRpI9vGd0Etvqvk&PyL9F=yy*rk z0psj+xnv%y4Zp)92t83s#)3mqe@Ie~wzu5Y9bl$Jj&(lwy>s1_rgVIb;sU9{bf!RF zReDYE5^`4&22trh2b&(>el@!7voW}L`)=Ouhq2hK9k1y*uV;$>L)Ew($rGipVMB7c z^Fx?@w~+CC)OZvCdwP@whG|45vpLIwt`3|f;VLPxcGE;f z68AJYlKFAR^++22P&YDm_vqjoBE_}IxJ~rjFq6rgqN>21mY(heA-4+7W`3@wAnTc?@;x3bu}?g+>?#utoUn@%j{9>jP6>86Zj)3ZloB=CtIyb^g>+R zrsqMNp5Np0t5!TL{QA4}JSQ z?=s+%u?G+!xSu*kTI8~)Td-LdgxG!@y?FHId_r$n5pebu$aM->+u{r_rYKUN!zpWO zLJx$dV<_++nMuUsWCct;g{?Tgph|iQJPpI8qc3#CFvcVZ=3CkMWHLFa^bzvzJEfMa z_js|g{+d3u*PH_hD0B4>xZLXYNRlg$6f0)qh=+ngB3ck)DDekY>Hl$J$H@uGhnyS- z0wyYnXP8}k8yj)jnaH=K%;SL|QKlB6fGyI*p62EhPm5g3mf$Yq)`0BnhP@ZgYs`zJYHoEWQ?}fzIoI{n)Ux>; zVDqRVwJnN;1iww9q;31J>G-^(_Qi#0#kPlZ9)^44Q(YF*y8BZ9Wy7YCvDR(xQ{s^{ z8x&={+GCu%G+MTSURy|_NOBvN2!Z;m8J-2u8d;CvxEmxLLt!FMU&L}?u5=k$A`Ea7 z{Py{-h8U)h32{bePXbx>b>vV_06&K>Ldx$!qbQ-Gh z_CN{|lCey%c-^?Nkut-d3xkfY#WKJ?%toFi#$nq|24i*LkV&8K6y9oLoWT5k0sHnX zT2_|@J)2eFPw<@#wtG*pwUNJ_vv7AFF3EX$l|(Y0&CD)Vf8U|ARJ5_*&e*R{unUVe zx=s@=rTF8q!)YURtfWv<=UbcC&)@d5N@R-`>5gtpj&Q{1*K#-Rnh!i0wXD-r75?wi z-F6;sXD!4K| zPsCc0Q9HH1XH#dm`?W^edZmMxjZ+t|{~*VuO`EuxH>POUh+#-}=X3$$NwiQ+=7QJ= zKd&CN+;2Xc5!b&-9Vu1A9jlK{1j;Y@C!7yxH%)_IO*fCO`j*f=9%Y|jU2h!L-UaNz z;pN>@uYKYOa#k^gicI0<(Pgp8D~j-IfycnO1Y6TFIUD~eN-uM(QPftB!6oDOYDBYP zAQ97hdopk{9E4?)=9=L(BeYtL36X)1%9sF+d z8)+g4xyt&(#Jg6h8yr#Qq*Ql8d&y3Y`0|E`5Yikw{P}933d@ z>}xS&5#+AmjkueBEy-HfnxS;ifh^D2wZ;b9$M*j|)zju7hW~?_b!nT2fSvP!; zm>f%KPN$PPUV<%l)H>(1J-#B0O|`T$eevj_;6^qyBosR@?>zp`ha4ziq?Sa%_5*4W zWu50w>{>F7)ub~w1RD^`#?y`-G8v1vYj3@DpJ!+_AEPFtaa5^gU|7N6(mAU20ny-% z2*1QZNZ0IA1l#-tDL-zRuRC8X&{BQU{NtB>&CYBN&~jM>>b|7PEk@ahF{kIlyip z5oU@0sjigbiYj9|976Jl_FqR*1d%nV?|1$q5kxUX@a)E#43T_RyQ!$U@x~K*=k)^p z$4f{vT$?bAFv$Tq6c!k7QdnXX+vWuXZ7`XNHpAPM3*U3?K@jm6Q1 zz;a}0pj4r4RIaG>4{t?2M4gi;Dv;E2FHbTDMx1h^i#~=vkDGAcB9W_3LWsKKI)og( z*X=VkNx9UV$K`iY5Na%Sd=nYJ)aCB|Kgk;T4LUZ{jvxT{->}lhEeI9|ePrE8rAeS! z1s_p?gUtXW1gac#up_GY9FE|hkIb&z-o4XBs53Dw_G|8AhD zAtsx$)C>nj4Y{+ONQ?R2b)YFfUYty@89ygkAPm zy&QdbVV(S!VXC3Uc%t*wNEenYKfvFz|S z(Mh$5Op!Tl8(`nHX!bqUnbrb z@d_C6+Fw6L{>#T*H?|W7{g*4pR+pwsOFvUAYlGW;^4B(Frk@ zdcv2wdkO!XzYTlc| zvE{v_q=Exf8RUTa?!83O=`Ljxs+O^_pzjX0+Tl4bm*BBGaV->@eO^P%*;+%Znq*3B zJABRt=WxBR3)YtNr{(fE5Sl(lH%jjjcmMg&p@$6Y2(;&t`5lR>DC=K&gU3jfP@| z*dCRDTTa_MZ~n#XFP2)PuQP(NBa=!|1Zhbh87_DI@uBQ{65?W6gJ43*suhK>?iucC zk?m>`EoYI9k{yH5-tduJV)YxLkRG?i+2-^y(0w&qS*EjtS}BTlauHgEj)%yi7DsMD z<%aGgIA-1{57F(z4W~nwY6amUmfx8RR@ua3wbNsOVjX z+GvqU$@-z!ZpkI)T}f~3+e7*~ZuzBwrc991qKY&KCK=oKu-pi!#{Y7)%Pil3slSYO znLF3uxI6=cP?gkheA`@*!&6V1D_-r$w$$~(^x4O*CrX%Y_p;SbeXCNczt_Q@GXBgD z^@zLvf|nX>SpaggEE#NadCOCzI0PgUHJ$iwHTx&-I!U_EMCWUyQRWfA;Iq;}$XhB3DnP|LWm6^~)!CcksbM?my2IUa#u&XckvJdxcfm zAG4Y|l0rY6H;=KWu6)9*G=KU5R-XmJHuPGq{5I5)P`n%HBY|=V;?%>^`G^FQ{|&d< e{(pS@2=KZ1vi2swHUeHG1d$e35UUe03jRM${aPdd literal 0 HcmV?d00001 diff --git a/public/css/style.css b/public/css/style.css index bb4abe3..d44ae62 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1072,4 +1072,13 @@ table input[type="text"] { flex-direction: column; align-items: flex-start; } +} + +/* App Footer */ +.app-footer { + text-align: center; + padding: 16px; + color: #888; + font-size: 13px; + margin-top: 24px; } \ No newline at end of file diff --git a/public/images/favicon.png b/public/images/icons/favicon.png similarity index 100% rename from public/images/favicon.png rename to public/images/icons/favicon.png diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 6d5e6bf..3ad3cdb 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -440,12 +440,17 @@ function renderWeek() { // Stunden zur Summe hinzufügen // Bei ganztägigem Urlaub oder Krank sollten es bereits 8 Stunden sein (vom Backend gesetzt) - // Feiertag: 8h Basis + gearbeitete Stunden (jede gearbeitete Stunde = Überstunde) + // Feiertag Werktag: 8h Basis + gearbeitete Stunden (jede gearbeitete Stunde = Überstunde) + // Feiertag am Wochenende: keine Tagesarbeitsstunden, nur gearbeitete Stunden (z. B. Reise) // Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt // Wochenend-Prozentsätze: Nur auf tatsächlich gearbeitete Stunden anwenden (nicht auf Urlaub, Krankheit, Feiertage) let hoursToAdd = 0; if (isHoliday) { - hoursToAdd = fullDayHours + (hours || 0); // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden) + if (isWeekend) { + hoursToAdd = hours || 0; // Feiertag am Wochenende: keine Tagesarbeitsstunden + } else { + hoursToAdd = fullDayHours + (hours || 0); // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden) + } } else { hoursToAdd = hours || 0; // Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) @@ -488,6 +493,9 @@ function renderWeek() { } } else if (isSick) { hoursDisplay = fullDayHours.toFixed(2) + ' h (Krank)'; + } else if (isHoliday && isWeekend) { + // Feiertag am Wochenende: keine Tagesarbeitsstunden + hoursDisplay = (hours ? hours.toFixed(2) : '0') + ' h (Feiertag)'; } else if (isHoliday && !hours) { hoursDisplay = fullDayHours.toFixed(2) + ' h (Feiertag)'; } else if (isHoliday && hours) { @@ -818,7 +826,9 @@ function updateOvertimeDisplay() { } else if (sickStatus) { totalHours += fullDayHours; // Krank = (Wochenarbeitszeit / Arbeitstage) Stunden } else if (currentHolidayDates.has(dateStr)) { - // Feiertag: (Wochenarbeitszeit / Arbeitstage) Basis + gearbeitete Stunden (jede Stunde = Überstunde) + // Feiertag: Werktag = Basis + gearbeitete Stunden; Wochenende = nur gearbeitete Stunden (keine Tagesarbeitsstunden) + const dayOfWeek = date.getDay(); + const isWeekendHoliday = (dayOfWeek === 6 || dayOfWeek === 0); const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); const startTime = startInput ? startInput.value : ''; @@ -833,7 +843,11 @@ function updateOvertimeDisplay() { } else if (currentEntries[dateStr]?.total_hours) { worked = parseFloat(currentEntries[dateStr].total_hours) || 0; } - totalHours += fullDayHours + worked; // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden) + if (isWeekendHoliday) { + totalHours += worked; // Feiertag am Wochenende: keine Tagesarbeitsstunden + } else { + totalHours += fullDayHours + worked; // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden) + } } else { // Wenn 8 Überstunden (ganzer Tag) eingetragen sind, zählt der Tag als 0 Stunden if (isFullDayOvertime) { @@ -922,7 +936,8 @@ function updateOvertimeDisplay() { const totalHoursWithVacation = totalHours + vacationHours; const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours); // overtimeHours = Überstunden diese Woche (wie im Backend berechnet) - const overtimeHours = totalHoursWithVacation - adjustedSollStunden; + // Genommene Überstunden werden abgezogen, um die Netto-Überstunden zu erhalten + const overtimeHours = totalHoursWithVacation - adjustedSollStunden - overtimeTaken; // Überstunden-Anzeige aktualisieren const overtimeSummaryItem = document.getElementById('overtimeSummaryItem'); @@ -1206,8 +1221,12 @@ async function saveEntry(input) { currentEntries[date].total_hours = totalHours; } else { // Zurück zu normaler Anzeige basierend auf anderen Status + const d = new Date(date); + const isWeekendHoliday = isHoliday && (d.getDay() === 6 || d.getDay() === 0); if (isSick) { hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Krank)'; + } else if (isWeekendHoliday) { + hoursElement.textContent = (hours ? hours.toFixed(2) : '0') + ' h (Feiertag)'; } else if (isHoliday && !hours) { hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Feiertag)'; } else if (isHoliday && hours) { @@ -1369,11 +1388,17 @@ async function saveEntry(input) { currentEntries[date].total_hours = totalHours; } else if (isSick) { hoursText = fullDayHours.toFixed(2) + ' h (Krank)'; - } else if (isHoliday && result.total_hours <= fullDayHours) { - hoursText = fullDayHours.toFixed(2) + ' h (Feiertag)'; - } else if (isHoliday && result.total_hours > fullDayHours) { - const overtime = result.total_hours - fullDayHours; - hoursText = fullDayHours.toFixed(2) + ' + ' + overtime.toFixed(2) + ' h (Überst.)'; + } else if (isHoliday) { + const d = new Date(date); + const isWeekendHoliday = (d.getDay() === 6 || d.getDay() === 0); + if (isWeekendHoliday) { + hoursText = (result.total_hours || 0).toFixed(2) + ' h (Feiertag)'; + } else if (result.total_hours <= fullDayHours) { + hoursText = fullDayHours.toFixed(2) + ' h (Feiertag)'; + } else { + const overtime = result.total_hours - fullDayHours; + hoursText = fullDayHours.toFixed(2) + ' + ' + overtime.toFixed(2) + ' h (Überst.)'; + } } hoursElement.textContent = hoursText; @@ -1973,8 +1998,12 @@ function toggleSickStatus(dateStr) { currentEntries[dateStr].total_hours = fullDayHours; } else { // Zurück zu normaler Anzeige basierend auf anderen Status + const d = new Date(dateStr); + const isWeekendHoliday = isHoliday && (d.getDay() === 6 || d.getDay() === 0); if (isFullDayVacation) { hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Urlaub)'; + } else if (isWeekendHoliday) { + hoursElement.textContent = (hours ? hours.toFixed(2) : '0') + ' h (Feiertag)'; } else if (isHoliday && !hours) { hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Feiertag)'; } else if (isHoliday && hours) { diff --git a/public/manifest.json b/public/manifest.json index 5dd6ba7..458952a 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -7,12 +7,12 @@ "theme_color": "#0a5ea8", "icons": [ { - "src": "/public/images/icons/icon-192x192.png", + "src": "/images/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/public/images/icons/icon-512x512.png", + "src": "/images/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/routes/verwaltung-routes.js b/routes/verwaltung-routes.js index 0a53a7d..670fd93 100644 --- a/routes/verwaltung-routes.js +++ b/routes/verwaltung-routes.js @@ -300,17 +300,33 @@ function registerVerwaltungRoutes(app) { } function processCurrentWeek(totalVacationDays) { - // Einträge für die Woche abrufen - db.all(`SELECT date, total_hours, overtime_taken_hours, vacation_type, sick_status + // Einträge für die Woche abrufen (id/updated_at für neuesten pro Tag) + db.all(`SELECT id, date, updated_at, total_hours, overtime_taken_hours, vacation_type, sick_status FROM timesheet_entries WHERE user_id = ? AND date >= ? AND date <= ? - ORDER BY date`, + ORDER BY date, updated_at DESC, id DESC`, [userId, week_start, week_end], - (err, entries) => { + (err, allEntries) => { if (err) { return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' }); } + // Nur neuesten Eintrag pro Tag zählen (wie PDF/Submit), sonst Doppelzählung bei Duplikaten + const entriesByDate = {}; + (allEntries || []).forEach(entry => { + const existing = entriesByDate[entry.date]; + if (!existing) { + entriesByDate[entry.date] = entry; + } else { + const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0; + const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0; + if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) { + entriesByDate[entry.date] = entry; + } + } + }); + const entries = Object.values(entriesByDate); + // Berechnungen let totalHours = 0; let overtimeTaken = 0; @@ -320,7 +336,7 @@ function registerVerwaltungRoutes(app) { entries.forEach(entry => { if (entry.overtime_taken_hours) { - overtimeTaken += entry.overtime_taken_hours; + overtimeTaken += parseFloat(entry.overtime_taken_hours) || 0; } // Krankheitstage zählen diff --git a/services/pdf-service.js b/services/pdf-service.js index 2383538..ca12c6e 100644 --- a/services/pdf-service.js +++ b/services/pdf-service.js @@ -187,7 +187,11 @@ function generatePDF(timesheetId, req, res) { if (row.type === 'holiday') { y = doc.y; x = 50; - const rowData = [formatDate(row.date), '-', '-', '-', fullDayHours.toFixed(2) + ' h (Feiertag)']; + // Feiertag am Wochenende: keine Tagesarbeitsstunden anzeigen + const holidayDay = new Date(row.date + 'T12:00:00').getDay(); + const isWeekendHoliday = (holidayDay === 0 || holidayDay === 6); + const holidayHoursStr = isWeekendHoliday ? '0 h (Feiertag)' : fullDayHours.toFixed(2) + ' h (Feiertag)'; + const rowData = [formatDate(row.date), '-', '-', '-', holidayHoursStr]; rowData.forEach((data, i) => { doc.text(data, x, y, { width: colWidths[i], align: 'left' }); x += colWidths[i]; @@ -455,7 +459,11 @@ function generatePDFToBuffer(timesheetId, req) { if (row.type === 'holiday') { y = doc.y; x = 50; - const rowDataBuf = [formatDate(row.date), '-', '-', '-', fullDayHoursBuf.toFixed(2) + ' h (Feiertag)']; + // Feiertag am Wochenende: keine Tagesarbeitsstunden anzeigen + const holidayDay = new Date(row.date + 'T12:00:00').getDay(); + const isWeekendHoliday = (holidayDay === 0 || holidayDay === 6); + const holidayHoursStr = isWeekendHoliday ? '0 h (Feiertag)' : fullDayHoursBuf.toFixed(2) + ' h (Feiertag)'; + const rowDataBuf = [formatDate(row.date), '-', '-', '-', holidayHoursStr]; rowDataBuf.forEach((data, i) => { doc.text(data, x, y, { width: colWidths[i], align: 'left' }); x += colWidths[i]; diff --git a/views/admin.ejs b/views/admin.ejs index f85bb4f..2a1761b 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -484,5 +484,6 @@ } }); + <%- include('footer') %> diff --git a/views/checkin-result.ejs b/views/checkin-result.ejs index 1e0ae43..5ac5a82 100644 --- a/views/checkin-result.ejs +++ b/views/checkin-result.ejs @@ -53,5 +53,6 @@

<%= title %>

<%= message %>

+ <%- include('footer') %> diff --git a/views/dashboard.ejs b/views/dashboard.ejs index b3d716b..d2cb997 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -507,5 +507,6 @@ } } + <%- include('footer') %> diff --git a/views/footer.ejs b/views/footer.ejs new file mode 100644 index 0000000..ec8afa7 --- /dev/null +++ b/views/footer.ejs @@ -0,0 +1,3 @@ +
+ Made with ❤️ by Carsten Graf +
diff --git a/views/header.ejs b/views/header.ejs index 28b2ece..0213b6d 100644 --- a/views/header.ejs +++ b/views/header.ejs @@ -2,14 +2,14 @@ - - - - + + + + - - + + diff --git a/views/login.ejs b/views/login.ejs index a32deaa..093e140 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -93,5 +93,6 @@ + <%- include('footer') %> diff --git a/views/overtime-breakdown.ejs b/views/overtime-breakdown.ejs index 9c6bb5f..3146fec 100644 --- a/views/overtime-breakdown.ejs +++ b/views/overtime-breakdown.ejs @@ -309,5 +309,6 @@ loadOvertimeBreakdown(); }); + <%- include('footer') %> diff --git a/views/verwaltung.ejs b/views/verwaltung.ejs index ff92263..f13811b 100644 --- a/views/verwaltung.ejs +++ b/views/verwaltung.ejs @@ -867,5 +867,6 @@ } }); + <%- include('footer') %>