Felder Validierung für E-Mail, PLZ und Telefonnummer. Eingabe akzeptieren wenn wenniger als 3 Plätze vorhanden sind.

This commit is contained in:
titver968
2026-01-09 09:34:08 +01:00
parent a99ddf6fa9
commit 0e21b78a4c
2 changed files with 257 additions and 71 deletions

Binary file not shown.

View File

@@ -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 @@
<input bind:value={geburtsdatum} type="date" placeholder="Geburtsdatum" required class="input" />
<input bind:value={strasse} placeholder="Straße" required class="input" />
<input bind:value={hausnummer} placeholder="Hausnummer" required class="input" />
<input bind:value={plz} placeholder="Postleitzahl" required class="input" />
<!-- PLZ mit Validierung -->
<div>
<input
bind:value={plz}
placeholder="Postleitzahl"
required
class="input"
class:input-error={plzFehler}
maxlength="5"
inputmode="numeric"
/>
{#if plzFehler}
<p class="text-red-600 text-sm mt-1">{plzFehler}</p>
{/if}
</div>
<input bind:value={ort} placeholder="Ort" required class="input" />
<input bind:value={telefon} placeholder="Telefonnummer" required class="input col-span-2" />
<input bind:value={email} type="email" placeholder="E-Mail-Adresse" required class="input col-span-2" />
<!-- Telefonnummer mit Validierung -->
<div class="col-span-2">
<input
bind:value={telefon}
type="tel"
placeholder="Telefonnummer (z.B. 0511 1234567 oder 0171 1234567)"
required
class="input"
class:input-error={telefonFehler}
/>
{#if telefonFehler}
<p class="text-red-600 text-sm mt-1">{telefonFehler}</p>
{/if}
</div>
<!-- E-Mail mit Validierung -->
<div class="col-span-2">
<input
bind:value={email}
type="email"
placeholder="E-Mail-Adresse"
required
class="input"
class:input-error={emailFehler}
/>
{#if emailFehler}
<p class="text-red-600 text-sm mt-1">{emailFehler}</p>
{/if}
</div>
<select bind:value={schulart} required class="input">
<option value="" disabled selected hidden>Schulart wählen</option>
<option value="Gymnasium">Gymnasium</option>
@@ -330,10 +479,10 @@
{#if !hideSozialVerhalten}
<div class="col-span-2">
<select
bind:value={sozialverhalten}
required
class="input"
class:input-error={sozialverhaltenFehler}
bind:value={sozialverhalten}
required
class="input"
class:input-error={sozialverhaltenFehler}
>
<option value="" disabled selected hidden>Sozialverhalten auswählen</option>
<option value="Entspricht den Erwartungen in vollem Umfang">Entspricht den Erwartungen in vollem Umfang</option>
@@ -364,24 +513,24 @@
<div class="grid grid-cols-2 gap-4">
<input
bind:value={noteDeutsch}
type="number"
min="1"
max="6"
placeholder="Note in Deutsch"
required
class="input"
class:input-error={notenFehler}
bind:value={noteDeutsch}
type="number"
min="1"
max="6"
placeholder="Note in Deutsch"
required
class="input"
class:input-error={notenFehler}
/>
<input
bind:value={noteMathe}
type="number"
min="1"
max="6"
placeholder="Note in Mathe"
required
class="input"
class:input-error={notenFehler}
bind:value={noteMathe}
type="number"
min="1"
max="6"
placeholder="Note in Mathe"
required
class="input"
class:input-error={notenFehler}
/>
</div>
{#if notenFehler}
@@ -396,7 +545,7 @@
<option value="" disabled selected hidden>Wunschzeitraum fürs Praktikum</option>
{#each filteredZeitraeume as d}
<option
value={d.id}>{d.bezeichnung} ({new Date(d.startDatum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })} - {new Date(d.endDatum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })})
value={d.id}>{d.bezeichnung} ({new Date(d.startDatum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })} - {new Date(d.endDatum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })})
</option>
{/each}
</select>
@@ -423,28 +572,51 @@
Lade verfügbare Dienststellen...
</div>
{:else}
<select bind:value={wunsch1Id} required disabled={!zeitraum} class="input" class:opacity-50={!zeitraum}>
<option value="" disabled selected>
{zeitraum ? '1. Wunschdienststelle' : 'Bitte zuerst Zeitraum wählen'}
</option>
{#each filteredDienststellen as d}
<option value={d.id}>{d.name} ({d.plaetze} {d.plaetze === 1 ? 'Platz' : 'Plätze'} frei)</option>
{/each}
</select>
<!-- Hinweis wenn nur begrenzte Auswahl möglich -->
{#if zeitraum && filteredDienststellen.length > 0 && filteredDienststellen.length < 3}
<div class="p-3 bg-blue-50 border border-blue-200 rounded-xl">
<div class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
</svg>
<p class="text-sm text-blue-700">
Für diesen Zeitraum {filteredDienststellen.length === 1 ? 'ist nur noch 1 Dienststelle' : 'sind nur noch ' + filteredDienststellen.length + ' Dienststellen'} verfügbar.
</p>
</div>
</div>
{/if}
<select bind:value={wunsch2Id} required disabled={!zeitraum || !wunsch1Id} class="input" class:opacity-50={!zeitraum || !wunsch1Id}>
<option value="" disabled selected>2. Wunschdienststelle</option>
{#each filteredDienststellen.filter(d => d.id != wunsch1Id) as d}
<option value={d.id}>{d.name} ({d.plaetze} {d.plaetze === 1 ? 'Platz' : 'Plätze'} frei)</option>
{/each}
</select>
<!-- 1. Wunsch - immer anzeigen wenn Dienststellen vorhanden -->
{#if filteredDienststellen.length >= 1}
<select bind:value={wunsch1Id} required disabled={!zeitraum} class="input" class:opacity-50={!zeitraum}>
<option value="" disabled selected>
{zeitraum ? '1. Wunschdienststelle' : 'Bitte zuerst Zeitraum wählen'}
</option>
{#each filteredDienststellen as d}
<option value={d.id}>{d.name} ({d.plaetze} {d.plaetze === 1 ? 'Platz' : 'Plätze'} frei)</option>
{/each}
</select>
{/if}
<select bind:value={wunsch3Id} required disabled={!zeitraum || !wunsch2Id} class="input" class:opacity-50={!zeitraum || !wunsch2Id}>
<option value="" disabled selected>3. Wunschdienststelle</option>
{#each filteredDienststellen.filter(d => d.id != wunsch1Id && d.id != wunsch2Id) as d}
<option value={d.id}>{d.name} ({d.plaetze} {d.plaetze === 1 ? 'Platz' : 'Plätze'} frei)</option>
{/each}
</select>
<!-- 2. Wunsch - nur anzeigen wenn mindestens 2 Dienststellen verfügbar -->
{#if filteredDienststellen.length >= 2}
<select bind:value={wunsch2Id} required disabled={!zeitraum || !wunsch1Id} class="input" class:opacity-50={!zeitraum || !wunsch1Id}>
<option value="" disabled selected>2. Wunschdienststelle</option>
{#each filteredDienststellen.filter(d => d.id != wunsch1Id) as d}
<option value={d.id}>{d.name} ({d.plaetze} {d.plaetze === 1 ? 'Platz' : 'Plätze'} frei)</option>
{/each}
</select>
{/if}
<!-- 3. Wunsch - nur anzeigen wenn mindestens 3 Dienststellen verfügbar -->
{#if filteredDienststellen.length >= 3}
<select bind:value={wunsch3Id} required disabled={!zeitraum || !wunsch2Id} class="input" class:opacity-50={!zeitraum || !wunsch2Id}>
<option value="" disabled selected>3. Wunschdienststelle</option>
{#each filteredDienststellen.filter(d => d.id != wunsch1Id && d.id != wunsch2Id) as d}
<option value={d.id}>{d.name} ({d.plaetze} {d.plaetze === 1 ? 'Platz' : 'Plätze'} frei)</option>
{/each}
</select>
{/if}
{/if}
<!-- Hinweis wenn keine Dienststellen verfügbar -->
@@ -472,12 +644,12 @@
<div>
<label for="pdf-upload" class="block text-gray-700 font-medium mb-1">Zeugnis hochladen:</label>
<input
id="pdf-upload"
type="file"
accept="application/pdf"
multiple
on:change={(e) => 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"
/>
</div>
{/key}
@@ -488,7 +660,21 @@
<div class="grid grid-cols-2 gap-4">
<input bind:value={notfallVorname} placeholder="Vorname Notfallkontakt" required class="input" />
<input bind:value={notfallNachname} placeholder="Nachname Notfallkontakt" required class="input" />
<input bind:value={notfallTelefon} type="tel" placeholder="Mobilnummer Notfallkontakt" required class="input col-span-2" />
<!-- Notfall-Telefonnummer mit Validierung -->
<div class="col-span-2">
<input
bind:value={notfallTelefon}
type="tel"
placeholder="Mobilnummer Notfallkontakt (z.B. 0171 1234567)"
required
class="input"
class:input-error={notfallTelefonFehler}
/>
{#if notfallTelefonFehler}
<p class="text-red-600 text-sm mt-1">{notfallTelefonFehler}</p>
{/if}
</div>
</div>
</div>
@@ -497,8 +683,8 @@
<div class="bg-white p-6 rounded shadow-lg text-center space-y-4 max-w-sm w-full">
<p class="text-red-600 font-semibold">{ablehnungHinweis}</p>
<button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
on:click={() => { resetForm(); showAblehnungModal = false; }}
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
on:click={() => { resetForm(); showAblehnungModal = false; }}
>
OK
</button>
@@ -508,14 +694,14 @@
<!-- Button - deaktiviert bei Validierungsfehlern oder fehlenden Pflichtfeldern -->
<button
type="submit"
disabled={formHatFehler || !zeitraum || !wunsch1Id || !wunsch2Id || !wunsch3Id}
class="w-full py-3 rounded-xl transition-all"
class:bg-blue-600={!formHatFehler && zeitraum && wunsch1Id && wunsch2Id && wunsch3Id}
class:hover:bg-blue-700={!formHatFehler && zeitraum && wunsch1Id && wunsch2Id && wunsch3Id}
class:text-white={!formHatFehler && zeitraum && wunsch1Id && wunsch2Id && wunsch3Id}
class:bg-gray-400={formHatFehler || !zeitraum || !wunsch1Id || !wunsch2Id || !wunsch3Id}
class:cursor-not-allowed={formHatFehler || !zeitraum || !wunsch1Id || !wunsch2Id || !wunsch3Id}
type="submit"
disabled={formHatFehler || !zeitraum || !wuenscheVollstaendig}
class="w-full py-3 rounded-xl transition-all"
class:bg-blue-600={!formHatFehler && zeitraum && wuenscheVollstaendig}
class:hover:bg-blue-700={!formHatFehler && zeitraum && wuenscheVollstaendig}
class:text-white={!formHatFehler && zeitraum && wuenscheVollstaendig}
class:bg-gray-400={formHatFehler || !zeitraum || !wuenscheVollstaendig}
class:cursor-not-allowed={formHatFehler || !zeitraum || !wuenscheVollstaendig}
>
Jetzt anmelden
</button>
@@ -529,8 +715,8 @@
<div class="bg-white p-6 rounded shadow-lg text-center space-y-4 max-w-sm w-full">
<p class="text-green-600 font-semibold">Anmeldung erfolgreich gesendet!</p>
<button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
on:click={resetForm}
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
on:click={resetForm}
>
OK
</button>