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
+93
View File
@@ -0,0 +1,93 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 80" width="1600" height="80" preserveAspectRatio="xMidYMid slice">
<!--
Cyberpunk, obere Leiste: Neonroehren hinter Regen.
Die Roehren sind doppelt gezeichnet - einmal breit und unscharf (der Schein),
einmal duenn und hell (das Glas). Genau daraus besteht der Neoneffekt; eine
einzelne farbige Linie sieht nur nach Linie aus.
Der Regen faellt schraeg und ist bewusst blass: er soll Bewegung andeuten,
nicht die Schrift der Leiste stoeren. Die Deckung darueber kommt aus dem
Theme (index.css), damit dieselbe Grafik im hellen Modus blass bleibt.
-->
<defs>
<filter id="schein" x="-60%" y="-200%" width="220%" height="500%">
<feGaussianBlur stdDeviation="7" />
</filter>
<filter id="weiterSchein" x="-80%" y="-300%" width="260%" height="700%">
<feGaussianBlur stdDeviation="18" />
</filter>
</defs>
<!--
Bewusst KEINE deckende Nachtflaeche: die Grafik ist durchsichtig und zeigt
nur Licht. Ueber der dunklen Flaeche des Dunkelmodus ergibt das leuchtendes
Neon, ueber der weissen des hellen Modus blasse farbige Linien. Mit einer
dunklen Grundflaeche wurde daraus im hellen Modus ein grauer Schmutzstreifen.
-->
<!-- Ferne Fensterreihen: die Stadt im Hintergrund -->
<g fill="#4a6fa8" opacity="0.35">
<rect x="120" y="18" width="3" height="5" /><rect x="130" y="30" width="3" height="4" />
<rect x="148" y="12" width="3" height="6" /><rect x="160" y="26" width="3" height="5" />
<rect x="420" y="22" width="3" height="5" /><rect x="432" y="14" width="3" height="4" />
<rect x="446" y="34" width="3" height="5" /><rect x="458" y="20" width="3" height="6" />
<rect x="980" y="16" width="3" height="5" /><rect x="992" y="28" width="3" height="4" />
<rect x="1006" y="20" width="3" height="6" /><rect x="1018" y="34" width="3" height="4" />
<rect x="1320" y="24" width="3" height="5" /><rect x="1332" y="14" width="3" height="6" />
<rect x="1346" y="30" width="3" height="4" />
</g>
<!-- Waagerechte Neonroehren -->
<g>
<!-- Magenta, lang -->
<rect x="180" y="26" width="300" height="8" fill="#c026d3" opacity="0.5" filter="url(#weiterSchein)" />
<rect x="180" y="28" width="300" height="4" fill="#e879f9" opacity="0.85" filter="url(#schein)" />
<rect x="180" y="29" width="300" height="2" fill="#fbe3ff" opacity="0.95" />
<!-- Cyan, kurz -->
<rect x="620" y="50" width="180" height="8" fill="#0891b2" opacity="0.5" filter="url(#weiterSchein)" />
<rect x="620" y="52" width="180" height="4" fill="#22d3ee" opacity="0.85" filter="url(#schein)" />
<rect x="620" y="53" width="180" height="2" fill="#d6feff" opacity="0.95" />
<!-- Violett, mittig -->
<rect x="900" y="20" width="240" height="8" fill="#7326d6" opacity="0.5" filter="url(#weiterSchein)" />
<rect x="900" y="22" width="240" height="4" fill="#a24cff" opacity="0.85" filter="url(#schein)" />
<rect x="900" y="23" width="240" height="2" fill="#ecdcff" opacity="0.95" />
<!-- Cyan, rechts -->
<rect x="1260" y="46" width="220" height="8" fill="#0891b2" opacity="0.45" filter="url(#weiterSchein)" />
<rect x="1260" y="48" width="220" height="4" fill="#22d3ee" opacity="0.8" filter="url(#schein)" />
<rect x="1260" y="49" width="220" height="2" fill="#d6feff" opacity="0.9" />
</g>
<!-- Senkrechte Roehren: setzen Takt in die Breite -->
<g>
<rect x="540" y="0" width="6" height="80" fill="#c026d3" opacity="0.35" filter="url(#weiterSchein)" />
<rect x="542" y="0" width="2" height="80" fill="#f0a6ff" opacity="0.7" filter="url(#schein)" />
<rect x="1180" y="0" width="6" height="80" fill="#0891b2" opacity="0.35" filter="url(#weiterSchein)" />
<rect x="1182" y="0" width="2" height="80" fill="#7ce9f7" opacity="0.7" filter="url(#schein)" />
</g>
<!-- Regen: schraege Striche, unterschiedlich lang und blass -->
<g stroke="#a5c8ff" stroke-linecap="round">
<g stroke-width="1.2" opacity="0.3">
<path d="M40 -6 l-8 26" /><path d="M96 4 l-8 26" /><path d="M152 -8 l-8 26" />
<path d="M214 10 l-8 26" /><path d="M268 -4 l-8 26" /><path d="M330 16 l-8 26" />
<path d="M392 2 l-8 26" /><path d="M452 20 l-8 26" /><path d="M512 -2 l-8 26" />
<path d="M574 14 l-8 26" /><path d="M636 4 l-8 26" /><path d="M698 22 l-8 26" />
<path d="M760 -6 l-8 26" /><path d="M822 12 l-8 26" /><path d="M884 0 l-8 26" />
<path d="M946 18 l-8 26" /><path d="M1008 6 l-8 26" /><path d="M1070 24 l-8 26" />
<path d="M1132 -4 l-8 26" /><path d="M1194 14 l-8 26" /><path d="M1256 2 l-8 26" />
<path d="M1318 20 l-8 26" /><path d="M1380 8 l-8 26" /><path d="M1442 26 l-8 26" />
<path d="M1504 -2 l-8 26" /><path d="M1566 16 l-8 26" />
</g>
<g stroke-width="1.6" opacity="0.22">
<path d="M70 34 l-10 34" /><path d="M186 40 l-10 34" /><path d="M302 30 l-10 34" />
<path d="M418 44 l-10 34" /><path d="M534 36 l-10 34" /><path d="M650 46 l-10 34" />
<path d="M766 32 l-10 34" /><path d="M882 42 l-10 34" /><path d="M998 34 l-10 34" />
<path d="M1114 48 l-10 34" /><path d="M1230 36 l-10 34" /><path d="M1346 44 l-10 34" />
<path d="M1462 32 l-10 34" /><path d="M1578 42 l-10 34" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB