Add Environment & Archive module: Eco metrics, Digital Archive, Government Portal, NAS settings

New Module: 'Umwelt & Archiv' (Eco & Archive) — 4 tabs:

1. Umwelt (Eco Dashboard):
   - Paper savings tracker (sheets, grams, CO2, water, trees)
   - Monthly trend bar chart
   - Hero card with environmental mission statement
   - EcoMetric model: daily tracking of paper/CO2/water/trees saved

2. Archiv (Digital Archive):
   - Full-text searchable document archive (OCR text search)
   - Document types: Report Card, Certificate, Enrollment, Letter, Contract
   - Categories: Student, Staff, Finance, Admin, Legal
   - Confidential documents with lock indicator
   - Upload dialog with drag-and-drop area
   - ArchivedDocument model with checksum, retention date, access level

3. Behörden (Government Portal):
   - Report types: Schulstatistik, Schulbesuch, Förderung, Personal, Bildungsbericht
   - Status tracking: DRAFT → SUBMITTED → ACKNOWLEDGED/REJECTED
   - Info box about German government portal integration
   - GovernmentReport model with submission tracking

4. Speicher (NAS/Storage Settings):
   - NAS server connection (SMB/NFS/WebDAV/S3)
   - Host, port, share, credentials configuration
   - Backup frequency (hourly/daily/weekly)
   - Optional cloud backup (Nextcloud/ownCloud/S3)
   - StorageConfig model

New Prisma models: ArchivedDocument, GovernmentReport, EcoMetric, StorageConfig
New API routes: /api/archived-documents, /api/government-reports, /api/eco-metrics, /api/storage-config
New view: EcoArchiveView registered in nav, store, role-access
This commit is contained in:
Z User committed 2026-09-06 18:47:48 +00:00
1 parent 59de7b692b
commit 231d8449e7
12 files changed
+851 -2

No files matched your search

@@ -0,0 +1,28 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getSession } from '@/lib/auth'
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const { id } = await params
const doc = await db.archivedDocument.findUnique({ where: { id } })
if (!doc) return NextResponse.json({ error: 'Not found' }, { status: 404 })
return NextResponse.json(doc)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const { id } = await params
await db.archivedDocument.delete({ where: { id } })
return NextResponse.json({ success: true })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
+56
View File
@@ -0,0 +1,56 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getSession } from '@/lib/auth'
export async function GET(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const { searchParams } = new URL(request.url)
const schoolId = searchParams.get('schoolId') || session.user.schoolId
if (!schoolId) return NextResponse.json([])
const category = searchParams.get('category')
const documentType = searchParams.get('documentType')
const search = searchParams.get('search')
const where: any = { schoolId }
if (category) where.category = category
if (documentType) where.documentType = documentType
if (search) where.OR = [
{ title: { contains: search } },
{ ocrText: { contains: search } },
{ tags: { contains: search } },
]
const docs = await db.archivedDocument.findMany({
where,
orderBy: { createdAt: 'desc' },
take: 100,
})
return NextResponse.json(docs)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function POST(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const role = session.user.role
if (role !== 'SCHOOL_ADMIN' && role !== 'SUPER_ADMIN' && role !== 'TEACHER') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const body = await request.json()
const doc = await db.archivedDocument.create({
data: { ...body, schoolId: session.user.schoolId, uploadedBy: session.user.id },
})
return NextResponse.json(doc, { status: 201 })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
+45
View File
@@ -0,0 +1,45 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getSession } from '@/lib/auth'
export async function GET(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const schoolId = session.user.schoolId
if (!schoolId) return NextResponse.json([])
const metrics = await db.ecoMetric.findMany({
where: { schoolId },
orderBy: { date: 'desc' },
take: 365,
})
// Aggregate totals
const totals = metrics.reduce((acc, m) => ({
paperSavedSheets: acc.paperSavedSheets + m.paperSavedSheets,
paperSavedGrams: acc.paperSavedGrams + m.paperSavedGrams,
co2SavedKg: acc.co2SavedKg + m.co2SavedKg,
waterSavedLiters: acc.waterSavedLiters + m.waterSavedLiters,
treesSaved: acc.treesSaved + m.treesSaved,
}), { paperSavedSheets: 0, paperSavedGrams: 0, co2SavedKg: 0, waterSavedLiters: 0, treesSaved: 0 })
return NextResponse.json({ metrics, totals })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function POST(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const body = await request.json()
const metric = await db.ecoMetric.create({
data: { ...body, schoolId: session.user.schoolId! },
})
return NextResponse.json(metric, { status: 201 })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
@@ -0,0 +1,16 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getSession } from '@/lib/auth'
export async function PUT(request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const { id } = await params
const body = await request.json()
const report = await db.governmentReport.update({ where: { id }, data: body })
return NextResponse.json(report)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
+38
View File
@@ -0,0 +1,38 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getSession } from '@/lib/auth'
export async function GET(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const schoolId = session.user.schoolId
if (!schoolId) return NextResponse.json([])
const reports = await db.governmentReport.findMany({
where: { schoolId },
orderBy: { createdAt: 'desc' },
})
return NextResponse.json(reports)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function POST(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const role = session.user.role
if (role !== 'SCHOOL_ADMIN' && role !== 'SUPER_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const body = await request.json()
const report = await db.governmentReport.create({
data: { ...body, schoolId: session.user.schoolId!, createdBy: session.user.id },
})
return NextResponse.json(report, { status: 201 })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
+39
View File
@@ -0,0 +1,39 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getSession } from '@/lib/auth'
export async function GET() {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const schoolId = session.user.schoolId
if (!schoolId) return NextResponse.json(null)
const config = await db.storageConfig.findUnique({ where: { schoolId } })
return NextResponse.json(config)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function POST(request: Request) {
try {
const session = await getSession()
if (!session?.user) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 })
const role = session.user.role
if (role !== 'SCHOOL_ADMIN' && role !== 'SUPER_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const schoolId = session.user.schoolId!
const body = await request.json()
const config = await db.storageConfig.upsert({
where: { schoolId },
update: { ...body },
create: { ...body, schoolId },
})
return NextResponse.json(config)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}