feat: perinco Pulse - Zeiterfassung fuer perinco

Erste Fassung, live unter https://pulse.perinco.de.

Frontend React 19 + TypeScript + Tailwind 4 (Vite), Backend Express mit
mysql2 auf MariaDB, Exporte als PDF (pdfkit), Excel (exceljs) und CSV.
Aufbau, Designsystem und die sieben Themes kommen aus dem perinco CRM.

Fachlich:
- Buchung mit Wochen-, Monats- und Jahresansicht, Zeiten als 1:23 oder 1,5
- Projekte mit n Budgettoepfen: Laufzeit, Volumen, Sperre bei Ablauf oder
  aufgebrauchtem Budget, Buchung nur auf den passenden Topf
- Kunden, Team mit Hierarchie, Reports im Harvest-Stil, Monatsexporte
- Rollen user/lead/management/admin, doppelt geprueft (Menue und Backend)
- SSO gegen Microsoft Entra ID, Uebernahme der AD-Benutzer per Drag & Drop

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-17 22:52:20 +02:00
co-authored by Claude Opus 5
commit ef676a95f4
104 changed files with 21030 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 900" width="260" height="900" preserveAspectRatio="xMidYMax slice">
<!--
Outdoor, Seitenleiste: Wiese, die von unten hereinwaechst.
Der obere Teil ist absichtlich leer (durchsichtig): dort stehen die
Menuepunkte. Das Gras sitzt in den unteren rund 300 px, wo die Leiste
ohnehin frei ist, und wird nach oben hin blasser.
Gezeichnet mit quadratischen Bezierkurven statt Bildpunkten - die Halme
bleiben damit in jeder Hoehe scharf.
-->
<defs>
<linearGradient id="wiese" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#7bbf5a" stop-opacity="0" />
<stop offset="0.45" stop-color="#6fb350" stop-opacity="0.55" />
<stop offset="1" stop-color="#4e8f39" stop-opacity="0.95" />
</linearGradient>
<linearGradient id="dunst" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#a8d68f" stop-opacity="0" />
<stop offset="1" stop-color="#a8d68f" stop-opacity="0.35" />
</linearGradient>
<filter id="weich" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="14" />
</filter>
</defs>
<!-- Gruener Schein, der den Uebergang traegt -->
<rect y="520" width="260" height="380" fill="url(#dunst)" filter="url(#weich)" />
<!-- Erdreich und Wiesenflaeche -->
<path d="M0 760 C 60 742 120 754 180 744 C 216 738 240 746 260 740 L260 900 L0 900 Z" fill="url(#wiese)" />
<!-- Hintere Halmreihe: hell, duenn, hoch -->
<g stroke="#8fca6e" stroke-width="2.4" stroke-linecap="round" fill="none" opacity="0.5">
<path d="M12 772 Q 6 720 20 686" />
<path d="M34 768 Q 40 716 30 676" />
<path d="M58 774 Q 52 726 66 692" />
<path d="M84 766 Q 92 714 82 680" />
<path d="M110 772 Q 104 722 118 690" />
<path d="M136 764 Q 144 712 134 678" />
<path d="M162 770 Q 156 720 170 688" />
<path d="M190 766 Q 198 716 188 684" />
<path d="M216 772 Q 210 724 224 694" />
<path d="M242 768 Q 250 718 240 686" />
</g>
<!-- Vordere Halmreihe: dunkler, kraeftiger, kuerzer -->
<g stroke="#3f7d2e" stroke-width="3.6" stroke-linecap="round" fill="none" opacity="0.85">
<path d="M0 830 Q 8 786 24 758" />
<path d="M24 836 Q 18 792 6 764" />
<path d="M48 828 Q 58 784 74 756" />
<path d="M72 838 Q 66 794 54 766" />
<path d="M98 830 Q 108 786 124 760" />
<path d="M122 840 Q 116 796 104 768" />
<path d="M148 828 Q 158 784 174 758" />
<path d="M172 838 Q 166 794 154 764" />
<path d="M198 832 Q 208 788 224 760" />
<path d="M222 840 Q 216 796 204 768" />
<path d="M248 830 Q 258 786 260 764" />
</g>
<!-- Ein paar Blueten, damit es nicht nach Rasenteppich aussieht -->
<g opacity="0.8">
<circle cx="42" cy="742" r="4" fill="#f7e07a" />
<circle cx="128" cy="726" r="3.4" fill="#f7f3e0" />
<circle cx="206" cy="748" r="3.8" fill="#f7e07a" />
<circle cx="88" cy="800" r="3" fill="#f7f3e0" />
<circle cx="186" cy="812" r="3.4" fill="#f7e07a" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB