Dienstellen auf die neue cookie
This commit is contained in:
@@ -42,34 +42,23 @@ model Anmeldung {
|
|||||||
anrede String
|
anrede String
|
||||||
vorname String
|
vorname String
|
||||||
nachname String
|
nachname String
|
||||||
geburtsdatum String
|
email String
|
||||||
strasse String
|
noteDeutsch String?
|
||||||
hausnummer String
|
noteMathe String?
|
||||||
ort String
|
sozialverhalten String?
|
||||||
plz String
|
|
||||||
telefon String
|
|
||||||
email String @unique
|
|
||||||
noteDeutsch Int
|
|
||||||
noteMathe Int
|
|
||||||
sozialverhalten String
|
|
||||||
schulart String
|
|
||||||
motivation String
|
|
||||||
|
|
||||||
praktikumId Int
|
|
||||||
|
|
||||||
wunsch1 Dienststelle @relation("Wunsch1", fields: [wunsch1Id], references: [id])
|
|
||||||
wunsch1Id Int
|
|
||||||
wunsch2 Dienststelle @relation("Wunsch2", fields: [wunsch2Id], references: [id])
|
|
||||||
wunsch2Id Int
|
|
||||||
wunsch3 Dienststelle @relation("Wunsch3", fields: [wunsch3Id], references: [id])
|
|
||||||
wunsch3Id Int
|
|
||||||
status Status @default(OFFEN)
|
status Status @default(OFFEN)
|
||||||
zugewiesenId Int?
|
zugewiesenId Int? // ID der zugewiesenen Dienststelle
|
||||||
zugewiesen Dienststelle? @relation("Zugewiesen", fields: [zugewiesenId], references: [id])
|
zugewiesen Dienststelle? @relation(fields: [zugewiesenId], references: [id])
|
||||||
|
wunsch1Id Int?
|
||||||
|
wunsch1 Dienststelle? @relation("Wunsch1", fields: [wunsch1Id], references: [id])
|
||||||
|
wunsch2Id Int?
|
||||||
|
wunsch2 Dienststelle? @relation("Wunsch2", fields: [wunsch2Id], references: [id])
|
||||||
|
wunsch3Id Int?
|
||||||
|
wunsch3 Dienststelle? @relation("Wunsch3", fields: [wunsch3Id], references: [id])
|
||||||
|
timestamp BigInt
|
||||||
|
pdfs PdfDatei[]
|
||||||
|
|
||||||
timestamp DateTime @default(now())
|
@@map("anmeldungen")
|
||||||
|
|
||||||
pdfs PdfDatei[] @relation("AnmeldungPdfs")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model PdfDatei {
|
model PdfDatei {
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
accept: { id: number };
|
||||||
|
reject: { id: number };
|
||||||
|
delete: { id: number };
|
||||||
|
}>();
|
||||||
|
|
||||||
interface Anmeldung {
|
interface Anmeldung {
|
||||||
pdfs: { pfad: string }[];
|
pdfs: { pfad: string }[];
|
||||||
anrede: string;
|
anrede: string;
|
||||||
@@ -20,24 +26,26 @@
|
|||||||
|
|
||||||
export let anmeldungen: Anmeldung[];
|
export let anmeldungen: Anmeldung[];
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
accept: { id: number };
|
|
||||||
reject: { id: number };
|
|
||||||
delete: { id: number };
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function formatDate(timestamp: number): string {
|
function formatDate(timestamp: number): string {
|
||||||
return new Date(timestamp).toLocaleDateString('de-DE', {
|
return new Date(timestamp).toLocaleDateString('de-DE', {
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadPdf(pdfPath: string) {
|
function handleAccept(id: number) {
|
||||||
window.open(pdfPath, '_blank');
|
dispatch('accept', { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReject(id: number) {
|
||||||
|
dispatch('reject', { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(id: number) {
|
||||||
|
dispatch('delete', { id });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -68,101 +76,111 @@
|
|||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
{#each anmeldungen as anmeldung (anmeldung.id)}
|
{#each anmeldungen as anmeldung (anmeldung.id)}
|
||||||
<tr class="hover:bg-gray-50">
|
<tr class="hover:bg-gray-50">
|
||||||
|
<!-- Bewerber Info -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div class="flex flex-col">
|
||||||
<div class="text-sm font-medium text-gray-900">
|
<div class="text-sm font-medium text-gray-900">
|
||||||
{anmeldung.anrede} {anmeldung.vorname} {anmeldung.nachname}
|
{anmeldung.anrede} {anmeldung.vorname} {anmeldung.nachname}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-500">{anmeldung.email}</div>
|
<div class="text-sm text-gray-500">
|
||||||
|
{anmeldung.email}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<!-- Noten -->
|
||||||
<div class="space-y-1">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
{#if anmeldung.noteDeutsch}
|
<div class="text-sm text-gray-900 space-y-1">
|
||||||
<div>Deutsch: <span class="font-medium">{anmeldung.noteDeutsch}</span></div>
|
<div><span class="font-medium">Deutsch:</span> {anmeldung.noteDeutsch || '—'}</div>
|
||||||
{/if}
|
<div><span class="font-medium">Mathe:</span> {anmeldung.noteMathe || '—'}</div>
|
||||||
{#if anmeldung.noteMathe}
|
|
||||||
<div>Mathe: <span class="font-medium">{anmeldung.noteMathe}</span></div>
|
|
||||||
{/if}
|
|
||||||
{#if anmeldung.sozialverhalten}
|
{#if anmeldung.sozialverhalten}
|
||||||
<div>Sozialverhalten: <span class="font-medium">{anmeldung.sozialverhalten}</span></div>
|
<div class="text-xs text-gray-600 mt-2">
|
||||||
|
<span class="font-medium">Sozialverhalten:</span><br>
|
||||||
|
{anmeldung.sozialverhalten}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-900">
|
<!-- Wünsche -->
|
||||||
<div class="space-y-1">
|
<td class="px-6 py-4">
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
{#if anmeldung.wunsch1}
|
{#if anmeldung.wunsch1}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-2">1</span>
|
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-100 text-blue-800 text-xs font-medium mr-2">1</span>
|
||||||
{anmeldung.wunsch1.name}
|
<span class="text-gray-900">{anmeldung.wunsch1.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if anmeldung.wunsch2}
|
{#if anmeldung.wunsch2}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 mr-2">2</span>
|
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-800 text-xs font-medium mr-2">2</span>
|
||||||
{anmeldung.wunsch2.name}
|
<span class="text-gray-900">{anmeldung.wunsch2.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if anmeldung.wunsch3}
|
{#if anmeldung.wunsch3}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 mr-2">3</span>
|
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-yellow-100 text-yellow-800 text-xs font-medium mr-2">3</span>
|
||||||
{anmeldung.wunsch3.name}
|
<span class="text-gray-900">{anmeldung.wunsch3.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<!-- Dokumente -->
|
||||||
{#if anmeldung.pdfs.length > 0}
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
{#each anmeldung.pdfs as pdf, index}
|
{#each anmeldung.pdfs as pdf, index}
|
||||||
<button
|
<div>
|
||||||
on:click={() => downloadPdf(pdf.pfad)}
|
<a
|
||||||
class="flex items-center text-blue-600 hover:text-blue-800 transition-colors"
|
href={pdf.pfad}
|
||||||
|
target="_blank"
|
||||||
|
class="inline-flex items-center text-sm text-blue-600 hover:text-blue-900 hover:underline"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
PDF {index + 1}
|
PDF {index + 1}
|
||||||
</button>
|
</a>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
|
||||||
<span class="text-gray-400">Keine Dokumente</span>
|
|
||||||
{/if}
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<!-- Anmeldedatum -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
{formatDate(anmeldung.timestamp)}
|
{formatDate(anmeldung.timestamp)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<!-- Aktionen -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
<div class="flex justify-end space-x-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<button
|
<button
|
||||||
on:click={() => dispatch('accept', { id: anmeldung.id })}
|
on:click={() => handleAccept(anmeldung.id)}
|
||||||
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-colors"
|
class="inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
</svg>
|
</svg>
|
||||||
Annehmen
|
Annehmen
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={() => dispatch('reject', { id: anmeldung.id })}
|
on:click={() => handleReject(anmeldung.id)}
|
||||||
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-yellow-600 hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 transition-colors"
|
class="inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
Ablehnen
|
Ablehnen
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={() => dispatch('delete', { id: anmeldung.id })}
|
on:click={() => handleDelete(anmeldung.id)}
|
||||||
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors"
|
class="inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
</svg>
|
</svg>
|
||||||
Löschen
|
Löschen
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
confirm: { dienststelleId: number };
|
||||||
|
cancel: void;
|
||||||
|
}>();
|
||||||
|
|
||||||
interface Wish {
|
interface Wish {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -10,113 +15,124 @@
|
|||||||
export let wishes: Wish[];
|
export let wishes: Wish[];
|
||||||
export let selectedId: number | null;
|
export let selectedId: number | null;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
let isLoading = false;
|
||||||
confirm: { dienststelleId: number };
|
|
||||||
cancel: {};
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let currentSelectedId = selectedId;
|
|
||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
if (currentSelectedId !== null) {
|
if (selectedId !== null) {
|
||||||
dispatch('confirm', { dienststelleId: currentSelectedId });
|
isLoading = true;
|
||||||
|
dispatch('confirm', { dienststelleId: selectedId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
dispatch('cancel', {});
|
dispatch('cancel');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
function handleBackdropClick(event: MouseEvent) {
|
||||||
|
if (event.target === event.currentTarget) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBackdropKeydown(event: KeyboardEvent) {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
handleCancel();
|
handleCancel();
|
||||||
} else if (event.key === 'Enter' && currentSelectedId !== null) {
|
|
||||||
handleConfirm();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
|
||||||
|
|
||||||
<!-- Backdrop -->
|
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity z-50"
|
class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4"
|
||||||
on:click={handleCancel}
|
on:click={handleBackdropClick}
|
||||||
on:keydown={(e) => e.key === 'Enter' && handleCancel()}
|
on:keydown={handleBackdropKeydown}
|
||||||
role="button"
|
|
||||||
tabindex="-1"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- Dialog -->
|
|
||||||
<div class="fixed inset-0 z-50 overflow-y-auto">
|
|
||||||
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
|
||||||
<div
|
|
||||||
class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
|
|
||||||
on:click|stopPropagation
|
|
||||||
on:keydown|stopPropagation
|
|
||||||
role="dialog"
|
role="dialog"
|
||||||
tabindex="-1"
|
aria-modal="true"
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
|
<!-- Modal Content -->
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="relative bg-white rounded-lg shadow-xl max-w-md w-full">
|
||||||
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10">
|
<!-- Header -->
|
||||||
<svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<div class="flex items-start justify-between p-6 border-b border-gray-200 rounded-t">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
<h3 class="text-lg font-semibold text-gray-900" id="modal-title">
|
||||||
</svg>
|
Dienststelle für Praktikum auswählen
|
||||||
</div>
|
|
||||||
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left w-full">
|
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
|
|
||||||
Praktikumsstelle zuweisen
|
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-4">
|
<button
|
||||||
<p class="text-sm text-gray-500 mb-4">
|
type="button"
|
||||||
Bitte wählen Sie eine der gewünschten Praktikumsstellen aus:
|
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center"
|
||||||
|
on:click={handleCancel}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Modal schließen</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="p-6">
|
||||||
|
<p class="text-sm text-gray-600 mb-4">
|
||||||
|
Wählen Sie eine der Wunsch-Dienststellen für diese Anmeldung aus:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<fieldset class="space-y-3">
|
<div class="space-y-3">
|
||||||
<legend class="sr-only">Praktikumsstelle auswählen</legend>
|
|
||||||
{#each wishes as wish}
|
{#each wishes as wish}
|
||||||
<label class="flex items-center">
|
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
bind:group={currentSelectedId}
|
bind:group={selectedId}
|
||||||
value={wish.id}
|
value={wish.id}
|
||||||
class="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-600"
|
disabled={isLoading}
|
||||||
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
|
||||||
/>
|
/>
|
||||||
<span class="ml-3 text-sm font-medium text-gray-700">
|
<span class="ml-3 text-sm font-medium text-gray-900">
|
||||||
{wish.name}
|
{wish.name}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{/each}
|
{/each}
|
||||||
</fieldset>
|
</div>
|
||||||
|
|
||||||
{#if wishes.length === 0}
|
{#if wishes.length === 0}
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-4">
|
||||||
<p class="text-sm text-gray-500">Keine Wünsche verfügbar</p>
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||||
|
</svg>
|
||||||
|
<p class="mt-2 text-sm text-gray-500">Keine Wünsche verfügbar</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
|
<!-- Footer -->
|
||||||
<button
|
<div class="flex items-center justify-end space-x-3 p-6 border-t border-gray-200 rounded-b">
|
||||||
type="button"
|
|
||||||
on:click={handleConfirm}
|
|
||||||
disabled={currentSelectedId === null}
|
|
||||||
class="inline-flex w-full justify-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 disabled:bg-gray-300 disabled:cursor-not-allowed sm:ml-3 sm:w-auto transition-colors"
|
|
||||||
>
|
|
||||||
Bestätigen
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={handleCancel}
|
on:click={handleCancel}
|
||||||
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto transition-colors"
|
disabled={isLoading}
|
||||||
|
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Abbrechen
|
Abbrechen
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
on:click={handleConfirm}
|
||||||
|
disabled={selectedId === null || isLoading || wishes.length === 0}
|
||||||
|
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center"
|
||||||
|
>
|
||||||
|
{#if isLoading}
|
||||||
|
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||||
|
</svg>
|
||||||
|
Wird zugewiesen...
|
||||||
|
{:else}
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
Praktikum zuweisen
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
|
// src/routes/admin/dienstellen/+page.server.ts
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ cookies }) => {
|
export const load: PageServerLoad = async ({ cookies }) => {
|
||||||
if (cookies.get('admin_session') !== 'true') {
|
// Korrigiere Cookie-Name um konsistent zu sein
|
||||||
throw redirect(303, '/admin'); // zurück zur Login-Seite
|
const adminAuth = cookies.get('admin-auth');
|
||||||
|
|
||||||
|
if (adminAuth !== 'authenticated') {
|
||||||
|
throw redirect(303, '/admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'Dienstellen verwalten'
|
||||||
|
};
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
|
// src/routes/admin/zeitraeume/+page.server.ts
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ cookies }) => {
|
export const load: PageServerLoad = async ({ cookies }) => {
|
||||||
if (cookies.get('admin_session') !== 'true') {
|
// Korrigiere Cookie-Name um konsistent zu sein
|
||||||
throw redirect(303, '/admin'); // zurück zur Login-Seite
|
const adminAuth = cookies.get('admin-auth');
|
||||||
|
|
||||||
|
if (adminAuth !== 'authenticated') {
|
||||||
|
throw redirect(303, '/admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'Zetraeume verwalten'
|
||||||
|
};
|
||||||
};
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// src/routes/api/admin/anmeldungen/+server.ts
|
||||||
import { PrismaClient, Status } from '@prisma/client';
|
import { PrismaClient, Status } from '@prisma/client';
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
@@ -8,12 +9,23 @@ const prisma = new PrismaClient();
|
|||||||
|
|
||||||
import type { Cookies } from '@sveltejs/kit';
|
import type { Cookies } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
// Korrigierte Auth-Funktion mit neuem Cookie-Namen
|
||||||
function checkAuth(cookies: Cookies) {
|
function checkAuth(cookies: Cookies) {
|
||||||
return cookies.get('admin_session') === 'true';
|
return cookies.get('admin-auth') === 'authenticated';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ cookies }) => {
|
export const GET: RequestHandler = async ({ cookies }) => {
|
||||||
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
if (!checkAuth(cookies)) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Nicht autorisiert' }),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const anmeldungen = await prisma.anmeldung.findMany({
|
const anmeldungen = await prisma.anmeldung.findMany({
|
||||||
include: {
|
include: {
|
||||||
wunsch1: true,
|
wunsch1: true,
|
||||||
@@ -23,16 +35,29 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
|||||||
},
|
},
|
||||||
orderBy: { timestamp: 'desc' }
|
orderBy: { timestamp: 'desc' }
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Response(JSON.stringify(anmeldungen), {
|
return new Response(JSON.stringify(anmeldungen), {
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Anmeldungen:', error);
|
||||||
|
return json({ error: 'Fehler beim Laden der Anmeldungen' }, { status: 500 });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ url }) => {
|
export const POST: RequestHandler = async ({ url, cookies, request }) => {
|
||||||
|
if (!checkAuth(cookies)) {
|
||||||
|
return json({ error: 'Nicht autorisiert' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
const id = Number(url.searchParams.get('id'));
|
const id = Number(url.searchParams.get('id'));
|
||||||
if (!id) return json({ error: 'Ungültige ID' }, { status: 400 });
|
if (!id) return json({ error: 'Ungültige ID' }, { status: 400 });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Prüfe ob eine spezifische Dienststelle zugewiesen werden soll
|
||||||
|
const body = await request.json().catch(() => ({}));
|
||||||
|
const dienststelleId = body.dienststelleId;
|
||||||
|
|
||||||
const anmeldung = await prisma.anmeldung.findUnique({
|
const anmeldung = await prisma.anmeldung.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
@@ -42,8 +67,44 @@ export const POST: RequestHandler = async ({ url }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!anmeldung) return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
|
if (!anmeldung) {
|
||||||
|
return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Falls spezifische Dienststelle gewählt wurde
|
||||||
|
if (dienststelleId) {
|
||||||
|
const dienststelle = await prisma.dienststelle.findUnique({
|
||||||
|
where: { id: dienststelleId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dienststelle) {
|
||||||
|
return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dienststelle.plaetze <= 0) {
|
||||||
|
return json({ error: 'Keine verfügbaren Plätze bei dieser Dienststelle' }, { status: 409 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$transaction([
|
||||||
|
prisma.anmeldung.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
status: Status.ANGENOMMEN,
|
||||||
|
zugewiesenId: dienststelleId
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
prisma.dienststelle.update({
|
||||||
|
where: { id: dienststelleId },
|
||||||
|
data: {
|
||||||
|
plaetze: { decrement: 1 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
return json({ success: true, message: `Zugewiesen an: ${dienststelle.name}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Automatische Zuweisung nach Wunschreihenfolge
|
||||||
const wuensche = [anmeldung.wunsch1, anmeldung.wunsch2, anmeldung.wunsch3];
|
const wuensche = [anmeldung.wunsch1, anmeldung.wunsch2, anmeldung.wunsch3];
|
||||||
|
|
||||||
for (const wunsch of wuensche) {
|
for (const wunsch of wuensche) {
|
||||||
@@ -70,17 +131,51 @@ export const POST: RequestHandler = async ({ url }) => {
|
|||||||
|
|
||||||
return json({ error: 'Keine verfügbaren Plätze bei Wunsch-Dienststellen' }, { status: 409 });
|
return json({ error: 'Keine verfügbaren Plätze bei Wunsch-Dienststellen' }, { status: 409 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error('Fehler beim Annehmen der Anmeldung:', err);
|
||||||
return json({ error: 'Interner Serverfehler' }, { status: 500 });
|
return json({ error: 'Interner Serverfehler' }, { status: 500 });
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Neue PATCH-Route für Ablehnung
|
||||||
|
export const PATCH: RequestHandler = async ({ url, cookies, request }) => {
|
||||||
|
if (!checkAuth(cookies)) {
|
||||||
|
return json({ error: 'Nicht autorisiert' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const id = Number(url.searchParams.get('id'));
|
||||||
|
if (!id) return json({ error: 'Ungültige ID' }, { status: 400 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = await request.json().catch(() => ({}));
|
||||||
|
|
||||||
|
if (body.action === 'reject') {
|
||||||
|
await prisma.anmeldung.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
status: Status.ABGELEHNT
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return json({ success: true, message: 'Anmeldung abgelehnt' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return json({ error: 'Unbekannte Aktion' }, { status: 400 });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Ablehnen der Anmeldung:', err);
|
||||||
|
return json({ error: 'Interner Serverfehler' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const DELETE: RequestHandler = async ({ cookies, url }) => {
|
export const DELETE: RequestHandler = async ({ cookies, url }) => {
|
||||||
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
if (!checkAuth(cookies)) {
|
||||||
|
return json({ error: 'Nicht autorisiert' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
const id = Number(url.searchParams.get('id'));
|
const id = Number(url.searchParams.get('id'));
|
||||||
if (isNaN(id)) {
|
if (isNaN(id)) {
|
||||||
return json({ error: 'Ungültige ID' }, { status: 400 });
|
return json({ error: 'Ungültige ID' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Alle PDF-Einträge zur Anmeldung laden
|
// 1. Alle PDF-Einträge zur Anmeldung laden
|
||||||
const pdfs = await prisma.pdfDatei.findMany({
|
const pdfs = await prisma.pdfDatei.findMany({
|
||||||
@@ -106,12 +201,14 @@ export const DELETE: RequestHandler = async ({ cookies, url }) => {
|
|||||||
where: { anmeldungId: id }
|
where: { anmeldungId: id }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Anmeldung löschen
|
// 4. Anmeldung löschen
|
||||||
await prisma.anmeldung.delete({where: { id } });
|
await prisma.anmeldung.delete({
|
||||||
return json({ ok: true });
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
return json({ success: true, message: 'Anmeldung erfolgreich gelöscht' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Löschen der Anmeldung:', error);
|
console.error('Fehler beim Löschen der Anmeldung:', error);
|
||||||
return json({ error: 'Löschen fehlgeschlagen' }, { status: 500 });
|
return json({ error: 'Löschen fehlgeschlagen' }, { status: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// src/routes/api/admin/dienststellen/+server.ts
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
@@ -6,73 +7,187 @@ const prisma = new PrismaClient();
|
|||||||
|
|
||||||
import type { Cookies } from '@sveltejs/kit';
|
import type { Cookies } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
// Korrigierte Auth-Funktion mit neuem Cookie-Namen
|
||||||
function checkAuth(cookies: Cookies) {
|
function checkAuth(cookies: Cookies) {
|
||||||
return cookies.get('admin_session') === 'true';
|
return cookies.get('admin-auth') === 'authenticated';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ cookies }) => {
|
export const GET: RequestHandler = async ({ cookies }) => {
|
||||||
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
if (!checkAuth(cookies)) {
|
||||||
const dienststellen = await prisma.dienststelle.findMany({ orderBy: { name: 'asc' } });
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Nicht autorisiert' }),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dienststellen = await prisma.dienststelle.findMany({
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
/*
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
Anmeldung: true // Use the correct relation name as defined in your Prisma schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
});
|
||||||
return json(dienststellen);
|
return json(dienststellen);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Dienststellen:', error);
|
||||||
|
return json({ error: 'Fehler beim Laden der Dienststellen' }, { status: 500 });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ cookies, request }) => {
|
export const POST: RequestHandler = async ({ cookies, request }) => {
|
||||||
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
if (!checkAuth(cookies)) {
|
||||||
|
return json({ error: 'Nicht autorisiert' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const { name, plaetze } = await request.json();
|
const { name, plaetze } = await request.json();
|
||||||
if (typeof plaetze !== 'number' || plaetze < 0) {
|
|
||||||
|
// Validierung
|
||||||
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||||
|
return json({ error: 'Name ist erforderlich' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof plaetze !== 'number' || plaetze < 0 || !Number.isInteger(plaetze)) {
|
||||||
return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 });
|
return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 });
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const created = await prisma.dienststelle.create({ data: {
|
// Prüfe ob Name bereits existiert
|
||||||
name,
|
const existing = await prisma.dienststelle.findFirst({
|
||||||
|
where: { name: name.trim() }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
return json({ error: 'Eine Dienststelle mit diesem Namen existiert bereits' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = await prisma.dienststelle.create({
|
||||||
|
data: {
|
||||||
|
name: name.trim(),
|
||||||
plaetze,
|
plaetze,
|
||||||
} });
|
}
|
||||||
return json(created);
|
});
|
||||||
} catch (e) {
|
|
||||||
console.error('Fehler beim Hinzufuegen:', e);
|
return json(created, { status: 201 });
|
||||||
return json({ error: 'Dienststelle existiert bereits' }, { status: 400 });
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Erstellen der Dienststelle:', error);
|
||||||
|
return json({ error: 'Fehler beim Erstellen der Dienststelle' }, { status: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PATCH: RequestHandler = async ({ cookies, request }) => {
|
export const PATCH: RequestHandler = async ({ cookies, request }) => {
|
||||||
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
if (!checkAuth(cookies)) {
|
||||||
|
return json({ error: 'Nicht autorisiert' }, { status: 401 });
|
||||||
const { id, name, plaetze } = await request.json();
|
|
||||||
|
|
||||||
if (typeof id !== 'number' || isNaN(id) || !name || typeof plaetze !== 'number' || plaetze < 0) {
|
|
||||||
return json({ error: 'Ungültige Eingabedaten' }, { status: 400 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { id, name, plaetze } = await request.json();
|
||||||
|
|
||||||
|
// Validierung
|
||||||
|
if (typeof id !== 'number' || isNaN(id)) {
|
||||||
|
return json({ error: 'Ungültige ID' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||||
|
return json({ error: 'Name ist erforderlich' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof plaetze !== 'number' || plaetze < 0 || !Number.isInteger(plaetze)) {
|
||||||
|
return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe ob Dienststelle existiert
|
||||||
const existing = await prisma.dienststelle.findUnique({ where: { id } });
|
const existing = await prisma.dienststelle.findUnique({ where: { id } });
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 });
|
return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const konflikt = await prisma.dienststelle.findFirst({
|
// Prüfe ob neuer Name bereits bei anderer Dienststelle existiert
|
||||||
|
const nameConflict = await prisma.dienststelle.findFirst({
|
||||||
where: {
|
where: {
|
||||||
name,
|
name: name.trim(),
|
||||||
NOT: { id },
|
NOT: { id },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (konflikt) {
|
|
||||||
|
if (nameConflict) {
|
||||||
return json({ error: 'Eine andere Dienststelle mit diesem Namen existiert bereits' }, { status: 400 });
|
return json({ error: 'Eine andere Dienststelle mit diesem Namen existiert bereits' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Prüfe ob Plätze reduziert werden und ob das möglich ist
|
||||||
|
const assignedCount = await prisma.anmeldung.count({
|
||||||
|
where: { zugewiesenId: id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (plaetze < assignedCount) {
|
||||||
|
return json({
|
||||||
|
error: `Plätze können nicht auf ${plaetze} reduziert werden. ${assignedCount} Anmeldungen sind bereits zugewiesen.`
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
const updated = await prisma.dienststelle.update({
|
const updated = await prisma.dienststelle.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name, plaetze },
|
data: {
|
||||||
|
name: name.trim(),
|
||||||
|
plaetze
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return json(updated);
|
return json(updated);
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Update:', e);
|
console.error('Fehler beim Aktualisieren der Dienststelle:', error);
|
||||||
return json({ error: 'Update fehlgeschlagen' }, { status: 400 });
|
return json({ error: 'Fehler beim Aktualisieren der Dienststelle' }, { status: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DELETE: RequestHandler = async ({ cookies, url }) => {
|
export const DELETE: RequestHandler = async ({ cookies, url }) => {
|
||||||
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
|
if (!checkAuth(cookies)) {
|
||||||
|
return json({ error: 'Nicht autorisiert' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
const id = Number(url.searchParams.get('id'));
|
const id = Number(url.searchParams.get('id'));
|
||||||
|
|
||||||
|
if (isNaN(id)) {
|
||||||
|
return json({ error: 'Ungültige ID' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prüfe ob Dienststelle existiert
|
||||||
|
const existing = await prisma.dienststelle.findUnique({ where: { id } });
|
||||||
|
if (!existing) {
|
||||||
|
return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe ob noch Anmeldungen zugewiesen sind
|
||||||
|
const assignedCount = await prisma.anmeldung.count({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ zugewiesenId: id },
|
||||||
|
{ wunsch1Id: id },
|
||||||
|
{ wunsch2Id: id },
|
||||||
|
{ wunsch3Id: id }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (assignedCount > 0) {
|
||||||
|
return json({
|
||||||
|
error: 'Dienststelle kann nicht gelöscht werden. Es sind noch Anmeldungen damit verknüpft.'
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
await prisma.dienststelle.delete({ where: { id } });
|
await prisma.dienststelle.delete({ where: { id } });
|
||||||
return json({ success: true });
|
return json({ success: true, message: 'Dienststelle erfolgreich gelöscht' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Löschen der Dienststelle:', error);
|
||||||
|
return json({ error: 'Fehler beim Löschen der Dienststelle' }, { status: 500 });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
// src/routes/api/admin/logout/+server.ts
|
||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ cookies }) => {
|
export const POST: RequestHandler = async ({ cookies }) => {
|
||||||
cookies.delete('admin_session', { path: '/' });
|
// Cookie löschen mit korrektem Namen
|
||||||
return new Response('Ausgeloggt');
|
cookies.delete('admin-auth', { path: '/' });
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ success: true, message: 'Erfolgreich ausgeloggt' }),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user