first praktikum variant

This commit is contained in:
titver968
2025-04-16 08:47:54 +02:00
parent d2857684fe
commit 10c443285d
58 changed files with 16193 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
<script lang="ts">
import '../app.css';
</script>
<slot />

120
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,120 @@
<script lang="ts">
import { onMount } from 'svelte';
let anrede = '';
let vorname = '';
let nachname = '';
let geburtsdatum = '';
let strasse = '';
let hausnummer = '';
let ort = '';
let plz = '';
let telefon = '';
let email = '';
let schulart = '';
let zeitraum = '';
let motivation = '';
let wunsch1Id = '';
let wunsch2Id = '';
let wunsch3Id = '';
let success = false;
let dienststellen = [];
onMount(async () => {
const res = await fetch('/api/admin/dienststellen');
dienststellen = await res.json();
});
async function anmelden() {
const res = await fetch('/api/anmelden', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
anrede, vorname, nachname, geburtsdatum,
strasse, hausnummer, ort, plz,
telefon, email, schulart, zeitraum, motivation,
wunsch1Id, wunsch2Id, wunsch3Id
})
});
const result = await res.json();
success = result.success;
}
</script>
<div class="min-h-screen bg-gray-100 flex items-center justify-center p-6">
<form on:submit|preventDefault={anmelden}
class="bg-white shadow-md rounded-2xl p-8 max-w-2xl w-full space-y-4">
<h1 class="text-2xl font-bold text-gray-800 mb-4 text-center">Praktikumsanmeldung</h1>
<!-- Anrede -->
<select bind:value={anrede} required class="input">
<option value="" disabled selected hidden>Anrede wählen</option>
<option value="Herr">Herr</option>
<option value="Frau">Frau</option>
<option value="Divers">Divers</option>
</select>
<!-- Persönliche Daten -->
<div class="grid grid-cols-2 gap-4">
<input bind:value={vorname} placeholder="Vorname" required class="input" />
<input bind:value={nachname} placeholder="Nachname" required class="input" />
<input bind:value={geburtsdatum} type="date" placeholder="Geburtsdatum" required class="input col-span-2" />
<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" />
<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" />
<input bind:value={schulart} placeholder="Schulart" required class="input col-span-2" />
<input bind:value={zeitraum} placeholder="Wunschzeitraum fürs Praktikum" required class="input col-span-2" />
</div>
<!-- Wunschdienststellen -->
<div class="grid grid-cols-1 gap-4">
<select bind:value={wunsch1Id} required>
<option value="" disabled selected>1. Wunschdienststelle</option>
{#each dienststellen as d}
<option value={d.id}>{d.name}</option>
{/each}
</select>
<select bind:value={wunsch2Id} required>
<option value="" disabled selected>2. Wunschdienststelle</option>
{#each dienststellen as d}
<option value={d.id}>{d.name}</option>
{/each}
</select>
<select bind:value={wunsch3Id} required>
<option value="" disabled selected>3. Wunschdienststelle</option>
{#each dienststellen as d}
<option value={d.id}>{d.name}</option>
{/each}
</select>
</div>
<!-- Motivation -->
<textarea bind:value={motivation} placeholder="Motivation (optional)"
class="w-full border border-gray-300 rounded-xl p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 h-32 resize-none" >
</textarea>
<!-- Button -->
<button type="submit"
class="w-full bg-blue-600 text-white py-3 rounded-xl hover:bg-blue-700 transition-all">
Jetzt anmelden
</button>
{#if success}
<p class="text-green-600 font-semibold text-center">Anmeldung erfolgreich gesendet!</p>
{/if}
</form>
</div>
<style>
.input {
@apply w-full border border-gray-300 rounded-xl p-3 focus:outline-none focus:ring-2 focus:ring-blue-500;
}
</style>

View File

@@ -0,0 +1,65 @@
<script lang="ts">
let passwort = '';
let eingeloggt = false;
let fehler = false;
let anmeldungen = [];
async function login() {
const res = await fetch('/api/admin/login', {
method: 'POST',
body: JSON.stringify({ passwort }),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
eingeloggt = true;
fehler = false;
const result = await fetch('/api/admin/anmeldungen');
anmeldungen = await result.json();
} else {
fehler = true;
}
}
</script>
<div class="p-6 max-w-4xl mx-auto">
{#if !eingeloggt}
<div class="space-y-4">
<h1 class="text-2xl font-bold">Admin Login</h1>
<input type="password" bind:value={passwort} placeholder="Passwort" class="input w-full" />
<button on:click={login} class="bg-blue-600 text-white px-4 py-2 rounded">Login</button>
{#if fehler}
<p class="text-red-600">Falsches Passwort</p>
{/if}
</div>
{:else}
<p><a href="/admin/dienststellen" class="text-blue-600 underline">Dienststellen verwalten</a></p>
<h1 class="text-2xl font-bold mb-4">Alle Anmeldungen</h1>
<table class="w-full border text-sm">
<thead>
<tr class="bg-gray-200">
<th class="p-2 text-left">Name</th>
<th class="p-2 text-left">E-Mail</th>
<th class="p-2 text-left">Wunsch 13</th>
<th class="p-2 text-left">Datum</th>
</tr>
</thead>
<tbody>
{#each anmeldungen as a}
<tr class="border-t">
<td class="p-2">{a.anrede} {a.vorname} {a.nachname}</td>
<td class="p-2">{a.email}</td>
<td class="p-2">{a.wunsch1}, {a.wunsch2}, {a.wunsch3}</td>
<td class="p-2">{new Date(a.timestamp).toLocaleDateString()}</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
<style>
.input {
@apply border rounded px-3 py-2 w-full;
}

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import { onMount } from 'svelte';
let dienststellen: { id: number; name: string }[] = [];
let neuerName = '';
let fehlermeldung = '';
async function ladeDienststellen() {
const res = await fetch('/api/admin/dienststellen');
dienststellen = await res.json();
}
async function hinzufuegen() {
fehlermeldung = '';
if (!neuerName.trim()) return;
const res = await fetch('/api/admin/dienststellen', {
method: 'POST',
body: JSON.stringify({ name: neuerName }),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
neuerName = '';
await ladeDienststellen();
} else {
const err = await res.json();
fehlermeldung = err.error || 'Fehler beim Hinzufügen';
}
}
async function loeschen(id: number) {
if (!confirm('Diese Dienststelle wirklich löschen?')) return;
await fetch(`/api/admin/dienststellen?id=${id}`, { method: 'DELETE' });
await ladeDienststellen();
}
onMount(ladeDienststellen);
</script>
<div class="p-6 max-w-xl mx-auto space-y-6">
<h1 class="text-2xl font-bold">Dienststellen verwalten</h1>
<div class="flex gap-2">
<input bind:value={neuerName} placeholder="Neue Dienststelle" class="input w-full" />
<button on:click={hinzufuegen} class="bg-blue-600 text-white px-4 py-2 rounded">Hinzufügen</button>
</div>
{#if fehlermeldung}
<p class="text-red-600">{fehlermeldung}</p>
{/if}
<ul class="divide-y border rounded">
{#each dienststellen as d}
<li class="flex justify-between items-center p-2">
<span>{d.name}</span>
<button on:click={() => loeschen(d.id)} class="text-sm text-red-600 hover:underline">Löschen</button>
</li>
{/each}
</ul>
</div>
<style>
.input {
@apply border rounded px-3 py-2;
}

View File

@@ -0,0 +1,19 @@
import { PrismaClient } from '@prisma/client';
import type { RequestHandler } from '@sveltejs/kit';
const prisma = new PrismaClient();
export const GET: RequestHandler = async () => {
const anmeldungen = await prisma.anmeldung.findMany({
//include: {
// wunsch1: true,
// wunsch2: true,
// wunsch3: true
//};
orderBy: { timestamp: 'desc' }
});
return new Response(JSON.stringify(anmeldungen), {
headers: { 'Content-Type': 'application/json' }
});
};

View File

@@ -0,0 +1,25 @@
import { PrismaClient } from '@prisma/client';
import type { RequestHandler } from '@sveltejs/kit';
const prisma = new PrismaClient();
export const GET: RequestHandler = async () => {
const dienststellen = await prisma.dienststelle.findMany({ orderBy: { name: 'asc' } });
return new Response(JSON.stringify(dienststellen));
};
export const POST: RequestHandler = async ({ request }) => {
const { name } = await request.json();
try {
const created = await prisma.dienststelle.create({ data: { name } });
return new Response(JSON.stringify(created));
} catch (e) {
return new Response(JSON.stringify({ error: 'Dienststelle existiert bereits' }), { status: 400 });
}
};
export const DELETE: RequestHandler = async ({ url }) => {
const id = Number(url.searchParams.get('id'));
await prisma.dienststelle.delete({ where: { id } });
return new Response(JSON.stringify({ success: true }));
};

View File

@@ -0,0 +1,13 @@
import type { RequestHandler } from '@sveltejs/kit';
const ADMIN_PASS = 'supergeheim'; // Passwort hier festlegen
export const POST: RequestHandler = async ({ request }) => {
const { passwort } = await request.json();
if (passwort === ADMIN_PASS) {
return new Response(JSON.stringify({ success: true }));
}
return new Response('Unauthorized', { status: 401 });
};

View File

@@ -0,0 +1,52 @@
import { PrismaClient } from '@prisma/client';
import type { RequestHandler } from '@sveltejs/kit';
const prisma = new PrismaClient();
export const POST: RequestHandler = async ({ request }) => {
const data = await request.json();
try {
await prisma.anmeldung.create({
data: {
anrede: data.anrede,
vorname: data.vorname,
nachname: data.nachname,
geburtsdatum: data.geburtsdatum,
strasse: data.strasse,
hausnummer: data.hausnummer,
ort: data.ort,
plz: data.plz,
telefon: data.telefon,
email: data.email,
schulart: data.schulart,
zeitraum: data.zeitraum,
motivation: data.motivation,
wunsch1Id: data.wunsch1Id,
wunsch2Id: data.wunsch2Id,
wunsch3Id: data.wunsch3Id
}
});
const exists = await prisma.anmeldung.findUnique({
where: { email: data.email }
});
if (exists) {
return new Response(JSON.stringify({ success: false, error: 'Diese E-Mail wurde bereits verwendet.' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
} else {
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
});
}
} catch (error) {
console.error(error);
return new Response(JSON.stringify({ success: false, error: 'Fehler bei Speicherung' }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
};