diff --git a/prisma/prisma/praktika.db b/prisma/prisma/praktika.db index 1777b31..b500e72 100644 Binary files a/prisma/prisma/praktika.db and b/prisma/prisma/praktika.db differ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 54e6ada..baa7990 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -44,12 +44,16 @@ let alterFehler = ''; let notenFehler = ''; let sozialverhaltenFehler = ''; + let emailFehler = ''; + let telefonFehler = ''; + let notfallTelefonFehler = ''; + let plzFehler = ''; // Hinweis für IGS/KGS mit Lernentwicklungsbericht $: zeigeIgsKgsHinweis = - ['IGSR', 'KGSR'].includes(schulart) && - schulklasse && - parseInt(schulklasse) < 10; + ['IGSR', 'KGSR'].includes(schulart) && + schulklasse && + parseInt(schulklasse) < 10; // Berechnung des Alters $: { @@ -80,6 +84,92 @@ } } + // Echtzeit-Validierung: E-Mail + $: { + if (email) { + // RFC 5322 konforme E-Mail-Validierung (vereinfacht) + const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; + if (!emailRegex.test(email)) { + emailFehler = 'Bitte gib eine gültige E-Mail-Adresse ein.'; + } else { + emailFehler = ''; + } + } else { + emailFehler = ''; + } + } + + // Echtzeit-Validierung: Telefonnummer + // Deutsche Telefonnummern: Festnetz 10-11 Ziffern, Mobil 11-12 Ziffern (mit führender 0) + // Erlaubt: Ziffern, Leerzeichen, Bindestriche, Schrägstriche, Klammern, + am Anfang + $: { + if (telefon) { + // Entferne alle Formatierungszeichen für die Ziffernzählung + const nurZiffern = telefon.replace(/[\s\-\/\(\)\+]/g, ''); + + // Prüfe ob nur erlaubte Zeichen enthalten sind + const erlaubteZeichenRegex = /^[\d\s\-\/\(\)\+]+$/; + + if (!erlaubteZeichenRegex.test(telefon)) { + telefonFehler = 'Die Telefonnummer enthält ungültige Zeichen.'; + } else if (!/^\d+$/.test(nurZiffern)) { + telefonFehler = 'Die Telefonnummer muss Ziffern enthalten.'; + } else if (nurZiffern.length < 10) { + telefonFehler = 'Die Telefonnummer ist zu kurz (mindestens 10 Ziffern).'; + } else if (nurZiffern.length > 15) { + telefonFehler = 'Die Telefonnummer ist zu lang (maximal 15 Ziffern).'; + } else { + telefonFehler = ''; + } + } else { + telefonFehler = ''; + } + } + + // Echtzeit-Validierung: Notfall-Telefonnummer + $: { + if (notfallTelefon) { + const nurZiffern = notfallTelefon.replace(/[\s\-\/\(\)\+]/g, ''); + const erlaubteZeichenRegex = /^[\d\s\-\/\(\)\+]+$/; + + if (!erlaubteZeichenRegex.test(notfallTelefon)) { + notfallTelefonFehler = 'Die Telefonnummer enthält ungültige Zeichen.'; + } else if (!/^\d+$/.test(nurZiffern)) { + notfallTelefonFehler = 'Die Telefonnummer muss Ziffern enthalten.'; + } else if (nurZiffern.length < 10) { + notfallTelefonFehler = 'Die Telefonnummer ist zu kurz (mindestens 10 Ziffern).'; + } else if (nurZiffern.length > 15) { + notfallTelefonFehler = 'Die Telefonnummer ist zu lang (maximal 15 Ziffern).'; + } else { + notfallTelefonFehler = ''; + } + } else { + notfallTelefonFehler = ''; + } + } + + // Echtzeit-Validierung: PLZ (genau 5 Ziffern für deutsche PLZ) + $: { + if (plz) { + const plzRegex = /^\d{5}$/; + if (!plzRegex.test(plz)) { + if (!/^\d*$/.test(plz)) { + plzFehler = 'Die Postleitzahl darf nur Ziffern enthalten.'; + } else if (plz.length < 5) { + plzFehler = 'Die Postleitzahl muss genau 5 Ziffern haben.'; + } else if (plz.length > 5) { + plzFehler = 'Die Postleitzahl darf maximal 5 Ziffern haben.'; + } else { + plzFehler = 'Bitte gib eine gültige Postleitzahl ein.'; + } + } else { + plzFehler = ''; + } + } else { + plzFehler = ''; + } + } + // Echtzeit-Validierung: Noten $: { const deutsch = parseInt(noteDeutsch); @@ -125,8 +215,18 @@ } } - // Prüfen ob Formular gültig ist - $: formHatFehler = alterFehler !== '' || notenFehler !== '' || sozialverhaltenFehler !== ''; + // Prüfen ob alle erforderlichen Wünsche ausgewählt wurden (abhängig von verfügbaren Dienststellen) + $: wuenscheVollstaendig = (() => { + const anzahl = filteredDienststellen.length; + if (anzahl === 0) return false; + if (anzahl === 1) return !!wunsch1Id; + if (anzahl === 2) return !!wunsch1Id && !!wunsch2Id; + return !!wunsch1Id && !!wunsch2Id && !!wunsch3Id; + })(); + + // Prüfen ob Formular gültig ist (erweitert um neue Validierungen) + $: formHatFehler = alterFehler !== '' || notenFehler !== '' || sozialverhaltenFehler !== '' || + emailFehler !== '' || telefonFehler !== '' || notfallTelefonFehler !== '' || plzFehler !== ''; // Dienststellen laden wenn Zeitraum sich ändert async function ladeDienststellen(zeitraumId: string) { @@ -174,8 +274,8 @@ let startDatum = ''; $: hideSozialVerhalten = - Number(schulklasse) >= 11 && - ["Gymnasium", "KGS_Gymnasialzweig", "Fachoberschule"].includes(schulart); + Number(schulklasse) >= 11 && + ["Gymnasium", "KGS_Gymnasialzweig", "Fachoberschule"].includes(schulart); onMount(async () => { const resZeitraeume = await fetch('/api/zeitraeume'); @@ -222,6 +322,10 @@ alterFehler = ''; notenFehler = ''; sozialverhaltenFehler = ''; + emailFehler = ''; + telefonFehler = ''; + notfallTelefonFehler = ''; + plzFehler = ''; dienststellen = []; } @@ -300,10 +404,55 @@ - + + +
+ + {#if plzFehler} +

{plzFehler}

+ {/if} +
+ - - + + +
+ + {#if telefonFehler} +

{telefonFehler}

+ {/if} +
+ + +
+ + {#if emailFehler} +

{emailFehler}

+ {/if} +
+ @@ -364,24 +513,24 @@
{#if notenFehler} @@ -396,7 +545,7 @@ {#each filteredZeitraeume as d} {/each} @@ -423,28 +572,51 @@ Lade verfügbare Dienststellen... {:else} - + + {#if zeitraum && filteredDienststellen.length > 0 && filteredDienststellen.length < 3} +
+
+ + + +

+ Für diesen Zeitraum {filteredDienststellen.length === 1 ? 'ist nur noch 1 Dienststelle' : 'sind nur noch ' + filteredDienststellen.length + ' Dienststellen'} verfügbar. +

+
+
+ {/if} - + + {#if filteredDienststellen.length >= 1} + + {/if} - + + {#if filteredDienststellen.length >= 2} + + {/if} + + + {#if filteredDienststellen.length >= 3} + + {/if} {/if} @@ -472,12 +644,12 @@
pdfDateien = Array.from((e.target as HTMLInputElement).files || [])} - class="input" + id="pdf-upload" + type="file" + accept="application/pdf" + multiple + on:change={(e) => pdfDateien = Array.from((e.target as HTMLInputElement).files || [])} + class="input" />
{/key} @@ -488,7 +660,21 @@
- + + +
+ + {#if notfallTelefonFehler} +

{notfallTelefonFehler}

+ {/if} +
@@ -497,8 +683,8 @@

{ablehnungHinweis}

@@ -508,14 +694,14 @@ @@ -529,8 +715,8 @@

Anmeldung erfolgreich gesendet!

@@ -570,4 +756,4 @@ box-shadow: 0 0 0 2px #dc2626; border-color: #dc2626; } - \ No newline at end of file +