pdf erforderlich, decrement wenn Dienstelle zugewiesen wurde
This commit is contained in:
@@ -20,10 +20,10 @@
|
||||
wunsch3?: { id: number; name: string };
|
||||
timestamp: number;
|
||||
id: number;
|
||||
status?: 'pending' | 'accepted' | 'rejected' | 'processing'; // Neuer Status
|
||||
assignedDienststelle?: { id: number; name: string }; // Zugewiesene Dienststelle
|
||||
processedBy?: string; // Wer die Anmeldung bearbeitet
|
||||
processedAt?: number; // Wann bearbeitet
|
||||
status?: 'pending' | 'accepted' | 'rejected'; // processing entfernt
|
||||
assignedDienststelle?: { id: number; name: string };
|
||||
processedBy?: string;
|
||||
processedAt?: number;
|
||||
}
|
||||
|
||||
interface EmailConfig {
|
||||
@@ -35,8 +35,8 @@
|
||||
let isLoading = true;
|
||||
let error = '';
|
||||
|
||||
// Filter für Status
|
||||
let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' | 'processing' = 'all';
|
||||
// Filter für Status (processing entfernt)
|
||||
let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' = 'all';
|
||||
let filteredAnmeldungen: Anmeldung[] = [];
|
||||
|
||||
// Dialog state
|
||||
@@ -63,11 +63,10 @@ Ihr Praktikumsteam`;
|
||||
let isLoadingEmailConfig = false;
|
||||
let isSavingEmailConfig = false;
|
||||
|
||||
// Status-Badge Funktionen
|
||||
// Status-Badge Funktionen (processing entfernt)
|
||||
function getStatusColor(status: string): string {
|
||||
switch (status) {
|
||||
case 'pending': return 'bg-yellow-100 text-yellow-800';
|
||||
case 'processing': return 'bg-blue-100 text-blue-800';
|
||||
case 'accepted': return 'bg-green-100 text-green-800';
|
||||
case 'rejected': return 'bg-red-100 text-red-800';
|
||||
default: return 'bg-gray-100 text-gray-800';
|
||||
@@ -77,7 +76,6 @@ Ihr Praktikumsteam`;
|
||||
function getStatusText(status: string): string {
|
||||
switch (status) {
|
||||
case 'pending': return 'Offen';
|
||||
case 'processing': return 'In Bearbeitung';
|
||||
case 'accepted': return 'Angenommen';
|
||||
case 'rejected': return 'Abgelehnt';
|
||||
default: return 'Unbekannt';
|
||||
@@ -94,7 +92,7 @@ Ihr Praktikumsteam`;
|
||||
}
|
||||
|
||||
$: {
|
||||
filterAnmeldungen();
|
||||
anmeldungen, statusFilter, filterAnmeldungen();
|
||||
}
|
||||
|
||||
async function loadAnmeldungen() {
|
||||
@@ -105,42 +103,31 @@ Ihr Praktikumsteam`;
|
||||
const res = await fetch('/api/admin/anmeldungen');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Fehler beim Laden: ${res.status}`);
|
||||
const errorText = await res.text();
|
||||
console.error('❌ API Fehler:', res.status, errorText);
|
||||
throw new Error(`Fehler beim Laden: ${res.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
anmeldungen = await res.json();
|
||||
// Standardstatus setzen falls nicht vorhanden
|
||||
anmeldungen = anmeldungen.map(a => ({ ...a, status: a.status || 'pending' }));
|
||||
const data = await res.json();
|
||||
|
||||
// Prüfen ob es ein Array ist
|
||||
if (!Array.isArray(data)) {
|
||||
console.error('❌ Antwort ist kein Array:', data);
|
||||
throw new Error('Antwort vom Server ist kein Array');
|
||||
}
|
||||
|
||||
anmeldungen = data.map(a => {
|
||||
return { ...a, status: a.status || 'pending' };
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Unbekannter Fehler';
|
||||
console.error('Fehler beim Laden der Anmeldungen:', err);
|
||||
console.error('❌ Frontend Fehler beim Laden der Anmeldungen:', err);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function setProcessingStatus(anmeldungId: number) {
|
||||
try {
|
||||
const res = await fetch(`/api/admin/anmeldungen?id=${anmeldungId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'set_processing',
|
||||
processedBy: 'current_user' // Hier sollte der aktuelle Benutzer stehen
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Fehler beim Setzen des Status: ${res.status}`);
|
||||
}
|
||||
|
||||
await loadAnmeldungen();
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Fehler beim Setzen des Status';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEmailConfig() {
|
||||
try {
|
||||
isLoadingEmailConfig = true;
|
||||
@@ -193,16 +180,6 @@ Ihr Praktikumsteam`;
|
||||
const anmeldung = anmeldungen.find(a => a.id === event.detail.id);
|
||||
if (!anmeldung) return;
|
||||
|
||||
// Prüfen ob bereits bearbeitet wird
|
||||
if (anmeldung.status === 'processing') {
|
||||
if (!confirm('Diese Anmeldung wird bereits bearbeitet. Trotzdem fortfahren?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Status auf "in Bearbeitung" setzen
|
||||
setProcessingStatus(event.detail.id);
|
||||
|
||||
availableWishes = [
|
||||
anmeldung.wunsch1 && { id: anmeldung.wunsch1.id, name: `1. Wunsch: ${anmeldung.wunsch1.name}` },
|
||||
anmeldung.wunsch2 && { id: anmeldung.wunsch2.id, name: `2. Wunsch: ${anmeldung.wunsch2.name}` },
|
||||
@@ -265,15 +242,6 @@ Ihr Praktikumsteam`;
|
||||
}
|
||||
|
||||
async function handleReject(event: CustomEvent<{id: number}>) {
|
||||
const anmeldung = anmeldungen.find(a => a.id === event.detail.id);
|
||||
|
||||
// Prüfen ob bereits bearbeitet wird
|
||||
if (anmeldung?.status === 'processing') {
|
||||
if (!confirm('Diese Anmeldung wird bereits bearbeitet. Trotzdem ablehnen?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!confirm('Diese Anmeldung wirklich ablehnen?')) return;
|
||||
|
||||
try {
|
||||
@@ -316,10 +284,6 @@ Ihr Praktikumsteam`;
|
||||
function closeDialog() {
|
||||
showDialog = false;
|
||||
selectedAnmeldungId = null;
|
||||
// Status zurücksetzen falls Dialog abgebrochen wird
|
||||
if (selectedAnmeldungId) {
|
||||
// Hier könnten Sie den Status zurück auf "pending" setzen
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -341,7 +305,7 @@ Ihr Praktikumsteam`;
|
||||
<main class="max-w-7xl mx-auto px-4 py-6">
|
||||
<!-- Filter und E-Mail Konfiguration -->
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<!-- Status Filter -->
|
||||
<!-- Status Filter (processing entfernt) -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="status-filter" class="text-sm font-medium text-gray-700">Filter:</label>
|
||||
<select
|
||||
@@ -351,7 +315,6 @@ Ihr Praktikumsteam`;
|
||||
>
|
||||
<option value="all">Alle ({anmeldungen.length})</option>
|
||||
<option value="pending">Offen ({anmeldungen.filter(a => (a.status || 'pending') === 'pending').length})</option>
|
||||
<option value="processing">In Bearbeitung ({anmeldungen.filter(a => a.status === 'processing').length})</option>
|
||||
<option value="accepted">Angenommen ({anmeldungen.filter(a => a.status === 'accepted').length})</option>
|
||||
<option value="rejected">Abgelehnt ({anmeldungen.filter(a => a.status === 'rejected').length})</option>
|
||||
</select>
|
||||
@@ -374,8 +337,8 @@ Ihr Praktikumsteam`;
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Status Übersicht -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<!-- Status Übersicht (processing entfernt) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
@@ -394,24 +357,6 @@ Ihr Praktikumsteam`;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">In Bearbeitung</p>
|
||||
<p class="text-2xl font-semibold text-gray-900">
|
||||
{anmeldungen.filter(a => a.status === 'processing').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/routes/api/admin/anmeldungen/+server.js
|
||||
// src/routes/api/admin/anmeldungen/+server.ts
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
@@ -17,59 +17,60 @@ export async function GET() {
|
||||
pdfs: true
|
||||
},
|
||||
orderBy: [
|
||||
{
|
||||
status: 'asc' // BEARBEITUNG zuerst, dann OFFEN, etc.
|
||||
},
|
||||
{
|
||||
timestamp: 'desc'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Daten für Frontend formatieren
|
||||
const formattedAnmeldungen = anmeldungen.map(anmeldung => ({
|
||||
id: anmeldung.id,
|
||||
anrede: anmeldung.anrede,
|
||||
vorname: anmeldung.vorname,
|
||||
nachname: anmeldung.nachname,
|
||||
email: anmeldung.email,
|
||||
noteDeutsch: anmeldung.noteDeutsch?.toString(),
|
||||
noteMathe: anmeldung.noteMathe?.toString(),
|
||||
|
||||
// Noten als String konvertieren (falls sie als Int gespeichert sind)
|
||||
noteDeutsch: anmeldung.noteDeutsch ? anmeldung.noteDeutsch.toString() : undefined,
|
||||
noteMathe: anmeldung.noteMathe ? anmeldung.noteMathe.toString() : undefined,
|
||||
sozialverhalten: anmeldung.sozialverhalten,
|
||||
|
||||
// Status-Mapping für Frontend
|
||||
status: mapPrismaStatusToFrontend(anmeldung.status),
|
||||
processedBy: anmeldung.processedBy,
|
||||
processedAt: anmeldung.processedAt?.getTime(),
|
||||
// processedBy: anmeldung.processedBy,
|
||||
processedAt: anmeldung.processedAt ? anmeldung.processedAt.getTime() : undefined,
|
||||
|
||||
// Wünsche
|
||||
// Wünsche - sicherstellen dass sie existieren
|
||||
wunsch1: anmeldung.wunsch1 ? {
|
||||
id: anmeldung.wunsch1.id,
|
||||
name: anmeldung.wunsch1.name
|
||||
} : null,
|
||||
} : undefined,
|
||||
wunsch2: anmeldung.wunsch2 ? {
|
||||
id: anmeldung.wunsch2.id,
|
||||
name: anmeldung.wunsch2.name
|
||||
} : null,
|
||||
} : undefined,
|
||||
wunsch3: anmeldung.wunsch3 ? {
|
||||
id: anmeldung.wunsch3.id,
|
||||
name: anmeldung.wunsch3.name
|
||||
} : null,
|
||||
} : undefined,
|
||||
|
||||
// Zugewiesene Dienststelle
|
||||
assignedDienststelle: anmeldung.zugewiesen ? {
|
||||
id: anmeldung.zugewiesen.id,
|
||||
name: anmeldung.zugewiesen.name
|
||||
} : null,
|
||||
} : undefined,
|
||||
|
||||
timestamp: anmeldung.timestamp.getTime(),
|
||||
pdfs: anmeldung.pdfs
|
||||
// Timestamp
|
||||
timestamp: anmeldung.timestamp ? anmeldung.timestamp.getTime() : Date.now(),
|
||||
|
||||
// PDFs
|
||||
pdfs: anmeldung.pdfs || []
|
||||
}));
|
||||
|
||||
return json(formattedAnmeldungen);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Anmeldungen:', error);
|
||||
return json({ error: 'Fehler beim Laden der Anmeldungen' }, { status: 500 });
|
||||
return json({ error: 'Fehler beim Laden der Anmeldungen', details: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ export async function POST({ request, url }) {
|
||||
return json({ error: 'ID und Dienststelle erforderlich' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Prüfen ob Anmeldung existiert und bearbeitet werden kann
|
||||
// Prüfen ob Anmeldung existiert
|
||||
const existingAnmeldung = await prisma.anmeldung.findUnique({
|
||||
where: { id }
|
||||
});
|
||||
@@ -96,27 +97,37 @@ export async function POST({ request, url }) {
|
||||
}
|
||||
|
||||
// Anmeldung als angenommen markieren
|
||||
await prisma.anmeldung.update({
|
||||
where: { id },
|
||||
data: {
|
||||
status: 'ANGENOMMEN',
|
||||
zugewiesenId: dienststelleId,
|
||||
processedBy: 'current_user', // TODO: Echten Benutzer verwenden
|
||||
processedAt: new Date()
|
||||
await prisma.$transaction([
|
||||
prisma.anmeldung.update({
|
||||
where: { id },
|
||||
data: {
|
||||
status: 'ANGENOMMEN',
|
||||
zugewiesenId: dienststelleId,
|
||||
processedAt: new Date()
|
||||
}
|
||||
}),
|
||||
|
||||
prisma.dienststelle.update({
|
||||
where: { id: dienststelleId },
|
||||
data: {
|
||||
plaetze: {
|
||||
decrement: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
]);
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Annehmen der Anmeldung:', error);
|
||||
return json({ error: 'Fehler beim Annehmen der Anmeldung' }, { status: 500 });
|
||||
return json({ error: 'Fehler beim Annehmen der Anmeldung', details: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH({ request, url }) {
|
||||
try {
|
||||
const id = parseInt(url.searchParams.get('id') || '0');
|
||||
const { action, processedBy } = await request.json();
|
||||
const { action } = await request.json();
|
||||
|
||||
if (!id) {
|
||||
return json({ error: 'ID erforderlich' }, { status: 400 });
|
||||
@@ -128,28 +139,21 @@ export async function PATCH({ request, url }) {
|
||||
case 'reject':
|
||||
updateData = {
|
||||
status: 'ABGELEHNT',
|
||||
processedBy: 'current_user', // TODO: Echten Benutzer verwenden
|
||||
processedAt: new Date()
|
||||
};
|
||||
break;
|
||||
|
||||
case 'set_processing':
|
||||
// Nur setzen wenn noch OFFEN
|
||||
const anmeldung = await prisma.anmeldung.findUnique({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
|
||||
if (!anmeldung) {
|
||||
return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (anmeldung.status !== 'OFFEN') {
|
||||
return json({ error: 'Anmeldung kann nicht mehr bearbeitet werden' }, { status: 409 });
|
||||
}
|
||||
|
||||
updateData = {
|
||||
status: 'BEARBEITUNG',
|
||||
processedBy: processedBy || 'current_user',
|
||||
processedAt: new Date()
|
||||
};
|
||||
break;
|
||||
@@ -157,7 +161,6 @@ export async function PATCH({ request, url }) {
|
||||
case 'reset_processing':
|
||||
updateData = {
|
||||
status: 'OFFEN',
|
||||
processedBy: null,
|
||||
processedAt: null
|
||||
};
|
||||
break;
|
||||
@@ -166,7 +169,7 @@ export async function PATCH({ request, url }) {
|
||||
return json({ error: 'Unbekannte Aktion' }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await prisma.anmeldung.update({
|
||||
await prisma.anmeldung.update({
|
||||
where: { id },
|
||||
data: updateData
|
||||
});
|
||||
@@ -174,7 +177,7 @@ export async function PATCH({ request, url }) {
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Anmeldung:', error);
|
||||
return json({ error: 'Fehler beim Aktualisieren der Anmeldung' }, { status: 500 });
|
||||
return json({ error: 'Fehler beim Aktualisieren der Anmeldung', details: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +196,7 @@ export async function DELETE({ url }) {
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen der Anmeldung:', error);
|
||||
return json({ error: 'Fehler beim Löschen der Anmeldung' }, { status: 500 });
|
||||
return json({ error: 'Fehler beim Löschen der Anmeldung', details: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,16 +210,4 @@ function mapPrismaStatusToFrontend(prismaStatus) {
|
||||
};
|
||||
|
||||
return statusMap[prismaStatus] || 'pending';
|
||||
}
|
||||
|
||||
// Hilfsfunktion: Frontend Status zu Prisma Status
|
||||
function mapFrontendStatusToPrisma(frontendStatus) {
|
||||
const statusMap = {
|
||||
'pending': 'OFFEN',
|
||||
'processing': 'BEARBEITUNG',
|
||||
'accepted': 'ANGENOMMEN',
|
||||
'rejected': 'ABGELEHNT'
|
||||
};
|
||||
|
||||
return statusMap[frontendStatus] || 'OFFEN';
|
||||
}
|
||||
@@ -12,6 +12,10 @@ export async function POST({ request }: RequestEvent) {
|
||||
|
||||
// const pdfs = formData.getAll('pdfs') as File[];
|
||||
const pdfFiles = formData.getAll('pdfs') as File[];
|
||||
const hasValidPdf = pdfFiles.some((file) => file.size > 0 && file.type === 'application/pdf');
|
||||
if (!hasValidPdf) {
|
||||
return json({ error: 'Bitte lade das Zeugnis hoch in PDF Format.' }, { status: 400 });
|
||||
}
|
||||
const pdfData = [];
|
||||
|
||||
// const gespeichertePfade: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user