pdf erforderlich, decrement wenn Dienstelle zugewiesen wurde
This commit is contained in:
14
prisma/migrations/20250726135031_on_delete/migration.sql
Normal file
14
prisma/migrations/20250726135031_on_delete/migration.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_PdfDatei" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"pfad" TEXT NOT NULL,
|
||||||
|
"anmeldungId" INTEGER NOT NULL,
|
||||||
|
CONSTRAINT "PdfDatei_anmeldungId_fkey" FOREIGN KEY ("anmeldungId") REFERENCES "anmeldungen" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_PdfDatei" ("anmeldungId", "id", "pfad") SELECT "anmeldungId", "id", "pfad" FROM "PdfDatei";
|
||||||
|
DROP TABLE "PdfDatei";
|
||||||
|
ALTER TABLE "new_PdfDatei" RENAME TO "PdfDatei";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `processedBy` on the `anmeldungen` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_anmeldungen" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"anrede" TEXT NOT NULL,
|
||||||
|
"vorname" TEXT NOT NULL,
|
||||||
|
"nachname" TEXT NOT NULL,
|
||||||
|
"geburtsdatum" TEXT NOT NULL,
|
||||||
|
"strasse" TEXT NOT NULL,
|
||||||
|
"hausnummer" TEXT NOT NULL,
|
||||||
|
"ort" TEXT NOT NULL,
|
||||||
|
"plz" TEXT NOT NULL,
|
||||||
|
"telefon" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"schulart" TEXT NOT NULL,
|
||||||
|
"schulklasse" TEXT,
|
||||||
|
"noteDeutsch" INTEGER NOT NULL,
|
||||||
|
"noteMathe" INTEGER NOT NULL,
|
||||||
|
"sozialverhalten" TEXT,
|
||||||
|
"motivation" TEXT,
|
||||||
|
"alter" INTEGER,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'OFFEN',
|
||||||
|
"processedAt" DATETIME,
|
||||||
|
"praktikumId" INTEGER,
|
||||||
|
"zugewiesenId" INTEGER,
|
||||||
|
"wunsch1Id" INTEGER,
|
||||||
|
"wunsch2Id" INTEGER,
|
||||||
|
"wunsch3Id" INTEGER,
|
||||||
|
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "anmeldungen_praktikumId_fkey" FOREIGN KEY ("praktikumId") REFERENCES "Praktikumszeitraum" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "anmeldungen_zugewiesenId_fkey" FOREIGN KEY ("zugewiesenId") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "anmeldungen_wunsch1Id_fkey" FOREIGN KEY ("wunsch1Id") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "anmeldungen_wunsch2Id_fkey" FOREIGN KEY ("wunsch2Id") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "anmeldungen_wunsch3Id_fkey" FOREIGN KEY ("wunsch3Id") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_anmeldungen" ("alter", "anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "noteDeutsch", "noteMathe", "ort", "plz", "praktikumId", "processedAt", "schulart", "schulklasse", "sozialverhalten", "status", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zugewiesenId") SELECT "alter", "anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "noteDeutsch", "noteMathe", "ort", "plz", "praktikumId", "processedAt", "schulart", "schulklasse", "sozialverhalten", "status", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zugewiesenId" FROM "anmeldungen";
|
||||||
|
DROP TABLE "anmeldungen";
|
||||||
|
ALTER TABLE "new_anmeldungen" RENAME TO "anmeldungen";
|
||||||
|
CREATE INDEX "anmeldungen_status_idx" ON "anmeldungen"("status");
|
||||||
|
CREATE INDEX "anmeldungen_processedAt_idx" ON "anmeldungen"("processedAt");
|
||||||
|
CREATE INDEX "anmeldungen_zugewiesenId_idx" ON "anmeldungen"("zugewiesenId");
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Dienststelle" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"plaetze" INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Dienststelle" ("id", "name", "plaetze") SELECT "id", "name", "plaetze" FROM "Dienststelle";
|
||||||
|
DROP TABLE "Dienststelle";
|
||||||
|
ALTER TABLE "new_Dienststelle" RENAME TO "Dienststelle";
|
||||||
|
CREATE UNIQUE INDEX "Dienststelle_name_key" ON "Dienststelle"("name");
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -25,7 +25,7 @@ model EmailConfig {
|
|||||||
model Dienststelle {
|
model Dienststelle {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String @unique
|
name String @unique
|
||||||
plaetze Int
|
plaetze Int @default(0)
|
||||||
anmeldungenWunsch1 Anmeldung[] @relation("Wunsch1")
|
anmeldungenWunsch1 Anmeldung[] @relation("Wunsch1")
|
||||||
anmeldungenWunsch2 Anmeldung[] @relation("Wunsch2")
|
anmeldungenWunsch2 Anmeldung[] @relation("Wunsch2")
|
||||||
anmeldungenWunsch3 Anmeldung[] @relation("Wunsch3")
|
anmeldungenWunsch3 Anmeldung[] @relation("Wunsch3")
|
||||||
@@ -70,7 +70,7 @@ model Anmeldung {
|
|||||||
status Status @default(OFFEN)
|
status Status @default(OFFEN)
|
||||||
|
|
||||||
// Neue Felder für Status-Tracking
|
// Neue Felder für Status-Tracking
|
||||||
processedBy String? // Wer bearbeitet die Anmeldung
|
// processedBy String? // Wer bearbeitet die Anmeldung
|
||||||
processedAt DateTime? // Wann wurde sie bearbeitet
|
processedAt DateTime? // Wann wurde sie bearbeitet
|
||||||
|
|
||||||
// Praktikumszeitraum Relation
|
// Praktikumszeitraum Relation
|
||||||
@@ -100,6 +100,6 @@ model Anmeldung {
|
|||||||
model PdfDatei {
|
model PdfDatei {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
pfad String
|
pfad String
|
||||||
anmeldung Anmeldung @relation(fields: [anmeldungId], references: [id])
|
anmeldung Anmeldung @relation(fields: [anmeldungId], references: [id], onDelete: Cascade)
|
||||||
anmeldungId Int
|
anmeldungId Int
|
||||||
}
|
}
|
||||||
@@ -33,12 +33,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vereinfachte Logik ohne processing Status
|
||||||
function canBeAccepted(status: string): boolean {
|
function canBeAccepted(status: string): boolean {
|
||||||
return status === 'pending' || status === 'processing';
|
return status === 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
function canBeRejected(status: string): boolean {
|
function canBeRejected(status: string): boolean {
|
||||||
return status === 'pending' || status === 'processing';
|
return status === 'pending';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -46,62 +47,51 @@
|
|||||||
<table class="min-w-full divide-y divide-gray-200">
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="w-24 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="min-w-0 w-1/4 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Bewerber/in
|
Bewerber/in
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="w-16 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Noten
|
Noten
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="min-w-0 w-1/3 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Wünsche
|
Wünsche / Zuweisung
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="w-32 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Zugewiesen
|
|
||||||
</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Eingegangen
|
Eingegangen
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th class="w-40 px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Bearbeitet
|
|
||||||
</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Aktionen
|
Aktionen
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<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" class:bg-blue-50={anmeldung.status === 'processing'}>
|
<tr class="hover:bg-gray-50">
|
||||||
<!-- Status -->
|
<!-- Status (processing Styling entfernt) -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-4 py-4 whitespace-nowrap">
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {getStatusColor(anmeldung.status || 'pending')}">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {getStatusColor(anmeldung.status || 'pending')}">
|
||||||
{getStatusText(anmeldung.status || 'pending')}
|
{getStatusText(anmeldung.status || 'pending')}
|
||||||
</span>
|
</span>
|
||||||
{#if anmeldung.status === 'processing' && anmeldung.processedBy}
|
|
||||||
<div class="text-xs text-gray-500 mt-1">
|
|
||||||
von {anmeldung.processedBy}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Bewerber/in -->
|
<!-- Bewerber/in -->
|
||||||
<td class="px-6 py-4">
|
<td class="px-4 py-4">
|
||||||
<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">
|
<div class="text-sm text-gray-500 break-all">
|
||||||
{anmeldung.email}
|
{anmeldung.email}
|
||||||
</div>
|
</div>
|
||||||
{#if anmeldung.pdfs && anmeldung.pdfs.length > 0}
|
{#if anmeldung.pdfs && anmeldung.pdfs.length > 0}
|
||||||
<div class="mt-1">
|
<div class="mt-2">
|
||||||
{#each anmeldung.pdfs as pdf}
|
{#each anmeldung.pdfs as pdf}
|
||||||
<a
|
<a
|
||||||
href="/api/admin/pdf?path={encodeURIComponent(pdf.pfad)}"
|
href="/api/admin/pdf?path={encodeURIComponent(pdf.pfad)}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="inline-flex items-center text-xs text-blue-600 hover:text-blue-800 mr-2"
|
class="inline-flex items-center text-xs text-blue-600 hover:text-blue-800 mr-2 mb-1"
|
||||||
>
|
>
|
||||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
||||||
@@ -114,14 +104,14 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Noten -->
|
<!-- Noten -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
{#if anmeldung.noteDeutsch || anmeldung.noteMathe}
|
{#if anmeldung.noteDeutsch || anmeldung.noteMathe}
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
{#if anmeldung.noteDeutsch}
|
{#if anmeldung.noteDeutsch}
|
||||||
<div>D: {anmeldung.noteDeutsch}</div>
|
<div class="text-xs">D: {anmeldung.noteDeutsch}</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if anmeldung.noteMathe}
|
{#if anmeldung.noteMathe}
|
||||||
<div>M: {anmeldung.noteMathe}</div>
|
<div class="text-xs">M: {anmeldung.noteMathe}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -134,77 +124,79 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Wünsche -->
|
<!-- Wünsche / Zuweisung -->
|
||||||
<td class="px-6 py-4 text-sm text-gray-900">
|
<td class="px-4 py-4 text-sm text-gray-900">
|
||||||
<div class="space-y-1">
|
<!-- Zugewiesene Dienststelle (falls vorhanden) -->
|
||||||
{#if anmeldung.wunsch1}
|
{#if anmeldung.assignedDienststelle}
|
||||||
|
<div class="mb-3 p-2 bg-green-50 border border-green-200 rounded-md">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-flex items-center justify-center w-4 h-4 text-xs font-medium text-white bg-blue-600 rounded-full mr-2">1</span>
|
<svg class="w-4 h-4 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
{anmeldung.wunsch1.name}
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium text-green-800 text-sm">Zugewiesen:</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-green-700 mt-1 ml-6">
|
||||||
|
{anmeldung.assignedDienststelle.name}
|
||||||
|
</div>
|
||||||
|
{#if anmeldung.processedAt}
|
||||||
|
<div class="text-xs text-green-600 mt-1 ml-6">
|
||||||
|
{formatProcessedDate(anmeldung.processedAt)}
|
||||||
|
{#if anmeldung.processedBy}
|
||||||
|
von {anmeldung.processedBy}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Wünsche -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="text-xs font-medium text-gray-500 uppercase tracking-wider">Wünsche:</div>
|
||||||
|
{#if anmeldung.wunsch1}
|
||||||
|
<div class="flex items-start">
|
||||||
|
<span class="inline-flex items-center justify-center w-5 h-5 text-xs font-medium text-white bg-blue-600 rounded-full mr-2 mt-0.5 flex-shrink-0">1</span>
|
||||||
|
<span class="text-sm leading-5">{anmeldung.wunsch1.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if anmeldung.wunsch2}
|
{#if anmeldung.wunsch2}
|
||||||
<div class="flex items-center">
|
<div class="flex items-start">
|
||||||
<span class="inline-flex items-center justify-center w-4 h-4 text-xs font-medium text-white bg-blue-500 rounded-full mr-2">2</span>
|
<span class="inline-flex items-center justify-center w-5 h-5 text-xs font-medium text-white bg-blue-500 rounded-full mr-2 mt-0.5 flex-shrink-0">2</span>
|
||||||
{anmeldung.wunsch2.name}
|
<span class="text-sm leading-5">{anmeldung.wunsch2.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if anmeldung.wunsch3}
|
{#if anmeldung.wunsch3}
|
||||||
<div class="flex items-center">
|
<div class="flex items-start">
|
||||||
<span class="inline-flex items-center justify-center w-4 h-4 text-xs font-medium text-white bg-blue-400 rounded-full mr-2">3</span>
|
<span class="inline-flex items-center justify-center w-5 h-5 text-xs font-medium text-white bg-blue-400 rounded-full mr-2 mt-0.5 flex-shrink-0">3</span>
|
||||||
{anmeldung.wunsch3.name}
|
<span class="text-sm leading-5">{anmeldung.wunsch3.name}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if !anmeldung.wunsch1 && !anmeldung.wunsch2 && !anmeldung.wunsch3}
|
||||||
|
<span class="text-gray-400 text-sm">Keine Wünsche angegeben</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Zugewiesen -->
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
||||||
{#if anmeldung.assignedDienststelle}
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
<span class="font-medium">{anmeldung.assignedDienststelle.name}</span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<span class="text-gray-400">-</span>
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Eingegangen -->
|
<!-- Eingegangen -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
{formatDate(anmeldung.timestamp)}
|
<div class="text-sm">{formatDate(anmeldung.timestamp)}</div>
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Bearbeitet -->
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
||||||
<div>{formatProcessedDate(anmeldung.processedAt)}</div>
|
|
||||||
{#if anmeldung.processedBy && anmeldung.processedAt}
|
|
||||||
<div class="text-xs text-gray-400">
|
|
||||||
von {anmeldung.processedBy}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Aktionen -->
|
<!-- Aktionen -->
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
<td class="px-4 py-4 whitespace-nowrap text-sm font-medium">
|
||||||
<div class="flex space-x-2">
|
<div class="flex flex-col space-y-2">
|
||||||
{#if canBeAccepted(anmeldung.status || 'pending')}
|
{#if canBeAccepted(anmeldung.status || 'pending')}
|
||||||
<button
|
<button
|
||||||
on:click={() => dispatch('accept', { id: anmeldung.id })}
|
on:click={() => dispatch('accept', { id: anmeldung.id })}
|
||||||
class="text-green-600 hover:text-green-900 px-2 py-1 rounded text-xs font-medium border border-green-200 hover:bg-green-50"
|
class="text-green-600 hover:text-green-900 px-3 py-1 rounded text-xs font-medium border border-green-200 hover:bg-green-50 w-full text-center"
|
||||||
class:opacity-50={anmeldung.status === 'processing'}
|
|
||||||
class:cursor-not-allowed={anmeldung.status === 'processing'}
|
|
||||||
>
|
>
|
||||||
{anmeldung.status === 'processing' ? 'Wird bearbeitet' : 'Annehmen'}
|
Annehmen
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if canBeRejected(anmeldung.status || 'pending')}
|
{#if canBeRejected(anmeldung.status || 'pending')}
|
||||||
<button
|
<button
|
||||||
on:click={() => dispatch('reject', { id: anmeldung.id })}
|
on:click={() => dispatch('reject', { id: anmeldung.id })}
|
||||||
class="text-red-600 hover:text-red-900 px-2 py-1 rounded text-xs font-medium border border-red-200 hover:bg-red-50"
|
class="text-red-600 hover:text-red-900 px-3 py-1 rounded text-xs font-medium border border-red-200 hover:bg-red-50 w-full text-center"
|
||||||
>
|
>
|
||||||
Ablehnen
|
Ablehnen
|
||||||
</button>
|
</button>
|
||||||
@@ -212,21 +204,11 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={() => dispatch('delete', { id: anmeldung.id })}
|
on:click={() => dispatch('delete', { id: anmeldung.id })}
|
||||||
class="text-gray-600 hover:text-gray-900 px-2 py-1 rounded text-xs font-medium border border-gray-200 hover:bg-gray-50"
|
class="text-gray-600 hover:text-gray-900 px-3 py-1 rounded text-xs font-medium border border-gray-200 hover:bg-gray-50 w-full text-center"
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Warnung bei "In Bearbeitung" Status -->
|
|
||||||
{#if anmeldung.status === 'processing'}
|
|
||||||
<div class="mt-2 flex items-center text-xs text-amber-600">
|
|
||||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
Wird bereits bearbeitet
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
wunsch3?: { id: number; name: string };
|
wunsch3?: { id: number; name: string };
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
id: number;
|
id: number;
|
||||||
status?: 'pending' | 'accepted' | 'rejected' | 'processing'; // Neuer Status
|
status?: 'pending' | 'accepted' | 'rejected'; // processing entfernt
|
||||||
assignedDienststelle?: { id: number; name: string }; // Zugewiesene Dienststelle
|
assignedDienststelle?: { id: number; name: string };
|
||||||
processedBy?: string; // Wer die Anmeldung bearbeitet
|
processedBy?: string;
|
||||||
processedAt?: number; // Wann bearbeitet
|
processedAt?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmailConfig {
|
interface EmailConfig {
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
let isLoading = true;
|
let isLoading = true;
|
||||||
let error = '';
|
let error = '';
|
||||||
|
|
||||||
// Filter für Status
|
// Filter für Status (processing entfernt)
|
||||||
let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' | 'processing' = 'all';
|
let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' = 'all';
|
||||||
let filteredAnmeldungen: Anmeldung[] = [];
|
let filteredAnmeldungen: Anmeldung[] = [];
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
@@ -63,11 +63,10 @@ Ihr Praktikumsteam`;
|
|||||||
let isLoadingEmailConfig = false;
|
let isLoadingEmailConfig = false;
|
||||||
let isSavingEmailConfig = false;
|
let isSavingEmailConfig = false;
|
||||||
|
|
||||||
// Status-Badge Funktionen
|
// Status-Badge Funktionen (processing entfernt)
|
||||||
function getStatusColor(status: string): string {
|
function getStatusColor(status: string): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending': return 'bg-yellow-100 text-yellow-800';
|
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 'accepted': return 'bg-green-100 text-green-800';
|
||||||
case 'rejected': return 'bg-red-100 text-red-800';
|
case 'rejected': return 'bg-red-100 text-red-800';
|
||||||
default: return 'bg-gray-100 text-gray-800';
|
default: return 'bg-gray-100 text-gray-800';
|
||||||
@@ -77,7 +76,6 @@ Ihr Praktikumsteam`;
|
|||||||
function getStatusText(status: string): string {
|
function getStatusText(status: string): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending': return 'Offen';
|
case 'pending': return 'Offen';
|
||||||
case 'processing': return 'In Bearbeitung';
|
|
||||||
case 'accepted': return 'Angenommen';
|
case 'accepted': return 'Angenommen';
|
||||||
case 'rejected': return 'Abgelehnt';
|
case 'rejected': return 'Abgelehnt';
|
||||||
default: return 'Unbekannt';
|
default: return 'Unbekannt';
|
||||||
@@ -94,7 +92,7 @@ Ihr Praktikumsteam`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
filterAnmeldungen();
|
anmeldungen, statusFilter, filterAnmeldungen();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAnmeldungen() {
|
async function loadAnmeldungen() {
|
||||||
@@ -105,42 +103,31 @@ Ihr Praktikumsteam`;
|
|||||||
const res = await fetch('/api/admin/anmeldungen');
|
const res = await fetch('/api/admin/anmeldungen');
|
||||||
|
|
||||||
if (!res.ok) {
|
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();
|
const data = await res.json();
|
||||||
// Standardstatus setzen falls nicht vorhanden
|
|
||||||
anmeldungen = anmeldungen.map(a => ({ ...a, status: a.status || 'pending' }));
|
// 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) {
|
} catch (err) {
|
||||||
error = err instanceof Error ? err.message : 'Unbekannter Fehler';
|
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 {
|
} finally {
|
||||||
isLoading = false;
|
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() {
|
async function loadEmailConfig() {
|
||||||
try {
|
try {
|
||||||
isLoadingEmailConfig = true;
|
isLoadingEmailConfig = true;
|
||||||
@@ -193,16 +180,6 @@ Ihr Praktikumsteam`;
|
|||||||
const anmeldung = anmeldungen.find(a => a.id === event.detail.id);
|
const anmeldung = anmeldungen.find(a => a.id === event.detail.id);
|
||||||
if (!anmeldung) return;
|
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 = [
|
availableWishes = [
|
||||||
anmeldung.wunsch1 && { id: anmeldung.wunsch1.id, name: `1. Wunsch: ${anmeldung.wunsch1.name}` },
|
anmeldung.wunsch1 && { id: anmeldung.wunsch1.id, name: `1. Wunsch: ${anmeldung.wunsch1.name}` },
|
||||||
anmeldung.wunsch2 && { id: anmeldung.wunsch2.id, name: `2. Wunsch: ${anmeldung.wunsch2.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}>) {
|
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;
|
if (!confirm('Diese Anmeldung wirklich ablehnen?')) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -316,10 +284,6 @@ Ihr Praktikumsteam`;
|
|||||||
function closeDialog() {
|
function closeDialog() {
|
||||||
showDialog = false;
|
showDialog = false;
|
||||||
selectedAnmeldungId = null;
|
selectedAnmeldungId = null;
|
||||||
// Status zurücksetzen falls Dialog abgebrochen wird
|
|
||||||
if (selectedAnmeldungId) {
|
|
||||||
// Hier könnten Sie den Status zurück auf "pending" setzen
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -341,7 +305,7 @@ Ihr Praktikumsteam`;
|
|||||||
<main class="max-w-7xl mx-auto px-4 py-6">
|
<main class="max-w-7xl mx-auto px-4 py-6">
|
||||||
<!-- Filter und E-Mail Konfiguration -->
|
<!-- Filter und E-Mail Konfiguration -->
|
||||||
<div class="mb-6 flex justify-between items-center">
|
<div class="mb-6 flex justify-between items-center">
|
||||||
<!-- Status Filter -->
|
<!-- Status Filter (processing entfernt) -->
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<label for="status-filter" class="text-sm font-medium text-gray-700">Filter:</label>
|
<label for="status-filter" class="text-sm font-medium text-gray-700">Filter:</label>
|
||||||
<select
|
<select
|
||||||
@@ -351,7 +315,6 @@ Ihr Praktikumsteam`;
|
|||||||
>
|
>
|
||||||
<option value="all">Alle ({anmeldungen.length})</option>
|
<option value="all">Alle ({anmeldungen.length})</option>
|
||||||
<option value="pending">Offen ({anmeldungen.filter(a => (a.status || 'pending') === 'pending').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="accepted">Angenommen ({anmeldungen.filter(a => a.status === 'accepted').length})</option>
|
||||||
<option value="rejected">Abgelehnt ({anmeldungen.filter(a => a.status === 'rejected').length})</option>
|
<option value="rejected">Abgelehnt ({anmeldungen.filter(a => a.status === 'rejected').length})</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -374,8 +337,8 @@ Ihr Praktikumsteam`;
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Übersicht -->
|
<!-- Status Übersicht (processing entfernt) -->
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
<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="bg-white rounded-lg border border-gray-200 p-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
@@ -394,24 +357,6 @@ Ihr Praktikumsteam`;
|
|||||||
</div>
|
</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">
|
|
||||||
<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="bg-white rounded-lg border border-gray-200 p-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<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 { json } from '@sveltejs/kit';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
@@ -17,59 +17,60 @@ export async function GET() {
|
|||||||
pdfs: true
|
pdfs: true
|
||||||
},
|
},
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{
|
|
||||||
status: 'asc' // BEARBEITUNG zuerst, dann OFFEN, etc.
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
timestamp: 'desc'
|
timestamp: 'desc'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Daten für Frontend formatieren
|
|
||||||
const formattedAnmeldungen = anmeldungen.map(anmeldung => ({
|
const formattedAnmeldungen = anmeldungen.map(anmeldung => ({
|
||||||
id: anmeldung.id,
|
id: anmeldung.id,
|
||||||
anrede: anmeldung.anrede,
|
anrede: anmeldung.anrede,
|
||||||
vorname: anmeldung.vorname,
|
vorname: anmeldung.vorname,
|
||||||
nachname: anmeldung.nachname,
|
nachname: anmeldung.nachname,
|
||||||
email: anmeldung.email,
|
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,
|
sozialverhalten: anmeldung.sozialverhalten,
|
||||||
|
|
||||||
// Status-Mapping für Frontend
|
// Status-Mapping für Frontend
|
||||||
status: mapPrismaStatusToFrontend(anmeldung.status),
|
status: mapPrismaStatusToFrontend(anmeldung.status),
|
||||||
processedBy: anmeldung.processedBy,
|
// processedBy: anmeldung.processedBy,
|
||||||
processedAt: anmeldung.processedAt?.getTime(),
|
processedAt: anmeldung.processedAt ? anmeldung.processedAt.getTime() : undefined,
|
||||||
|
|
||||||
// Wünsche
|
// Wünsche - sicherstellen dass sie existieren
|
||||||
wunsch1: anmeldung.wunsch1 ? {
|
wunsch1: anmeldung.wunsch1 ? {
|
||||||
id: anmeldung.wunsch1.id,
|
id: anmeldung.wunsch1.id,
|
||||||
name: anmeldung.wunsch1.name
|
name: anmeldung.wunsch1.name
|
||||||
} : null,
|
} : undefined,
|
||||||
wunsch2: anmeldung.wunsch2 ? {
|
wunsch2: anmeldung.wunsch2 ? {
|
||||||
id: anmeldung.wunsch2.id,
|
id: anmeldung.wunsch2.id,
|
||||||
name: anmeldung.wunsch2.name
|
name: anmeldung.wunsch2.name
|
||||||
} : null,
|
} : undefined,
|
||||||
wunsch3: anmeldung.wunsch3 ? {
|
wunsch3: anmeldung.wunsch3 ? {
|
||||||
id: anmeldung.wunsch3.id,
|
id: anmeldung.wunsch3.id,
|
||||||
name: anmeldung.wunsch3.name
|
name: anmeldung.wunsch3.name
|
||||||
} : null,
|
} : undefined,
|
||||||
|
|
||||||
// Zugewiesene Dienststelle
|
// Zugewiesene Dienststelle
|
||||||
assignedDienststelle: anmeldung.zugewiesen ? {
|
assignedDienststelle: anmeldung.zugewiesen ? {
|
||||||
id: anmeldung.zugewiesen.id,
|
id: anmeldung.zugewiesen.id,
|
||||||
name: anmeldung.zugewiesen.name
|
name: anmeldung.zugewiesen.name
|
||||||
} : null,
|
} : undefined,
|
||||||
|
|
||||||
timestamp: anmeldung.timestamp.getTime(),
|
// Timestamp
|
||||||
pdfs: anmeldung.pdfs
|
timestamp: anmeldung.timestamp ? anmeldung.timestamp.getTime() : Date.now(),
|
||||||
|
|
||||||
|
// PDFs
|
||||||
|
pdfs: anmeldung.pdfs || []
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return json(formattedAnmeldungen);
|
return json(formattedAnmeldungen);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Laden der Anmeldungen:', 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 });
|
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({
|
const existingAnmeldung = await prisma.anmeldung.findUnique({
|
||||||
where: { id }
|
where: { id }
|
||||||
});
|
});
|
||||||
@@ -96,27 +97,37 @@ export async function POST({ request, url }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Anmeldung als angenommen markieren
|
// Anmeldung als angenommen markieren
|
||||||
await prisma.anmeldung.update({
|
await prisma.$transaction([
|
||||||
where: { id },
|
prisma.anmeldung.update({
|
||||||
data: {
|
where: { id },
|
||||||
status: 'ANGENOMMEN',
|
data: {
|
||||||
zugewiesenId: dienststelleId,
|
status: 'ANGENOMMEN',
|
||||||
processedBy: 'current_user', // TODO: Echten Benutzer verwenden
|
zugewiesenId: dienststelleId,
|
||||||
processedAt: new Date()
|
processedAt: new Date()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
prisma.dienststelle.update({
|
||||||
|
where: { id: dienststelleId },
|
||||||
|
data: {
|
||||||
|
plaetze: {
|
||||||
|
decrement: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
return json({ success: true });
|
return json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Annehmen der Anmeldung:', 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 }) {
|
export async function PATCH({ request, url }) {
|
||||||
try {
|
try {
|
||||||
const id = parseInt(url.searchParams.get('id') || '0');
|
const id = parseInt(url.searchParams.get('id') || '0');
|
||||||
const { action, processedBy } = await request.json();
|
const { action } = await request.json();
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return json({ error: 'ID erforderlich' }, { status: 400 });
|
return json({ error: 'ID erforderlich' }, { status: 400 });
|
||||||
@@ -128,28 +139,21 @@ export async function PATCH({ request, url }) {
|
|||||||
case 'reject':
|
case 'reject':
|
||||||
updateData = {
|
updateData = {
|
||||||
status: 'ABGELEHNT',
|
status: 'ABGELEHNT',
|
||||||
processedBy: 'current_user', // TODO: Echten Benutzer verwenden
|
|
||||||
processedAt: new Date()
|
processedAt: new Date()
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'set_processing':
|
case 'set_processing':
|
||||||
// Nur setzen wenn noch OFFEN
|
|
||||||
const anmeldung = await prisma.anmeldung.findUnique({
|
const anmeldung = await prisma.anmeldung.findUnique({
|
||||||
where: { id }
|
where: { id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!anmeldung) {
|
if (!anmeldung) {
|
||||||
return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
|
return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anmeldung.status !== 'OFFEN') {
|
|
||||||
return json({ error: 'Anmeldung kann nicht mehr bearbeitet werden' }, { status: 409 });
|
|
||||||
}
|
|
||||||
|
|
||||||
updateData = {
|
updateData = {
|
||||||
status: 'BEARBEITUNG',
|
status: 'BEARBEITUNG',
|
||||||
processedBy: processedBy || 'current_user',
|
|
||||||
processedAt: new Date()
|
processedAt: new Date()
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -157,7 +161,6 @@ export async function PATCH({ request, url }) {
|
|||||||
case 'reset_processing':
|
case 'reset_processing':
|
||||||
updateData = {
|
updateData = {
|
||||||
status: 'OFFEN',
|
status: 'OFFEN',
|
||||||
processedBy: null,
|
|
||||||
processedAt: null
|
processedAt: null
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -166,7 +169,7 @@ export async function PATCH({ request, url }) {
|
|||||||
return json({ error: 'Unbekannte Aktion' }, { status: 400 });
|
return json({ error: 'Unbekannte Aktion' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await prisma.anmeldung.update({
|
await prisma.anmeldung.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: updateData
|
data: updateData
|
||||||
});
|
});
|
||||||
@@ -174,7 +177,7 @@ export async function PATCH({ request, url }) {
|
|||||||
return json({ success: true });
|
return json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Aktualisieren der Anmeldung:', 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 });
|
return json({ success: true });
|
||||||
} 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: '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';
|
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 pdfs = formData.getAll('pdfs') as File[];
|
||||||
const pdfFiles = 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 pdfData = [];
|
||||||
|
|
||||||
// const gespeichertePfade: string[] = [];
|
// const gespeichertePfade: string[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user