schulart und Noten in fronend. Erste versuch Zeitraum in backend

This commit is contained in:
titver968
2025-05-21 08:31:07 +02:00
parent 4d7d330e93
commit 78942e95e1
10 changed files with 199 additions and 68 deletions

91
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "praktikum",
"version": "0.0.1",
"dependencies": {
"@prisma/client": "^6.7.0",
"@prisma/client": "^6.8.2",
"@sveltejs/adapter-node": "^5.2.12",
"bcryptjs": "^3.0.2"
},
@@ -28,7 +28,7 @@
"postcss": "^8.5.3",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prisma": "^6.7.0",
"prisma": "^6.8.2",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.17",
@@ -859,9 +859,9 @@
"license": "MIT"
},
"node_modules/@prisma/client": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz",
"integrity": "sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.8.2.tgz",
"integrity": "sha512-5II+vbyzv4si6Yunwgkj0qT/iY0zyspttoDrL3R4BYgLdp42/d2C8xdi9vqkrYtKt9H32oFIukvyw3Koz5JoDg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
@@ -881,64 +881,63 @@
}
},
"node_modules/@prisma/config": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz",
"integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.8.2.tgz",
"integrity": "sha512-ZJY1fF4qRBPdLQ/60wxNtX+eu89c3AkYEcP7L3jkp0IPXCNphCYxikTg55kPJLDOG6P0X+QG5tCv6CmsBRZWFQ==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"esbuild": ">=0.12 <1",
"esbuild-register": "3.6.0"
"jiti": "2.4.2"
}
},
"node_modules/@prisma/debug": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz",
"integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz",
"integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz",
"integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.8.2.tgz",
"integrity": "sha512-XqAJ//LXjqYRQ1RRabs79KOY4+v6gZOGzbcwDQl0D6n9WBKjV7qdrbd042CwSK0v0lM9MSHsbcFnU2Yn7z8Zlw==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.7.0",
"@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"@prisma/fetch-engine": "6.7.0",
"@prisma/get-platform": "6.7.0"
"@prisma/debug": "6.8.2",
"@prisma/engines-version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e",
"@prisma/fetch-engine": "6.8.2",
"@prisma/get-platform": "6.8.2"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz",
"integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==",
"version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e.tgz",
"integrity": "sha512-Rkik9lMyHpFNGaLpPF3H5q5TQTkm/aE7DsGM5m92FZTvWQsvmi6Va8On3pWvqLHOt5aPUvFb/FeZTmphI4CPiQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz",
"integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.8.2.tgz",
"integrity": "sha512-lCvikWOgaLOfqXGacEKSNeenvj0n3qR5QvZUOmPE2e1Eh8cMYSobxonCg9rqM6FSdTfbpqp9xwhSAOYfNqSW0g==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.7.0",
"@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"@prisma/get-platform": "6.7.0"
"@prisma/debug": "6.8.2",
"@prisma/engines-version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e",
"@prisma/get-platform": "6.8.2"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz",
"integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz",
"integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.7.0"
"@prisma/debug": "6.8.2"
}
},
"node_modules/@rollup/plugin-commonjs": {
@@ -2547,19 +2546,6 @@
"@esbuild/win32-x64": "0.25.2"
}
},
"node_modules/esbuild-register": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"peerDependencies": {
"esbuild": ">=0.12 <1"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -4223,15 +4209,15 @@
}
},
"node_modules/prisma": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
"integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==",
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.8.2.tgz",
"integrity": "sha512-JNricTXQxzDtRS7lCGGOB4g5DJ91eg3nozdubXze3LpcMl1oWwcFddrj++Up3jnRE6X/3gB/xz3V+ecBk/eEGA==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/config": "6.7.0",
"@prisma/engines": "6.7.0"
"@prisma/config": "6.8.2",
"@prisma/engines": "6.8.2"
},
"bin": {
"prisma": "build/index.js"
@@ -4239,9 +4225,6 @@
"engines": {
"node": ">=18.18"
},
"optionalDependencies": {
"fsevents": "2.3.3"
},
"peerDependencies": {
"typescript": ">=5.1.0"
},

View File

@@ -32,7 +32,7 @@
"postcss": "^8.5.3",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prisma": "^6.7.0",
"prisma": "^6.8.2",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.17",
@@ -43,7 +43,7 @@
"vite-plugin": "^0.0.0"
},
"dependencies": {
"@prisma/client": "^6.7.0",
"@prisma/client": "^6.8.2",
"@sveltejs/adapter-node": "^5.2.12",
"bcryptjs": "^3.0.2"
}

View File

@@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "Praktikumszeitraum" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"bezeichnung" TEXT NOT NULL,
"startDatum" DATETIME NOT NULL,
"endDatum" DATETIME NOT NULL
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Anmeldung" (
"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,
"zeitraum" TEXT NOT NULL,
"motivation" TEXT NOT NULL,
"praktikumId" INTEGER,
"wunsch1Id" INTEGER NOT NULL,
"wunsch2Id" INTEGER NOT NULL,
"wunsch3Id" INTEGER NOT NULL,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Anmeldung_praktikumId_fkey" FOREIGN KEY ("praktikumId") REFERENCES "Praktikumszeitraum" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch1Id_fkey" FOREIGN KEY ("wunsch1Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch2Id_fkey" FOREIGN KEY ("wunsch2Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch3Id_fkey" FOREIGN KEY ("wunsch3Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Anmeldung" ("anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "ort", "plz", "schulart", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zeitraum") SELECT "anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "ort", "plz", "schulart", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zeitraum" FROM "Anmeldung";
DROP TABLE "Anmeldung";
ALTER TABLE "new_Anmeldung" RENAME TO "Anmeldung";
CREATE UNIQUE INDEX "Anmeldung_email_key" ON "Anmeldung"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
-- CreateIndex
CREATE UNIQUE INDEX "Praktikumszeitraum_bezeichnung_key" ON "Praktikumszeitraum"("bezeichnung");

View File

@@ -23,6 +23,14 @@ model Dienststelle {
anmeldungenWunsch3 Anmeldung[] @relation("Wunsch3")
}
model Praktikumszeitraum {
id Int @id @default(autoincrement())
bezeichnung String @unique // z.B. "Frühjahr 2025"
startDatum DateTime
endDatum DateTime
anmeldungen Anmeldung[]
}
model Anmeldung {
id Int @id @default(autoincrement())
anrede String
@@ -36,9 +44,12 @@ model Anmeldung {
telefon String
email String @unique
schulart String
zeitraum String
motivation String
praktikumId Int
praktikum Praktikumszeitraum @relation(fields: [praktikumId], references: [id])
wunsch1Id Int
wunsch2Id Int
wunsch3Id Int

View File

@@ -94,10 +94,18 @@
const deutsch = parseInt(deutschNote);
const mathe = parseInt(matheNote);
if (isNaN(deutsch) || isNaN(mathe) || deutsch > 3 && mathe > 3) {
ablehnungHinweis = 'Du brauchst mindestens eine 3 in Deutsch oder Mathematik, um dich bewerben zu können. Bewirb dich gern erneut, wenn du die Voraussetzung erfüllst.';
showAblehnungModal = true;
return;
if (['Gymnasium', 'Gymnasialzweig'].includes(schulart) ) {
if (isNaN(deutsch) || isNaN(mathe) || deutsch > 4 && mathe > 4) {
ablehnungHinweis = 'Du brauchst mindestens eine 4 in Deutsch oder Mathematik, um dich bewerben zu können. Bewirb dich gern erneut, wenn du die Voraussetzung erfüllst.';
showAblehnungModal = true;
return;
}
} else {
if (isNaN(deutsch) || isNaN(mathe) || deutsch > 3 && mathe > 3) {
ablehnungHinweis = 'Du brauchst mindestens eine 3 in Deutsch oder Mathematik, um dich bewerben zu können. Bewirb dich gern erneut, wenn du die Voraussetzung erfüllst.';
showAblehnungModal = true;
return;
}
}
if (sozialverhalten === 'Entspricht den Erwartungen mit Einschränkungen') {
@@ -151,11 +159,13 @@
<!-- Schulart Dropdown -->
<select bind:value={schulart} required class="input">
<option value="" disabled selected hidden>Schulart wählen</option>
<option value="Mittelschule">Mittelschule</option>
<option value="Realschule">Realschule</option>
<option value="Gymnasium">Gymnasium</option>
<option value="Gymnasialzweig">Gymnasialzweig</option>
<option value="Realschule">Realschule</option>
<option value="FOS">Fachoberschule (FOS)</option>
<option value="BOS">Berufsoberschule (BOS)</option>
<option value="IGS">Integrierte Gesamtschule (IGS)</option>
<option value="KGS">Kooperative Gesamtschule (KGS)</option>
</select>
<!-- Noten -->
<div class="grid grid-cols-2 gap-4">

View File

@@ -33,13 +33,16 @@
<div class="space-y-4">
<h1 class="text-2xl font-bold mb-4">Admin-Bereich</h1>
<div class="flex flex-col gap-4">
<a href="/admin/anmeldungen" class="bg-blue-600 text-white px-4 py-3 rounded text-center hover:bg-blue-700">
<a href="/admin/anmeldungen" class="bg-blue-600 text-white px-4 py-3 rounded text-center hover:bg-blue-800">
📝 Anmeldungen anzeigen
</a>
<a href="/admin/dienststellen" class="bg-green-600 text-white px-4 py-3 rounded text-center hover:bg-green-700">
<a href="/admin/dienststellen" class="bg-green-600 text-white px-4 py-3 rounded text-center hover:bg-green-800">
🏢 Dienststellen verwalten
</a>
<a href="/admin/change-password" class="bg-cyan-600 text-white px-4 py-3 rounded text-center hover:bg-green-700">
<a href="/admin/zeitraum" class="bg-yellow-600 text-white px-4 py-3 rounded text-center hover:bg-yellow-800">
🗓 Zeitraum verwaltung
</a>
<a href="/admin/change-password" class="bg-cyan-600 text-white px-4 py-3 rounded text-center hover:bg-cyan-800">
👨‍💼 Passwort ädern
</a>
</div>

View File

@@ -0,0 +1,8 @@
import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ cookies }) => {
if (cookies.get('admin_session') !== 'true') {
throw redirect(303, '/admin'); // zurück zur Login-Seite
}
};

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import { onMount } from 'svelte'
let bezeichnung = ''
let startDatum = ''
let endDatum = ''
let zeitraeume = []
onMount(async () => {
zeitraeume = await fetchZeitraeume()
})
async function handleSubmit() {
await createPraktikumszeitraum({ bezeichnung, startDatum, endDatum })
zeitraeume = await fetchZeitraeume()
// Felder zurücksetzen
bezeichnung = ''
startDatum = ''
endDatum = ''
}
</script>
<h1 class="text-2xl font-bold mb-4">🗓 Praktikumszeiträume verwalten</h1>
<form on:submit|preventDefault={handleSubmit} class="grid gap-4 max-w-xl bg-white p-4 rounded shadow">
<input type="text" bind:value={bezeichnung} placeholder="Bezeichnung (z.B. Frühjahr 2025)" required class="input" />
<div class="flex gap-2">
<input type="date" bind:value={startDatum} required class="input flex-1" />
<input type="date" bind:value={endDatum} required class="input flex-1" />
</div>
<button type="submit" class="bg-green-600 text-white py-2 rounded hover:bg-green-700">✅ Zeitraum speichern</button>
</form>
<h2 class="text-xl font-semibold mt-8 mb-2">📋 Bereits erfasste Zeiträume</h2>
<div class="grid gap-4">
{#each zeitraeume as z}
<div class="p-4 bg-gray-100 rounded shadow">
<h3 class="font-bold">{z.bezeichnung}</h3>
<p class="text-sm text-gray-700">
{new Date(z.startDatum).toLocaleDateString()} {new Date(z.endDatum).toLocaleDateString()}
</p>
</div>
{/each}
</div>
<style>
.input {
@apply border p-2 rounded w-full;
}
</style>

View File

@@ -0,0 +1,22 @@
import { db } from '$lib/server/prisma'
export async function GET() {
const zeitraeume = await db.praktikumszeitraum.findMany({
orderBy: { startDatum: 'asc' }
})
return new Response(JSON.stringify(zeitraeume))
}
export async function POST({ request }) {
const { bezeichnung, startDatum, endDatum } = await request.json()
await db.praktikumszeitraum.create({
data: {
bezeichnung,
startDatum: new Date(startDatum),
endDatum: new Date(endDatum)
}
})
return new Response('OK')
}

View File

@@ -38,7 +38,6 @@ export async function POST({ request }) {
telefon: get('telefon'),
email: get('email'),
schulart: get('schulart'),
zeitraum: get('zeitraum'),
motivation: get('motivation'),
wunsch1Id: parseInt(get('wunsch1Id')),
wunsch2Id: parseInt(get('wunsch2Id')),