Split 1257-LOC client page into _types.ts plus nine components under _components/ (TabNavigation/StatCard/FilterBar in shared, CourseCard, EnrollmentCard, CertificatesTab, EnrollmentEditModal, CourseEditModal, SettingsTab, and PageSections for header actions and empty states). Behavior preserved exactly; page.tsx is now a thin wiring shell. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
86 lines
4.9 KiB
TypeScript
86 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import Link from 'next/link'
|
|
import { Course, COURSE_CATEGORY_INFO } from '@/lib/sdk/academy/types'
|
|
|
|
export function CourseCard({ course, enrollmentCount, onEdit }: { course: Course; enrollmentCount: number; onEdit?: (course: Course) => void }) {
|
|
const categoryInfo = COURSE_CATEGORY_INFO[course.category] || COURSE_CATEGORY_INFO['custom']
|
|
|
|
return (
|
|
<div className="relative group">
|
|
<Link href={`/sdk/academy/${course.id}`}>
|
|
<div className="bg-white rounded-xl border-2 p-6 hover:shadow-md transition-all cursor-pointer border-gray-200 hover:border-purple-300">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${categoryInfo.bgColor} ${categoryInfo.color}`}>
|
|
{categoryInfo.label}
|
|
</span>
|
|
{course.status === 'published' && (
|
|
<span className="px-2 py-1 text-xs rounded-full bg-green-100 text-green-700">Veroeffentlicht</span>
|
|
)}
|
|
</div>
|
|
<h3 className="text-lg font-semibold text-gray-900 truncate">{course.title}</h3>
|
|
<p className="text-sm text-gray-500 mt-1 line-clamp-2">{course.description}</p>
|
|
<div className="mt-3 flex items-center gap-4 text-sm text-gray-500">
|
|
<span className="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="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" />
|
|
</svg>
|
|
{course.lessons.length} Lektionen
|
|
</span>
|
|
<span className="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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
{course.durationMinutes} Min.
|
|
</span>
|
|
<span className="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="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
{enrollmentCount} Teilnehmer
|
|
</span>
|
|
<span className="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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
Bestehensgrenze: {course.passingScore}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="text-right ml-4 text-gray-500">
|
|
<div className="text-sm font-medium">
|
|
{course.requiredForRoles.includes('all') ? 'Pflicht fuer alle' : `${course.requiredForRoles.length} Rollen`}
|
|
</div>
|
|
<div className="text-xs mt-0.5">
|
|
{new Date(course.updatedAt).toLocaleDateString('de-DE')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 pt-4 border-t border-gray-100 flex items-center justify-between">
|
|
<div className="text-sm text-gray-500">
|
|
Erstellt: {new Date(course.createdAt).toLocaleDateString('de-DE')}
|
|
</div>
|
|
<span className="px-3 py-1 text-sm text-purple-600 hover:bg-purple-50 rounded-lg transition-colors">
|
|
Details
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
{onEdit && (
|
|
<button
|
|
onClick={(e) => { e.preventDefault(); onEdit(course) }}
|
|
className="absolute top-3 right-3 p-1.5 bg-white rounded-lg shadow border border-gray-200 text-gray-400 hover:text-purple-600 hover:border-purple-300 opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
title="Kurs bearbeiten"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|