Zeitraeume Verwaltung in Admin Bereich
This commit is contained in:
@@ -39,8 +39,8 @@
|
|||||||
<a href="/admin/dienststellen" class="bg-green-600 text-white px-4 py-3 rounded text-center hover:bg-green-800">
|
<a href="/admin/dienststellen" class="bg-green-600 text-white px-4 py-3 rounded text-center hover:bg-green-800">
|
||||||
🏢 Dienststellen verwalten
|
🏢 Dienststellen verwalten
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/zeitraum" class="bg-yellow-600 text-white px-4 py-3 rounded text-center hover:bg-yellow-800">
|
<a href="/admin/zeitraeume" class="bg-yellow-600 text-white px-4 py-3 rounded text-center hover:bg-yellow-800">
|
||||||
🗓 Zeitraum verwaltung
|
🗓 Praktikumzeiträume verwalten
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/change-password" class="bg-cyan-600 text-white px-4 py-3 rounded text-center hover:bg-cyan-800">
|
<a href="/admin/change-password" class="bg-cyan-600 text-white px-4 py-3 rounded text-center hover:bg-cyan-800">
|
||||||
👨💼 Passwort ädern
|
👨💼 Passwort ädern
|
||||||
|
|||||||
167
src/routes/admin/zeitraeume/+page.svelte
Normal file
167
src/routes/admin/zeitraeume/+page.svelte
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
let bezeichnung = '';
|
||||||
|
let startDatum = '';
|
||||||
|
let endDatum = '';
|
||||||
|
let zeitraeume = [];
|
||||||
|
let neuerBezeichnung = '';
|
||||||
|
let neuerstartDatum = '';
|
||||||
|
let neuerendDatum = '';
|
||||||
|
let fehlermeldung = '';
|
||||||
|
let bearbeiteId: number | null = null;
|
||||||
|
|
||||||
|
async function ladeZeitraeume() {
|
||||||
|
const res = await fetch('/api/admin/zeitraeume');
|
||||||
|
zeitraeume = await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bearbeiten(d: { id: number; bezeichnung: string; startDatum: Date; endDatum: Date }) {
|
||||||
|
neuerBezeichnung = d.bezeichnung;
|
||||||
|
neuerstartDatum = d.startDatum;
|
||||||
|
neuerendDatum = d.endDatum;
|
||||||
|
bearbeiteId = d.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function speichern() {
|
||||||
|
fehlermeldung = '';
|
||||||
|
if (!neuerBezeichnung.trim()) return;
|
||||||
|
|
||||||
|
const method = bearbeiteId ? 'PATCH' : 'POST';
|
||||||
|
const body = bearbeiteId
|
||||||
|
? { id: bearbeiteId, bezeichnung: neuerBezeichnung, startDatum: neuerstartDatum, endDatum: neuerendDatum }
|
||||||
|
: { bezeichnung: neuerBezeichnung, startDatum: neuerstartDatum, endDatum: neuerendDatum };
|
||||||
|
|
||||||
|
const res = await fetch('/api/admin/zeitraeume', {
|
||||||
|
method,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
neuerBezeichnung = '';
|
||||||
|
neuerstartDatum = '';
|
||||||
|
neuerendDatum = '';
|
||||||
|
bearbeiteId = null;
|
||||||
|
await ladeZeitraeume();
|
||||||
|
} else {
|
||||||
|
const err = await res.json();
|
||||||
|
fehlermeldung = err.error || 'Fehler beim Speichern';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loeschen(id: number) {
|
||||||
|
if (!confirm('Diese Zeitraum wirklich löschen?')) return;
|
||||||
|
await fetch(`/api/admin/zeitraeume?id=${id}`, { method: 'DELETE' });
|
||||||
|
await ladeZeitraeume();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(ladeZeitraeume);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-6 max-w-6xl mx-auto space-y-8">
|
||||||
|
<h1 class="text-2xl font-bold text-center">Praktikumszeiträume verwalten</h1>
|
||||||
|
|
||||||
|
<!-- Eingabefelder -->
|
||||||
|
<div class="flex flex-wrap gap-4 items-center">
|
||||||
|
<input
|
||||||
|
bind:value={neuerBezeichnung}
|
||||||
|
placeholder="Bezeichnung"
|
||||||
|
class="input w-full sm:w-[30%] border rounded px-3 py-2"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
bind:value={neuerstartDatum}
|
||||||
|
placeholder="Startdatum"
|
||||||
|
class="input w-full sm:w-[10%] border rounded px-3 py-2"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
bind:value={neuerendDatum}
|
||||||
|
placeholder="Enddatum"
|
||||||
|
class="input w-full sm:w-[10%] border rounded px-3 py-2"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
neuerBezeichnung = '';
|
||||||
|
neuerstartDatum = '';
|
||||||
|
neuerendDatum = '';
|
||||||
|
bearbeiteId = null;
|
||||||
|
}}
|
||||||
|
class="text-sm text-gray-500 hover:underline"
|
||||||
|
>
|
||||||
|
Zurücksetzen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={speichern}
|
||||||
|
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm"
|
||||||
|
>
|
||||||
|
{bearbeiteId !== null ? 'Ändern' : 'Hinzufügen'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fehlermeldung -->
|
||||||
|
{#if fehlermeldung}
|
||||||
|
<p class="text-red-600 text-sm">{fehlermeldung}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Tabellenähnliche Anzeige -->
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<div class="min-w-[600px] divide-y border rounded">
|
||||||
|
<!-- Kopfzeile -->
|
||||||
|
<div class="grid grid-cols-4 gap-x-8 font-semibold text-sm bg-gray-100 p-2">
|
||||||
|
<div>Bezeichnung</div>
|
||||||
|
<div class="text-right">Startdatum</div>
|
||||||
|
<div class="text-right">Enddatum</div>
|
||||||
|
<div class="text-right">Aktionen</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Einträge -->
|
||||||
|
{#each zeitraeume as d}
|
||||||
|
<div class="grid grid-cols-4 gap-x-8 items-center p-2 text-sm">
|
||||||
|
<div>{d.bezeichnung}</div>
|
||||||
|
<div class="text-right">{new Date(d.startDatum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })}</div>
|
||||||
|
<div class="text-right">{new Date(d.endDatum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })}</div>
|
||||||
|
<div class="flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
neuerBezeichnung = d.bezeichnung;
|
||||||
|
neuerstartDatum = d.startDatum;
|
||||||
|
neuerendDatum = d.endDatum;
|
||||||
|
bearbeiteId = d.id;
|
||||||
|
}}
|
||||||
|
class="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
Bearbeiten
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => loeschen(d.id)}
|
||||||
|
class="text-red-600 hover:underline"
|
||||||
|
>
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logout-Button -->
|
||||||
|
<div class="pt-4 text-center">
|
||||||
|
<button
|
||||||
|
on:click={async () => {
|
||||||
|
await fetch('/api/admin/logout', { method: 'POST' });
|
||||||
|
location.reload();
|
||||||
|
}}
|
||||||
|
class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.input {
|
||||||
|
@apply border p-2 rounded w-full;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
let bezeichnung = ''
|
|
||||||
let startDatum = ''
|
|
||||||
let endDatum = ''
|
|
||||||
let zeitraeume = []
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
zeitraeume = await fetchZeitraeume()
|
|
||||||
})
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
await createPraktikumszeitraum({ bezeichnung, startDatum, endDatum })
|
|
||||||
zeitraeume = await fetchZeitraeume()
|
|
||||||
|
|
||||||
// Felder zurücksetzen
|
|
||||||
bezeichnung = ''
|
|
||||||
startDatum = ''
|
|
||||||
endDatum = ''
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1 class="text-2xl font-bold mb-4">🗓 Praktikumszeiträume verwalten</h1>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid gap-4 max-w-xl bg-white p-4 rounded shadow">
|
|
||||||
<input type="text" bind:value={bezeichnung} placeholder="Bezeichnung (z. B. Frühjahr 2025)" required class="input" />
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input type="date" bind:value={startDatum} required class="input flex-1" />
|
|
||||||
<input type="date" bind:value={endDatum} required class="input flex-1" />
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="bg-green-600 text-white py-2 rounded hover:bg-green-700">✅ Zeitraum speichern</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold mt-8 mb-2">📋 Bereits erfasste Zeiträume</h2>
|
|
||||||
<div class="grid gap-4">
|
|
||||||
{#each zeitraeume as z}
|
|
||||||
<div class="p-4 bg-gray-100 rounded shadow">
|
|
||||||
<h3 class="font-bold">{z.bezeichnung}</h3>
|
|
||||||
<p class="text-sm text-gray-700">
|
|
||||||
{new Date(z.startDatum).toLocaleDateString()} – {new Date(z.endDatum).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.input {
|
|
||||||
@apply border p-2 rounded w-full;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
84
src/routes/api/admin/zeitraeume/+server.ts
Normal file
84
src/routes/api/admin/zeitraeume/+server.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
import type { Cookies } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
function checkAuth(cookies: Cookies) {
|
||||||
|
return cookies.get('admin_session') === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidDate(date: string | Date) {
|
||||||
|
const parsed = new Date(date)
|
||||||
|
return !isNaN(parsed.getTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ cookies }) => {
|
||||||
|
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
||||||
|
const zeitraeume = await prisma.praktikumszeitraum.findMany();
|
||||||
|
return json(zeitraeume);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST: RequestHandler = async ({ cookies, request }) => {
|
||||||
|
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
||||||
|
const { bezeichnung, startDatum, endDatum } = await request.json();
|
||||||
|
if (!isValidDate(startDatum) || !isValidDate(endDatum)) {
|
||||||
|
return json({ error: 'Ungültige Datum' }, { status: 400 });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const created = await prisma.praktikumszeitraum.create({ data: {
|
||||||
|
bezeichnung,
|
||||||
|
startDatum: new Date(startDatum),
|
||||||
|
endDatum: new Date(endDatum)
|
||||||
|
} });
|
||||||
|
return json(created);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fehler beim Hinzufuegen:', e);
|
||||||
|
return json({ error: 'Zeitraum existiert bereits' }, { status: 400 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PATCH: RequestHandler = async ({ cookies, request }) => {
|
||||||
|
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
||||||
|
|
||||||
|
const { id, bezeichnung, startDatum, endDatum } = await request.json();
|
||||||
|
|
||||||
|
if (typeof id !== 'number' || isNaN(id) || !name || isValidDate(startDatum) || isValidDate(endDatum)) {
|
||||||
|
return json({ error: 'Ungültige Eingabedaten' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.praktikumszeitraum.findUnique({ where: { id } });
|
||||||
|
if (!existing) {
|
||||||
|
return json({ error: 'Zeitraum nicht gefunden' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const konflikt = await prisma.praktikumszeitraum.findFirst({
|
||||||
|
where: {
|
||||||
|
bezeichnung,
|
||||||
|
NOT: { id },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (konflikt) {
|
||||||
|
return json({ error: 'Eine andere Praktikumszeitraum mit diesem Namen existiert bereits' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updated = await prisma.praktikumszeitraum.update({
|
||||||
|
where: { id },
|
||||||
|
data: { bezeichnung, startDatum, endDatum },
|
||||||
|
});
|
||||||
|
return json(updated);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fehler beim Update:', e);
|
||||||
|
return json({ error: 'Update fehlgeschlagen' }, { status: 400 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DELETE: RequestHandler = async ({ cookies, url }) => {
|
||||||
|
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
||||||
|
const id = Number(url.searchParams.get('id'));
|
||||||
|
await prisma.praktikumszeitraum.delete({ where: { id } });
|
||||||
|
return json({ success: true });
|
||||||
|
};
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { db } from '$lib/server/prisma'
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
const zeitraeume = await db.praktikumszeitraum.findMany({
|
|
||||||
orderBy: { startDatum: 'asc' }
|
|
||||||
})
|
|
||||||
return new Response(JSON.stringify(zeitraeume))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST({ request }) {
|
|
||||||
const { bezeichnung, startDatum, endDatum } = await request.json()
|
|
||||||
|
|
||||||
await db.praktikumszeitraum.create({
|
|
||||||
data: {
|
|
||||||
bezeichnung,
|
|
||||||
startDatum: new Date(startDatum),
|
|
||||||
endDatum: new Date(endDatum)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Response('OK')
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user