i18n: Replace hardcoded English strings with German in key components
UnifiedGradingPanel - all labels translated to German ProfessionalCalendar - all UI text translated to German AI Studio - confirmed German throughout
This commit is contained in:
1 parent
678a4e9b83
commit
7461f7699c
2 files changed
+75
-75
No files matched your search
@@ -163,7 +163,7 @@ export function ProfessionalCalendar({
|
|||||||
if (onEventCreate) {
|
if (onEventCreate) {
|
||||||
await onEventCreate(newEvent);
|
await onEventCreate(newEvent);
|
||||||
}
|
}
|
||||||
toast.success('Event created successfully');
|
toast.success('Ereignis erfolgreich erstellt');
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to create event');
|
toast.error('Failed to create event');
|
||||||
@@ -183,7 +183,7 @@ export function ProfessionalCalendar({
|
|||||||
if (onEventUpdate) {
|
if (onEventUpdate) {
|
||||||
await onEventUpdate(event);
|
await onEventUpdate(event);
|
||||||
}
|
}
|
||||||
toast.success('Event updated successfully');
|
toast.success('Ereignis aktualisiert successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to update event');
|
toast.error('Failed to update event');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -201,7 +201,7 @@ export function ProfessionalCalendar({
|
|||||||
await onEventDelete(eventId);
|
await onEventDelete(eventId);
|
||||||
}
|
}
|
||||||
setSelectedEvent(null);
|
setSelectedEvent(null);
|
||||||
toast.success('Event deleted successfully');
|
toast.success('Ereignis gelöscht successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to delete event');
|
toast.error('Failed to delete event');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -350,7 +350,7 @@ export function ProfessionalCalendar({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setCurrentDate(new Date())}
|
onClick={() => setCurrentDate(new Date())}
|
||||||
>
|
>
|
||||||
Today
|
Heute
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -380,7 +380,7 @@ export function ProfessionalCalendar({
|
|||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
New Event
|
Neues Ereignis
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ export function ProfessionalCalendar({
|
|||||||
<Badge variant="secondary">{event.type}</Badge>
|
<Badge variant="secondary">{event.type}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
{format(event.date, 'PPP', { locale: dateLocale })} at {CalendarManager.formatTimeRange(event)}
|
{format(event.date, 'PPP', { locale: dateLocale })} um {CalendarManager.formatTimeRange(event)}
|
||||||
</p>
|
</p>
|
||||||
{event.description && (
|
{event.description && (
|
||||||
<p className="text-sm mt-2">{event.description}</p>
|
<p className="text-sm mt-2">{event.description}</p>
|
||||||
@@ -507,9 +507,9 @@ function EventDetailDialog({
|
|||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs text-muted-foreground">Date & Time</Label>
|
<Label className="text-xs text-muted-foreground">Datum & Zeit</Label>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{format(event.date, 'PPP')} at {CalendarManager.formatTimeRange(event)}
|
{format(event.date, 'PPP')} um {CalendarManager.formatTimeRange(event)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -517,7 +517,7 @@ function EventDetailDialog({
|
|||||||
<div className="flex gap-2 items-start">
|
<div className="flex gap-2 items-start">
|
||||||
<MapPin className="h-4 w-4 text-muted-foreground mt-1" />
|
<MapPin className="h-4 w-4 text-muted-foreground mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs text-muted-foreground">Location</Label>
|
<Label className="text-xs text-muted-foreground">Ort</Label>
|
||||||
<p className="text-sm">{event.location}</p>
|
<p className="text-sm">{event.location}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -525,7 +525,7 @@ function EventDetailDialog({
|
|||||||
|
|
||||||
{event.description && (
|
{event.description && (
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs text-muted-foreground">Description</Label>
|
<Label className="text-xs text-muted-foreground">Beschreibung</Label>
|
||||||
<p className="text-sm">{event.description}</p>
|
<p className="text-sm">{event.description}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -534,7 +534,7 @@ function EventDetailDialog({
|
|||||||
<div className="flex gap-2 items-start">
|
<div className="flex gap-2 items-start">
|
||||||
<Users className="h-4 w-4 text-muted-foreground mt-1" />
|
<Users className="h-4 w-4 text-muted-foreground mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs text-muted-foreground">Attendees</Label>
|
<Label className="text-xs text-muted-foreground">Teilnehmer</Label>
|
||||||
<p className="text-sm">{event.attendees.join(', ')}</p>
|
<p className="text-sm">{event.attendees.join(', ')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -603,12 +603,12 @@ function EventCreatorDialog({
|
|||||||
<Dialog open onOpenChange={onClose}>
|
<Dialog open onOpenChange={onClose}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create New Event</DialogTitle>
|
<DialogTitle>Create Neues Ereignis</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="title">Title</Label>
|
<Label htmlFor="title">Titel</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
@@ -659,7 +659,7 @@ function EventCreatorDialog({
|
|||||||
{!formData.allDay && (
|
{!formData.allDay && (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="startTime">Start Time</Label>
|
<Label htmlFor="startTime">Startzeit</Label>
|
||||||
<Input
|
<Input
|
||||||
id="startTime"
|
id="startTime"
|
||||||
type="time"
|
type="time"
|
||||||
@@ -668,7 +668,7 @@ function EventCreatorDialog({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="endTime">End Time</Label>
|
<Label htmlFor="endTime">Endzeit</Label>
|
||||||
<Input
|
<Input
|
||||||
id="endTime"
|
id="endTime"
|
||||||
type="time"
|
type="time"
|
||||||
@@ -680,7 +680,7 @@ function EventCreatorDialog({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="location">Location</Label>
|
<Label htmlFor="location">Ort</Label>
|
||||||
<Input
|
<Input
|
||||||
id="location"
|
id="location"
|
||||||
value={formData.location}
|
value={formData.location}
|
||||||
@@ -690,7 +690,7 @@ function EventCreatorDialog({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="description">Description</Label>
|
<Label htmlFor="description">Beschreibung</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
@@ -703,9 +703,9 @@ function EventCreatorDialog({
|
|||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={onClose}>
|
<Button variant="outline" onClick={onClose}>
|
||||||
Cancel
|
Abbrechen
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSubmit}>Create Event</Button>
|
<Button onClick={handleSubmit}>Ereignis erstellen</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ interface Grade {
|
|||||||
subject: string;
|
subject: string;
|
||||||
assessment: string;
|
assessment: string;
|
||||||
score: number;
|
score: number;
|
||||||
maxScore: number;
|
maxPunkte: number;
|
||||||
weight?: number;
|
weight?: number;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
gradedAt?: string;
|
gradedAt?: string;
|
||||||
@@ -72,7 +72,7 @@ interface UnifiedGradingPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unified Grading Panel
|
* Unified Benotung
|
||||||
* Consolidates: grading-view.tsx, tablet-grading-view.tsx, grade-analytics-view.tsx
|
* Consolidates: grading-view.tsx, tablet-grading-view.tsx, grade-analytics-view.tsx
|
||||||
* Single component supports all viewing modes and devices
|
* Single component supports all viewing modes and devices
|
||||||
*/
|
*/
|
||||||
@@ -89,7 +89,7 @@ export function UnifiedGradingPanel({
|
|||||||
const [selectedGrade, setSelectedGrade] = useState<Grade | null>(null);
|
const [selectedGrade, setSelectedGrade] = useState<Grade | null>(null);
|
||||||
const [isEditingGrade, setIsEditingGrade] = useState(false);
|
const [isEditingGrade, setIsEditingGrade] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filterSubject, setFilterSubject] = useState('all');
|
const [filterFach, setFilterFach] = useState('all');
|
||||||
const [filterStatus, setFilterStatus] = useState<'all' | 'pending' | 'graded' | 'released'>('all');
|
const [filterStatus, setFilterStatus] = useState<'all' | 'pending' | 'graded' | 'released'>('all');
|
||||||
const [viewType, setViewType] = useState<'grid' | 'list' | 'analytics'>(
|
const [viewType, setViewType] = useState<'grid' | 'list' | 'analytics'>(
|
||||||
variant === 'list' ? 'list' : 'grid'
|
variant === 'list' ? 'list' : 'grid'
|
||||||
@@ -116,8 +116,8 @@ export function UnifiedGradingPanel({
|
|||||||
studentName: item.studentName || (item.student ? `${item.student.firstName} ${item.student.lastName}` : '—'),
|
studentName: item.studentName || (item.student ? `${item.student.firstName} ${item.student.lastName}` : '—'),
|
||||||
subject: typeof item.subject === 'string' ? item.subject : (item.subject?.name || item.subjectId || '—'),
|
subject: typeof item.subject === 'string' ? item.subject : (item.subject?.name || item.subjectId || '—'),
|
||||||
assessment: item.assessment || item.title || item.name || '—',
|
assessment: item.assessment || item.title || item.name || '—',
|
||||||
score: typeof item.score === 'number' ? item.score : (item.maxScore ?? 0),
|
score: typeof item.score === 'number' ? item.score : (item.maxPunkte ?? 0),
|
||||||
maxScore: item.maxScore ?? 100,
|
maxPunkte: item.maxPunkte ?? 100,
|
||||||
weight: item.weight,
|
weight: item.weight,
|
||||||
comment: item.comment || item.note || '',
|
comment: item.comment || item.note || '',
|
||||||
gradedAt: item.gradedAt || item.date || item.createdAt,
|
gradedAt: item.gradedAt || item.date || item.createdAt,
|
||||||
@@ -143,9 +143,9 @@ export function UnifiedGradingPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subject filter
|
// Fach filter
|
||||||
if (filterSubject !== 'all') {
|
if (filterFach !== 'all') {
|
||||||
filtered = filtered.filter((g) => g.subject === filterSubject);
|
filtered = filtered.filter((g) => g.subject === filterFach);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status filter
|
// Status filter
|
||||||
@@ -154,18 +154,18 @@ export function UnifiedGradingPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFilteredGrades(filtered);
|
setFilteredGrades(filtered);
|
||||||
}, [searchTerm, filterSubject, filterStatus]);
|
}, [searchTerm, filterFach, filterStatus]);
|
||||||
|
|
||||||
// Update filters when any filter changes
|
// Update filters when any filter changes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
applyFilters(grades);
|
applyFilters(grades);
|
||||||
}, [searchTerm, filterSubject, filterStatus, applyFilters]);
|
}, [searchTerm, filterFach, filterStatus, applyFilters]);
|
||||||
|
|
||||||
// Calculate statistics
|
// Calculate statistics
|
||||||
const stats = useMemo(() => {
|
const stats = useMemo(() => {
|
||||||
const completed = grades.filter((g) => g.status !== 'pending').length;
|
const completed = grades.filter((g) => g.status !== 'pending').length;
|
||||||
const average = grades.length > 0
|
const average = grades.length > 0
|
||||||
? (grades.reduce((sum, g) => sum + (g.score / g.maxScore) * 100, 0) / grades.length).toFixed(1)
|
? (grades.reduce((sum, g) => sum + (g.score / g.maxPunkte) * 100, 0) / grades.length).toFixed(1)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -231,7 +231,7 @@ export function UnifiedGradingPanel({
|
|||||||
}
|
}
|
||||||
}, [mutate]);
|
}, [mutate]);
|
||||||
|
|
||||||
// Subject list for filtering
|
// Fach list for filtering
|
||||||
const subjects = useMemo(() => {
|
const subjects = useMemo(() => {
|
||||||
const subjectSet = new Set(grades.map((g) => g.subject));
|
const subjectSet = new Set(grades.map((g) => g.subject));
|
||||||
return Array.from(subjectSet).sort();
|
return Array.from(subjectSet).sort();
|
||||||
@@ -244,7 +244,7 @@ export function UnifiedGradingPanel({
|
|||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="grid grid-cols-4 gap-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm">Total Grades</CardTitle>
|
<CardTitle className="text-sm">Noten gesamt</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats.total}</div>
|
<div className="text-2xl font-bold">{stats.total}</div>
|
||||||
@@ -252,7 +252,7 @@ export function UnifiedGradingPanel({
|
|||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm">Completed</CardTitle>
|
<CardTitle className="text-sm">Erledigt</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats.completed}</div>
|
<div className="text-2xl font-bold">{stats.completed}</div>
|
||||||
@@ -260,7 +260,7 @@ export function UnifiedGradingPanel({
|
|||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm">Pending</CardTitle>
|
<CardTitle className="text-sm">Ausstehend</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-orange-500">{stats.pending}</div>
|
<div className="text-2xl font-bold text-orange-500">{stats.pending}</div>
|
||||||
@@ -268,7 +268,7 @@ export function UnifiedGradingPanel({
|
|||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-sm">Average</CardTitle>
|
<CardTitle className="text-sm">Durchschnitt</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{stats.average}%</div>
|
<div className="text-2xl font-bold">{stats.average}%</div>
|
||||||
@@ -280,10 +280,10 @@ export function UnifiedGradingPanel({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center justify-between">
|
<CardTitle className="flex items-center justify-between">
|
||||||
<span>Grades</span>
|
<span>Noten</span>
|
||||||
<Button onClick={handleSubmitGrades} disabled={stats.pending === stats.total}>
|
<Button onClick={handleSubmitGrades} disabled={stats.pending === stats.total}>
|
||||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||||
Submit Grades
|
Noten einreichen
|
||||||
</Button>
|
</Button>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -292,13 +292,13 @@ export function UnifiedGradingPanel({
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="border-b">
|
<thead className="border-b">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left py-2 px-3">Student</th>
|
<th className="text-left py-2 px-3">Schüler</th>
|
||||||
<th className="text-left py-2 px-3">Subject</th>
|
<th className="text-left py-2 px-3">Fach</th>
|
||||||
<th className="text-left py-2 px-3">Assessment</th>
|
<th className="text-left py-2 px-3">Leistungsüberprüfung</th>
|
||||||
<th className="text-center py-2 px-3">Score</th>
|
<th className="text-center py-2 px-3">Punkte</th>
|
||||||
<th className="text-center py-2 px-3">Percentage</th>
|
<th className="text-center py-2 px-3">Prozent</th>
|
||||||
<th className="text-center py-2 px-3">Status</th>
|
<th className="text-center py-2 px-3">Status</th>
|
||||||
<th className="text-right py-2 px-3">Actions</th>
|
<th className="text-right py-2 px-3">Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -314,10 +314,10 @@ export function UnifiedGradingPanel({
|
|||||||
<td className="py-2 px-3">{grade.subject}</td>
|
<td className="py-2 px-3">{grade.subject}</td>
|
||||||
<td className="py-2 px-3">{grade.assessment}</td>
|
<td className="py-2 px-3">{grade.assessment}</td>
|
||||||
<td className="py-2 px-3 text-center font-semibold">
|
<td className="py-2 px-3 text-center font-semibold">
|
||||||
{grade.score}/{grade.maxScore}
|
{grade.score}/{grade.maxPunkte}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-3 text-center">
|
<td className="py-2 px-3 text-center">
|
||||||
{((grade.score / grade.maxScore) * 100).toFixed(1)}%
|
{((grade.score / grade.maxPunkte) * 100).toFixed(1)}%
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-3 text-center">
|
<td className="py-2 px-3 text-center">
|
||||||
<Badge
|
<Badge
|
||||||
@@ -373,7 +373,7 @@ export function UnifiedGradingPanel({
|
|||||||
<h3 className="font-semibold">{grade.studentName}</h3>
|
<h3 className="font-semibold">{grade.studentName}</h3>
|
||||||
<p className="text-sm text-muted-foreground">{grade.subject}</p>
|
<p className="text-sm text-muted-foreground">{grade.subject}</p>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-lg font-bold">{grade.score}/{grade.maxScore}</span>
|
<span className="text-lg font-bold">{grade.score}/{grade.maxPunkte}</span>
|
||||||
<Badge>{grade.status}</Badge>
|
<Badge>{grade.status}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -404,7 +404,7 @@ export function UnifiedGradingPanel({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right space-y-1">
|
<div className="text-right space-y-1">
|
||||||
<div className="font-semibold">{grade.score}/{grade.maxScore}</div>
|
<div className="font-semibold">{grade.score}/{grade.maxPunkte}</div>
|
||||||
<Badge variant="outline">{grade.status}</Badge>
|
<Badge variant="outline">{grade.status}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -414,11 +414,11 @@ export function UnifiedGradingPanel({
|
|||||||
|
|
||||||
// Analytics view (replaces the removed grade-analytics nav entry)
|
// Analytics view (replaces the removed grade-analytics nav entry)
|
||||||
const renderAnalyticsView = () => {
|
const renderAnalyticsView = () => {
|
||||||
const avgScore = grades.length > 0
|
const avgPunkte = grades.length > 0
|
||||||
? (grades.reduce((s, g) => s + (g.score / g.maxScore) * 100, 0) / grades.length).toFixed(1)
|
? (grades.reduce((s, g) => s + (g.score / g.maxPunkte) * 100, 0) / grades.length).toFixed(1)
|
||||||
: '0';
|
: '0';
|
||||||
const passRate = grades.length > 0
|
const passRate = grades.length > 0
|
||||||
? Math.round((grades.filter(g => (g.score / g.maxScore) >= 0.5).length / grades.length) * 100)
|
? Math.round((grades.filter(g => (g.score / g.maxPunkte) >= 0.5).length / grades.length) * 100)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// Distribution by subject
|
// Distribution by subject
|
||||||
@@ -426,7 +426,7 @@ export function UnifiedGradingPanel({
|
|||||||
grades.forEach(g => {
|
grades.forEach(g => {
|
||||||
if (!subjectStats[g.subject]) subjectStats[g.subject] = { count: 0, avg: 0 };
|
if (!subjectStats[g.subject]) subjectStats[g.subject] = { count: 0, avg: 0 };
|
||||||
subjectStats[g.subject].count++;
|
subjectStats[g.subject].count++;
|
||||||
subjectStats[g.subject].avg += (g.score / g.maxScore) * 100;
|
subjectStats[g.subject].avg += (g.score / g.maxPunkte) * 100;
|
||||||
});
|
});
|
||||||
Object.keys(subjectStats).forEach(s => {
|
Object.keys(subjectStats).forEach(s => {
|
||||||
subjectStats[s].avg = Math.round(subjectStats[s].avg / subjectStats[s].count);
|
subjectStats[s].avg = Math.round(subjectStats[s].avg / subjectStats[s].count);
|
||||||
@@ -436,13 +436,13 @@ export function UnifiedGradingPanel({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Summary cards */}
|
{/* Summary cards */}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Durchschnitt</p><p className="text-2xl font-bold text-emerald-600">{avgScore}%</p></CardContent></Card>
|
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Durchschnitt</p><p className="text-2xl font-bold text-emerald-600">{avgPunkte}%</p></CardContent></Card>
|
||||||
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Bestehensquote</p><p className="text-2xl font-bold text-sky-600">{passRate}%</p></CardContent></Card>
|
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Bestehensquote</p><p className="text-2xl font-bold text-sky-600">{passRate}%</p></CardContent></Card>
|
||||||
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Noten gesamt</p><p className="text-2xl font-bold">{grades.length}</p></CardContent></Card>
|
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Noten gesamt</p><p className="text-2xl font-bold">{grades.length}</p></CardContent></Card>
|
||||||
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Fächer</p><p className="text-2xl font-bold">{Object.keys(subjectStats).length}</p></CardContent></Card>
|
<Card><CardContent className="p-4"><p className="text-xs text-muted-foreground">Fächer</p><p className="text-2xl font-bold">{Object.keys(subjectStats).length}</p></CardContent></Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Subject breakdown */}
|
{/* Fach breakdown */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader><CardTitle className="text-base">Noten nach Fach</CardTitle></CardHeader>
|
<CardHeader><CardTitle className="text-base">Noten nach Fach</CardTitle></CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
@@ -477,7 +477,7 @@ export function UnifiedGradingPanel({
|
|||||||
{ label: 'ueber 90%', min: 90, max: 101, color: 'bg-emerald-500' },
|
{ label: 'ueber 90%', min: 90, max: 101, color: 'bg-emerald-500' },
|
||||||
].map(bucket => {
|
].map(bucket => {
|
||||||
const count = grades.filter(g => {
|
const count = grades.filter(g => {
|
||||||
const pct = (g.score / g.maxScore) * 100;
|
const pct = (g.score / g.maxPunkte) * 100;
|
||||||
return pct >= bucket.min && pct < bucket.max;
|
return pct >= bucket.min && pct < bucket.max;
|
||||||
}).length;
|
}).length;
|
||||||
const max = Math.max(grades.length, 1);
|
const max = Math.max(grades.length, 1);
|
||||||
@@ -510,15 +510,15 @@ export function UnifiedGradingPanel({
|
|||||||
{/* Header with filters */}
|
{/* Header with filters */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<h2 className="text-2xl font-bold">Grading Panel</h2>
|
<h2 className="text-2xl font-bold">Benotung</h2>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button size="sm" variant="outline">
|
<Button size="sm" variant="outline">
|
||||||
<Download className="h-4 w-4 mr-2" />
|
<Download className="h-4 w-4 mr-2" />
|
||||||
Export
|
Exportieren
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="outline">
|
<Button size="sm" variant="outline">
|
||||||
<Upload className="h-4 w-4 mr-2" />
|
<Upload className="h-4 w-4 mr-2" />
|
||||||
Import
|
Importieren
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -526,17 +526,17 @@ export function UnifiedGradingPanel({
|
|||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search grades..."
|
placeholder="Noten suchen..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select value={filterSubject} onValueChange={setFilterSubject}>
|
<Select value={filterFach} onValueChange={setFilterFach}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="All subjects" />
|
<SelectValue placeholder="Alle Fächer" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All subjects</SelectItem>
|
<SelectItem value="all">Alle Fächer</SelectItem>
|
||||||
{subjects.map((subject) => (
|
{subjects.map((subject) => (
|
||||||
<SelectItem key={subject} value={subject}>
|
<SelectItem key={subject} value={subject}>
|
||||||
{subject}
|
{subject}
|
||||||
@@ -547,13 +547,13 @@ export function UnifiedGradingPanel({
|
|||||||
|
|
||||||
<Select value={filterStatus} onValueChange={(v) => setFilterStatus(v as any)}>
|
<Select value={filterStatus} onValueChange={(v) => setFilterStatus(v as any)}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="All statuses" />
|
<SelectValue placeholder="Alle Status" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All statuses</SelectItem>
|
<SelectItem value="all">Alle Status</SelectItem>
|
||||||
<SelectItem value="pending">Pending</SelectItem>
|
<SelectItem value="pending">Ausstehend</SelectItem>
|
||||||
<SelectItem value="graded">Graded</SelectItem>
|
<SelectItem value="graded">Bewertet</SelectItem>
|
||||||
<SelectItem value="released">Released</SelectItem>
|
<SelectItem value="released">Veröffentlicht</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
@@ -628,14 +628,14 @@ function GradeEditDialog({
|
|||||||
<Dialog open onOpenChange={onClose}>
|
<Dialog open onOpenChange={onClose}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Edit Grade: {grade.studentName}</DialogTitle>
|
<DialogTitle>Note bearbeiten: {grade.studentName}</DialogTitle>
|
||||||
<DialogDescription>{grade.subject} - {grade.assessment}</DialogDescription>
|
<DialogDescription>{grade.subject} - {grade.assessment}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<Label>Score</Label>
|
<Label>Punkte</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.score}
|
value={formData.score}
|
||||||
@@ -643,25 +643,25 @@ function GradeEditDialog({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Max Score</Label>
|
<Label>Max. Punkte</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.maxScore}
|
value={formData.maxPunkte}
|
||||||
onChange={(e) => setFormData({ ...formData, maxScore: parseFloat(e.target.value) })}
|
onChange={(e) => setFormData({ ...formData, maxPunkte: parseFloat(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Percentage: {((formData.score / formData.maxScore) * 100).toFixed(1)}%</Label>
|
<Label>Prozent: {((formData.score / formData.maxPunkte) * 100).toFixed(1)}%</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Comment</Label>
|
<Label>Kommentar</Label>
|
||||||
<Input
|
<Input
|
||||||
value={formData.comment || ''}
|
value={formData.comment || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, comment: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, comment: e.target.value })}
|
||||||
placeholder="Add a comment..."
|
placeholder="Kommentar hinzufügen..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -687,7 +687,7 @@ function GradeEditDialog({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Save Changes
|
Speichern
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
Reference in new issue
Block a user