Fix: Use @shared/* alias instead of relative paths for Docker compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,42 +14,66 @@ interface LearningUnit {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface UnitProgress {
|
||||
total_stars?: number
|
||||
exercises?: Record<string, { completed?: number; correct?: number; incorrect?: number }>
|
||||
}
|
||||
|
||||
interface UnitCardProps {
|
||||
unit: LearningUnit
|
||||
progress?: UnitProgress | null
|
||||
wordCount?: number
|
||||
isDark: boolean
|
||||
glassCard: string
|
||||
onDelete: (id: string) => void
|
||||
}
|
||||
|
||||
const exerciseTypes = [
|
||||
{ key: 'flashcards', label: 'Karteikarten', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10', color: 'from-amber-500 to-orange-500' },
|
||||
{ key: 'flashcards', label: 'Karten', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10', color: 'from-amber-500 to-orange-500' },
|
||||
{ key: 'quiz', label: 'Quiz', icon: 'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z', color: 'from-purple-500 to-pink-500' },
|
||||
{ key: 'type', label: 'Eintippen', icon: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z', color: 'from-blue-500 to-cyan-500' },
|
||||
{ key: 'story', label: 'Geschichte', icon: 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253', color: 'from-amber-500 to-yellow-500' },
|
||||
{ key: 'type', label: 'Tippen', icon: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z', color: 'from-blue-500 to-cyan-500' },
|
||||
{ key: 'listen', label: 'Hoeren', icon: 'M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z', color: 'from-green-500 to-emerald-500' },
|
||||
{ key: 'match', label: 'Zuordnen', icon: 'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1', color: 'from-indigo-500 to-violet-500' },
|
||||
{ key: 'pronounce', label: 'Sprechen', icon: 'M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z', color: 'from-rose-500 to-red-500' },
|
||||
]
|
||||
|
||||
export function UnitCard({ unit, isDark, glassCard, onDelete }: UnitCardProps) {
|
||||
export function UnitCard({ unit, progress, wordCount, isDark, glassCard, onDelete }: UnitCardProps) {
|
||||
const createdDate = new Date(unit.created_at).toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||
})
|
||||
|
||||
// Calculate progress percentage from exercises
|
||||
const totalCorrect = progress?.exercises
|
||||
? Object.values(progress.exercises).reduce((s, e) => s + (e?.correct || 0), 0)
|
||||
: 0
|
||||
const totalAnswered = progress?.exercises
|
||||
? Object.values(progress.exercises).reduce((s, e) => s + (e?.completed || 0), 0)
|
||||
: 0
|
||||
const progressPct = totalAnswered > 0 ? Math.round((totalCorrect / totalAnswered) * 100) : 0
|
||||
const stars = progress?.total_stars || 0
|
||||
|
||||
return (
|
||||
<div className={`${glassCard} rounded-2xl p-6 transition-all hover:shadow-lg`}>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
{unit.label}
|
||||
</h3>
|
||||
<p className={`text-sm mt-1 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
||||
{unit.meta}
|
||||
<div className={`${glassCard} rounded-2xl p-5 transition-all hover:shadow-lg`}>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className={`text-lg font-semibold truncate ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
{unit.label}
|
||||
</h3>
|
||||
{stars > 0 && (
|
||||
<span className="flex items-center gap-0.5 text-yellow-400 text-sm font-bold">
|
||||
<span>⭐</span>{stars}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className={`text-sm mt-0.5 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
||||
{wordCount ? `${wordCount} Woerter` : unit.meta} · {createdDate}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onDelete(unit.id)}
|
||||
className={`p-2 rounded-lg transition-colors ${isDark ? 'hover:bg-white/10 text-white/40 hover:text-red-400' : 'hover:bg-slate-100 text-slate-400 hover:text-red-500'}`}
|
||||
title="Loeschen"
|
||||
className={`p-1.5 rounded-lg transition-colors ${isDark ? 'hover:bg-white/10 text-white/30 hover:text-red-400' : 'hover:bg-slate-100 text-slate-300 hover:text-red-500'}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
@@ -57,35 +81,36 @@ export function UnitCard({ unit, isDark, glassCard, onDelete }: UnitCardProps) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Exercise Type Buttons */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* Progress Bar */}
|
||||
{totalAnswered > 0 && (
|
||||
<div className="mb-3">
|
||||
<div className={`h-2 rounded-full overflow-hidden ${isDark ? 'bg-white/10' : 'bg-slate-100'}`}>
|
||||
<div
|
||||
className="h-full rounded-full bg-gradient-to-r from-blue-500 to-green-500 transition-all duration-500"
|
||||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className={`text-xs mt-1 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
|
||||
{progressPct}% · {totalCorrect}/{totalAnswered} richtig
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Exercise Buttons */}
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{exerciseTypes.map((ex) => (
|
||||
<Link
|
||||
key={ex.key}
|
||||
href={`/learn/${unit.id}/${ex.key}`}
|
||||
className={`flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium text-white bg-gradient-to-r ${ex.color} hover:shadow-lg hover:scale-[1.02] transition-all`}
|
||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium text-white bg-gradient-to-r ${ex.color} hover:shadow-md hover:scale-[1.02] transition-all`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d={ex.icon} />
|
||||
</svg>
|
||||
{ex.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className={`flex items-center gap-3 mt-4 pt-3 border-t ${isDark ? 'border-white/10' : 'border-black/5'}`}>
|
||||
<span className={`text-xs ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
|
||||
Erstellt: {createdDate}
|
||||
</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${
|
||||
unit.status === 'qa_generated' || unit.status === 'mc_generated' || unit.status === 'cloze_generated'
|
||||
? (isDark ? 'bg-green-500/20 text-green-300' : 'bg-green-100 text-green-700')
|
||||
: (isDark ? 'bg-yellow-500/20 text-yellow-300' : 'bg-yellow-100 text-yellow-700')
|
||||
}`}>
|
||||
{unit.status === 'raw' ? 'Neu' : 'Module generiert'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user