Files
breakpilot-lehrer/website/app/admin/compliance/audit-checklist/page.tsx
Benjamin Admin 34da9f4cda [split-required] Split 700-870 LOC files across all services
backend-lehrer (11 files):
- llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6)
- messenger_api.py (840 → 5), print_generator.py (824 → 5)
- unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4)
- llm_gateway/routes/edu_search_seeds.py (710 → 4)

klausur-service (12 files):
- ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4)
- legal_corpus_api.py (790 → 4), page_crop.py (758 → 3)
- mail/ai_service.py (747 → 4), github_crawler.py (767 → 3)
- trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4)
- dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4)

website (6 pages):
- audit-checklist (867 → 8), content (806 → 6)
- screen-flow (790 → 4), scraper (789 → 5)
- zeugnisse (776 → 5), modules (745 → 4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:01:18 +02:00

225 lines
9.0 KiB
TypeScript

'use client'
/**
* Audit Checklist Page - Sprint 3 Feature
*
* Ermoeglicht Auditoren:
* - Audit-Sessions zu erstellen und zu verwalten
* - Checklisten-Items durchzuarbeiten
* - Sign-offs mit digitaler Signatur (SHA-256) zu erstellen
* - Fortschritt und Statistiken zu verfolgen
*/
import Link from 'next/link'
import AdminLayout from '@/components/admin/AdminLayout'
import { RESULT_STATUS, SESSION_STATUS } from './_components/types'
import { useAuditChecklist } from './_components/useAuditChecklist'
import SessionCard from './_components/SessionCard'
import AuditProgressBar from './_components/AuditProgressBar'
import ChecklistItemRow from './_components/ChecklistItemRow'
import CreateSessionModal from './_components/CreateSessionModal'
import SignOffModal from './_components/SignOffModal'
export default function AuditChecklistPage() {
const {
sessions, selectedSession, setSelectedSession,
filteredItems, statistics, regulations,
loading, loadingChecklist, error,
filterStatus, setFilterStatus,
filterRegulation, setFilterRegulation,
searchQuery, setSearchQuery,
showCreateModal, setShowCreateModal,
showSignOffModal, setShowSignOffModal,
selectedItem, setSelectedItem,
lang, handleSignOff, handleCreateSession,
} = useAuditChecklist()
// Session list view when no session is selected
if (!selectedSession && !loading) {
return (
<AdminLayout title="Audit Checkliste" description="Strukturierte Pruefungen durchfuehren">
<div className="mb-6 flex items-center justify-between">
<Link
href="/admin/compliance"
className="text-sm text-slate-600 hover:text-slate-900 flex items-center gap-1"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck zu Compliance
</Link>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Neue Audit-Session
</button>
</div>
{error && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">{error}</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{sessions.length === 0 ? (
<div className="col-span-full bg-white rounded-lg border border-slate-200 p-8 text-center">
<svg className="w-12 h-12 mx-auto text-slate-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
<h3 className="text-lg font-medium text-slate-700 mb-2">Keine Audit-Sessions</h3>
<p className="text-slate-500 mb-4">Erstellen Sie eine neue Audit-Session um zu beginnen.</p>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700"
>
Erste Session erstellen
</button>
</div>
) : (
sessions.map(session => (
<SessionCard key={session.id} session={session} onClick={() => setSelectedSession(session)} />
))
)}
</div>
{showCreateModal && (
<CreateSessionModal
regulations={regulations}
onClose={() => setShowCreateModal(false)}
onCreate={handleCreateSession}
/>
)}
</AdminLayout>
)
}
if (loading) {
return (
<AdminLayout title="Audit Checkliste" description="Laden...">
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
</div>
</AdminLayout>
)
}
// Checklist view for selected session
return (
<AdminLayout title={selectedSession?.name || 'Audit Checkliste'} description="Pruefung durchfuehren">
<div className="mb-6 flex items-center justify-between">
<button
onClick={() => setSelectedSession(null)}
className="text-sm text-slate-600 hover:text-slate-900 flex items-center gap-1"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck zur Uebersicht
</button>
<div className="flex items-center gap-3">
<span className={`px-3 py-1 text-sm font-medium rounded-full ${SESSION_STATUS[selectedSession?.status || 'draft'].color}`}>
{SESSION_STATUS[selectedSession?.status || 'draft'].label}
</span>
<button
className="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 flex items-center gap-2"
onClick={() => {/* Export PDF */}}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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>
Export PDF
</button>
</div>
</div>
{statistics && <AuditProgressBar statistics={statistics} lang={lang} />}
{/* Filters */}
<div className="bg-white rounded-lg border border-slate-200 p-4 mb-6">
<div className="flex flex-wrap gap-4">
<div className="flex-1 min-w-[200px]">
<label className="block text-xs font-medium text-slate-500 mb-1">Suche</label>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Artikel, Titel..."
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm"
/>
</div>
<div>
<label className="block text-xs font-medium text-slate-500 mb-1">Verordnung</label>
<select
value={filterRegulation}
onChange={(e) => setFilterRegulation(e.target.value)}
className="px-3 py-2 border border-slate-300 rounded-lg text-sm"
>
<option value="all">Alle</option>
{regulations.map(reg => (
<option key={reg.code} value={reg.code}>{reg.code}</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-slate-500 mb-1">Status</label>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-3 py-2 border border-slate-300 rounded-lg text-sm"
>
<option value="all">Alle</option>
{Object.entries(RESULT_STATUS).map(([key, { label }]) => (
<option key={key} value={key}>{label}</option>
))}
</select>
</div>
</div>
</div>
{/* Checklist Items */}
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
{loadingChecklist ? (
<div className="p-8 text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-2 text-slate-500">Checkliste wird geladen...</p>
</div>
) : filteredItems.length === 0 ? (
<div className="p-8 text-center">
<p className="text-slate-500">Keine Eintraege gefunden</p>
</div>
) : (
<div className="divide-y divide-slate-100">
{filteredItems.map((item, index) => (
<ChecklistItemRow
key={item.requirement_id}
item={item}
index={index + 1}
onSignOff={() => {
setSelectedItem(item)
setShowSignOffModal(true)
}}
lang={lang}
/>
))}
</div>
)}
</div>
{showSignOffModal && selectedItem && (
<SignOffModal
item={selectedItem}
auditorName={selectedSession?.auditor_name || ''}
onClose={() => {
setShowSignOffModal(false)
setSelectedItem(null)
}}
onSubmit={(result, notes, sign) => handleSignOff(selectedItem, result, notes, sign)}
/>
)}
</AdminLayout>
)
}