backend-lehrer (10 files): - game/database.py (785 → 5), correction_api.py (683 → 4) - classroom_engine/antizipation.py (676 → 5) - llm_gateway schools/edu_search already done in prior batch klausur-service (12 files): - orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4) - zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5) - eh_templates.py (658 → 5), mail/api.py (651 → 5) - qdrant_service.py (638 → 5), training_api.py (625 → 4) website (6 pages): - middleware (696 → 8), mail (733 → 6), consent (628 → 8) - compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7) studio-v2 (3 components): - B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2) - dashboard-experimental (739 → 2) admin-lehrer (4 files): - uebersetzungen (769 → 4), manager (670 → 2) - ChunkBrowserQA (675 → 6), dsfa/page (674 → 5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
204 lines
9.5 KiB
TypeScript
204 lines
9.5 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import { useRouter } from 'next/navigation'
|
||
|
||
import { PerformanceProvider, usePerformance } from '@/lib/spatial-ui/PerformanceContext'
|
||
import { FocusProvider } from '@/lib/spatial-ui/FocusContext'
|
||
import { FloatingMessage } from '@/components/spatial-ui/FloatingMessage'
|
||
import {
|
||
GlassCard, AnalogClock, Compass, BarChart, TemperatureDisplay,
|
||
ProgressRing, StatDisplay, ListItem, ActionButton, QualityIndicator,
|
||
} from './_components/DashboardWidgets'
|
||
|
||
// =============================================================================
|
||
// MAIN DASHBOARD
|
||
// =============================================================================
|
||
|
||
function DashboardContent() {
|
||
const router = useRouter()
|
||
const { settings } = usePerformance()
|
||
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
|
||
const [time, setTime] = useState(new Date())
|
||
|
||
useEffect(() => {
|
||
const timer = setInterval(() => setTime(new Date()), 1000)
|
||
return () => clearInterval(timer)
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
if (!settings.enableParallax) return
|
||
const handleMouseMove = (e: MouseEvent) => setMousePos({ x: e.clientX, y: e.clientY })
|
||
window.addEventListener('mousemove', handleMouseMove)
|
||
return () => window.removeEventListener('mousemove', handleMouseMove)
|
||
}, [settings.enableParallax])
|
||
|
||
const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 1920
|
||
const windowHeight = typeof window !== 'undefined' ? window.innerHeight : 1080
|
||
const parallax = settings.enableParallax
|
||
? { x: (mousePos.x / windowWidth - 0.5) * 15, y: (mousePos.y / windowHeight - 0.5) * 15 }
|
||
: { x: 0, y: 0 }
|
||
|
||
const greeting = time.getHours() < 12 ? 'Guten Morgen' : time.getHours() < 18 ? 'Guten Tag' : 'Guten Abend'
|
||
|
||
const weeklyData = [
|
||
{ label: 'Mo', value: 4 }, { label: 'Di', value: 7 }, { label: 'Mi', value: 3 },
|
||
{ label: 'Do', value: 8 }, { label: 'Fr', value: 6, highlight: true },
|
||
{ label: 'Sa', value: 2 }, { label: 'So', value: 0 },
|
||
]
|
||
|
||
return (
|
||
<div className="min-h-screen relative overflow-hidden">
|
||
{/* Background */}
|
||
<div
|
||
className="absolute inset-0 bg-gradient-to-br from-slate-900 via-indigo-950 to-slate-900"
|
||
style={{ transform: `translate(${parallax.x * 0.5}px, ${parallax.y * 0.5}px) scale(1.05)`, transition: 'transform 0.3s ease-out' }}
|
||
>
|
||
<div className="absolute inset-0 opacity-30" style={{
|
||
backgroundImage: `radial-gradient(2px 2px at 20px 30px, white, transparent),
|
||
radial-gradient(2px 2px at 40px 70px, rgba(255,255,255,0.8), transparent),
|
||
radial-gradient(1px 1px at 90px 40px, white, transparent),
|
||
radial-gradient(2px 2px at 160px 120px, rgba(255,255,255,0.9), transparent),
|
||
radial-gradient(1px 1px at 230px 80px, white, transparent),
|
||
radial-gradient(2px 2px at 300px 150px, rgba(255,255,255,0.7), transparent)`,
|
||
backgroundSize: '400px 200px',
|
||
}} />
|
||
<div className="absolute w-[500px] h-[500px] rounded-full opacity-20" style={{
|
||
background: 'radial-gradient(circle, rgba(99, 102, 241, 0.5) 0%, transparent 70%)',
|
||
left: '10%', top: '20%', transform: `translate(${parallax.x}px, ${parallax.y}px)`, transition: 'transform 0.5s ease-out',
|
||
}} />
|
||
<div className="absolute w-[400px] h-[400px] rounded-full opacity-15" style={{
|
||
background: 'radial-gradient(circle, rgba(167, 139, 250, 0.5) 0%, transparent 70%)',
|
||
right: '5%', bottom: '10%', transform: `translate(${-parallax.x * 0.8}px, ${-parallax.y * 0.8}px)`, transition: 'transform 0.5s ease-out',
|
||
}} />
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div className="relative z-10 min-h-screen p-6">
|
||
{/* Header */}
|
||
<header className="flex items-start justify-between mb-8">
|
||
<div>
|
||
<p className="text-white/40 text-sm font-medium tracking-wide uppercase mb-1">
|
||
{time.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||
</p>
|
||
<h1 className="text-4xl font-light text-white tracking-tight">{greeting}</h1>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<GlassCard size="sm" className="!p-3">
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-lg">🔔</span>
|
||
<span className="text-white font-medium text-sm">3</span>
|
||
</div>
|
||
</GlassCard>
|
||
<GlassCard size="sm" className="!p-3">
|
||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-400 to-purple-500" />
|
||
</GlassCard>
|
||
</div>
|
||
</header>
|
||
|
||
{/* Main Grid */}
|
||
<div className="grid grid-cols-12 gap-4 max-w-7xl mx-auto">
|
||
<div className="col-span-3">
|
||
<GlassCard size="lg" delay={50}>
|
||
<div className="flex flex-col items-center">
|
||
<AnalogClock />
|
||
<p className="text-white text-2xl font-light mt-4">
|
||
{time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}
|
||
</p>
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
<div className="col-span-3">
|
||
<GlassCard size="lg" delay={100}><TemperatureDisplay temp={8} condition="partly_cloudy" /></GlassCard>
|
||
</div>
|
||
<div className="col-span-3">
|
||
<GlassCard size="lg" delay={150}>
|
||
<div className="flex flex-col items-center">
|
||
<Compass direction={225} />
|
||
<p className="text-white/50 text-sm mt-3">SW Wind</p>
|
||
<p className="text-white text-lg font-light">12 km/h</p>
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
<div className="col-span-3">
|
||
<GlassCard size="lg" delay={200}>
|
||
<StatDisplay icon="📋" value="12" label="Offene Korrekturen" />
|
||
<div className="mt-4 pt-4 border-t border-white/10 flex justify-around">
|
||
<div className="text-center"><p className="text-xl font-light text-white">28</p><p className="text-white/40 text-xs">Diese Woche</p></div>
|
||
<div className="text-center"><p className="text-xl font-light text-white">156</p><p className="text-white/40 text-xs">Gesamt</p></div>
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
|
||
<div className="col-span-6">
|
||
<GlassCard size="lg" delay={250}>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h2 className="text-white text-sm font-medium uppercase tracking-wide opacity-60">Korrekturen diese Woche</h2>
|
||
<span className="text-white/40 text-sm">30 gesamt</span>
|
||
</div>
|
||
<BarChart data={weeklyData} maxValue={10} />
|
||
</GlassCard>
|
||
</div>
|
||
|
||
<div className="col-span-3">
|
||
<GlassCard size="lg" delay={300}>
|
||
<div className="flex justify-around">
|
||
<ProgressRing progress={75} label="Fortschritt" value="75%" color="#60a5fa" />
|
||
<ProgressRing progress={92} label="Qualitaet" value="92%" color="#a78bfa" />
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
<div className="col-span-3">
|
||
<GlassCard size="lg" delay={350}>
|
||
<StatDisplay icon="⏱" value="4.2" unit="h" label="Zeit gespart" />
|
||
<p className="text-center text-white/30 text-xs mt-3">durch KI-Unterstuetzung</p>
|
||
</GlassCard>
|
||
</div>
|
||
|
||
<div className="col-span-8">
|
||
<GlassCard size="lg" delay={400}>
|
||
<div className="flex items-center justify-between mb-3">
|
||
<h2 className="text-white text-sm font-medium uppercase tracking-wide opacity-60">Aktuelle Klausuren</h2>
|
||
<button className="text-white/40 text-xs hover:text-white transition-colors">Alle anzeigen</button>
|
||
</div>
|
||
<div className="space-y-1">
|
||
<ListItem icon="📝" title="Deutsch LK - Textanalyse" subtitle="24 Schueler" value="18/24" delay={450} />
|
||
<ListItem icon="✅" title="Deutsch GK - Eroerterung" subtitle="Abgeschlossen" value="28/28" delay={500} />
|
||
<ListItem icon="📝" title="Vorabitur - Gedichtanalyse" subtitle="22 Schueler" value="10/22" delay={550} />
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
|
||
<div className="col-span-4">
|
||
<GlassCard size="lg" delay={450}>
|
||
<h2 className="text-white text-sm font-medium uppercase tracking-wide opacity-60 mb-4">Schnellaktionen</h2>
|
||
<div className="space-y-2">
|
||
<ActionButton icon="➕" label="Neue Klausur" primary delay={500} />
|
||
<ActionButton icon="📤" label="Arbeiten hochladen" delay={550} />
|
||
<ActionButton icon="🎨" label="Worksheet Editor" onClick={() => router.push('/worksheet-editor')} delay={600} />
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<FloatingMessage autoDismissMs={12000} maxQueue={3} position="top-right" offset={{ x: 24, y: 24 }} />
|
||
<QualityIndicator />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// =============================================================================
|
||
// MAIN PAGE
|
||
// =============================================================================
|
||
|
||
export default function ExperimentalDashboard() {
|
||
return (
|
||
<PerformanceProvider>
|
||
<FocusProvider>
|
||
<DashboardContent />
|
||
</FocusProvider>
|
||
</PerformanceProvider>
|
||
)
|
||
}
|