first praktikum variant
This commit is contained in:
4
src/routes/+layout.svelte
Normal file
4
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,4 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
</script>
|
||||
<slot />
|
||||
120
src/routes/+page.svelte
Normal file
120
src/routes/+page.svelte
Normal 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>
|
||||
65
src/routes/admin/+page.svelte
Normal file
65
src/routes/admin/+page.svelte
Normal 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 1–3</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;
|
||||
}
|
||||
65
src/routes/admin/dienststellen/+page.svelte
Normal file
65
src/routes/admin/dienststellen/+page.svelte
Normal 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;
|
||||
}
|
||||
19
src/routes/api/admin/anmeldungen/+server.ts
Normal file
19
src/routes/api/admin/anmeldungen/+server.ts
Normal 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' }
|
||||
});
|
||||
};
|
||||
25
src/routes/api/admin/dienststellen/+server.ts
Normal file
25
src/routes/api/admin/dienststellen/+server.ts
Normal 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 }));
|
||||
};
|
||||
13
src/routes/api/admin/login/+server.ts
Normal file
13
src/routes/api/admin/login/+server.ts
Normal 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 });
|
||||
};
|
||||
52
src/routes/api/anmelden/+server.ts
Normal file
52
src/routes/api/anmelden/+server.ts
Normal 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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user