Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
4.9 KiB
TypeScript
174 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import { Calendar, FileQuestion, Users, Clock, ChevronRight } from 'lucide-react'
|
|
import { UpcomingEvent, EventType } from '@/lib/companion/types'
|
|
import { EVENT_TYPE_CONFIG } from '@/lib/companion/constants'
|
|
|
|
interface EventsCardProps {
|
|
events: UpcomingEvent[]
|
|
onEventClick?: (event: UpcomingEvent) => void
|
|
loading?: boolean
|
|
maxItems?: number
|
|
}
|
|
|
|
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
|
FileQuestion,
|
|
Users,
|
|
Clock,
|
|
Calendar,
|
|
}
|
|
|
|
function getEventIcon(type: EventType) {
|
|
const config = EVENT_TYPE_CONFIG[type]
|
|
const Icon = iconMap[config.icon] || Calendar
|
|
return { Icon, ...config }
|
|
}
|
|
|
|
function formatEventDate(dateStr: string, inDays: number): string {
|
|
if (inDays === 0) return 'Heute'
|
|
if (inDays === 1) return 'Morgen'
|
|
if (inDays < 7) return `In ${inDays} Tagen`
|
|
|
|
const date = new Date(dateStr)
|
|
return date.toLocaleDateString('de-DE', {
|
|
weekday: 'short',
|
|
day: 'numeric',
|
|
month: 'short',
|
|
})
|
|
}
|
|
|
|
interface EventItemProps {
|
|
event: UpcomingEvent
|
|
onClick?: () => void
|
|
}
|
|
|
|
function EventItem({ event, onClick }: EventItemProps) {
|
|
const { Icon, color, bg } = getEventIcon(event.type)
|
|
const isUrgent = event.inDays <= 2
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`
|
|
w-full flex items-center gap-3 p-3 rounded-lg
|
|
transition-all duration-200
|
|
hover:bg-slate-50
|
|
${isUrgent ? 'bg-red-50/50' : ''}
|
|
`}
|
|
>
|
|
<div className={`p-2 rounded-lg ${bg}`}>
|
|
<Icon className={`w-4 h-4 ${color}`} />
|
|
</div>
|
|
|
|
<div className="flex-1 text-left min-w-0">
|
|
<p className="font-medium text-slate-900 truncate">{event.title}</p>
|
|
<p className={`text-sm ${isUrgent ? 'text-red-600 font-medium' : 'text-slate-500'}`}>
|
|
{formatEventDate(event.date, event.inDays)}
|
|
</p>
|
|
</div>
|
|
|
|
<ChevronRight className="w-4 h-4 text-slate-400 flex-shrink-0" />
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export function EventsCard({
|
|
events,
|
|
onEventClick,
|
|
loading,
|
|
maxItems = 5,
|
|
}: EventsCardProps) {
|
|
const displayEvents = events.slice(0, maxItems)
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Calendar className="w-5 h-5 text-blue-500" />
|
|
<h3 className="font-semibold text-slate-900">Termine</h3>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{[1, 2, 3].map((i) => (
|
|
<div key={i} className="h-14 bg-slate-100 rounded-lg animate-pulse" />
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (events.length === 0) {
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Calendar className="w-5 h-5 text-blue-500" />
|
|
<h3 className="font-semibold text-slate-900">Termine</h3>
|
|
</div>
|
|
<div className="text-center py-6">
|
|
<Calendar className="w-10 h-10 text-slate-300 mx-auto mb-2" />
|
|
<p className="text-sm text-slate-500">Keine anstehenden Termine</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="w-5 h-5 text-blue-500" />
|
|
<h3 className="font-semibold text-slate-900">Termine</h3>
|
|
</div>
|
|
<span className="text-sm text-slate-500">
|
|
{events.length} Termin{events.length !== 1 ? 'e' : ''}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
{displayEvents.map((event) => (
|
|
<EventItem
|
|
key={event.id}
|
|
event={event}
|
|
onClick={() => onEventClick?.(event)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{events.length > maxItems && (
|
|
<button className="w-full mt-3 py-2 text-sm text-slate-600 hover:text-slate-900 hover:bg-slate-50 rounded-lg transition-colors">
|
|
Alle {events.length} anzeigen
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Compact inline version for header/toolbar
|
|
*/
|
|
export function EventsInline({ events }: { events: UpcomingEvent[] }) {
|
|
const nextEvent = events[0]
|
|
|
|
if (!nextEvent) {
|
|
return (
|
|
<div className="flex items-center gap-2 text-sm text-slate-500">
|
|
<Calendar className="w-4 h-4" />
|
|
<span>Keine Termine</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const { Icon, color } = getEventIcon(nextEvent.type)
|
|
const isUrgent = nextEvent.inDays <= 2
|
|
|
|
return (
|
|
<div className={`flex items-center gap-2 text-sm ${isUrgent ? 'text-red-600' : 'text-slate-600'}`}>
|
|
<Icon className={`w-4 h-4 ${color}`} />
|
|
<span className="truncate max-w-[150px]">{nextEvent.title}</span>
|
|
<span className="text-slate-400">-</span>
|
|
<span className={isUrgent ? 'font-medium' : ''}>
|
|
{formatEventDate(nextEvent.date, nextEvent.inDays)}
|
|
</span>
|
|
</div>
|
|
)
|
|
}
|