Fixed files: - admin/compliance-overview/page.tsx: 2 strings (error message, consultations) - admin/exam-proctoring/page.tsx: 1 string (Alerts) - admin/signage/page.tsx: 15 strings (tabs, labels, options, buttons) - professional-calendar.tsx: 4 strings (event title, date, location, description) All admin pages and calendar components now fully German.
716 lines
22 KiB
TypeScript
716 lines
22 KiB
TypeScript
// @ts-nocheck
|
|
'use client';
|
|
|
|
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import {
|
|
format,
|
|
startOfMonth,
|
|
endOfMonth,
|
|
startOfWeek,
|
|
endOfWeek,
|
|
addDays,
|
|
addMonths,
|
|
isSameDay,
|
|
isSameMonth,
|
|
isToday as isDateToday,
|
|
parseISO,
|
|
differenceInCalendarDays,
|
|
} from 'date-fns';
|
|
import { de as deLocale, enUS as enLocale } from 'date-fns/locale';
|
|
import {
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
Plus,
|
|
Edit,
|
|
Trash2,
|
|
Copy,
|
|
Eye,
|
|
GripVertical,
|
|
X,
|
|
Clock,
|
|
MapPin,
|
|
Users,
|
|
AlertCircle,
|
|
Check,
|
|
MoreVertical,
|
|
} from 'lucide-react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
ContextMenu,
|
|
ContextMenuContent,
|
|
ContextMenuItem,
|
|
ContextMenuTrigger,
|
|
ContextMenuSeparator,
|
|
} from '@/components/ui/context-menu';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
DropdownMenuSeparator,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { CalendarManager, CalendarEvent, CalendarEventType } from '@/lib/calendar-manager';
|
|
import { useApiGet } from '@/lib/hooks/useApi';
|
|
import { toast } from 'sonner';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface ProfessionalCalendarProps {
|
|
initialEvents?: CalendarEvent[];
|
|
onEventCreate?: (event: CalendarEvent) => Promise<void>;
|
|
onEventUpdate?: (event: CalendarEvent) => Promise<void>;
|
|
onEventDelete?: (eventId: string) => Promise<void>;
|
|
variant?: 'month' | 'week' | 'day' | 'agenda';
|
|
locale?: 'de' | 'en';
|
|
className?: string;
|
|
}
|
|
|
|
export function ProfessionalCalendar({
|
|
initialEvents = [],
|
|
onEventCreate,
|
|
onEventUpdate,
|
|
onEventDelete,
|
|
variant = 'month',
|
|
locale = 'de',
|
|
className,
|
|
}: ProfessionalCalendarProps) {
|
|
const [currentDate, setCurrentDate] = useState(new Date());
|
|
const [events, setEvents] = useState<CalendarEvent[]>(initialEvents);
|
|
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null);
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
const [draggedEvent, setDraggedEvent] = useState<CalendarEvent | null>(null);
|
|
const [view, setView] = useState<'month' | 'week' | 'day' | 'agenda'>(variant);
|
|
const [eventFilter, setEventFilter] = useState<'all' | 'exams' | 'events' | 'holidays'>('all');
|
|
const contextMenuRef = useRef<{ x: number; y: number } | null>(null);
|
|
|
|
const dateLocale = locale === 'de' ? deLocale : enLocale;
|
|
|
|
// Fetch events from backend
|
|
const monthParam = format(currentDate, 'yyyy-MM');
|
|
const { data: backendEvents, isLoading } = useApiGet(
|
|
`/api/calendar-events?month=${monthParam}`,
|
|
{ revalidateOnFocus: true }
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (backendEvents) {
|
|
setEvents(backendEvents);
|
|
}
|
|
}, [backendEvents]);
|
|
|
|
// Calculate calendar grid
|
|
const monthStart = startOfMonth(currentDate);
|
|
const monthEnd = endOfMonth(monthStart);
|
|
const calendarStart = startOfWeek(monthStart);
|
|
const calendarEnd = endOfWeek(monthEnd);
|
|
|
|
const days: Date[] = [];
|
|
let day = new Date(calendarStart);
|
|
while (day <= calendarEnd) {
|
|
days.push(new Date(day));
|
|
day = addDays(day, 1);
|
|
}
|
|
|
|
// Get events for current month
|
|
const monthEvents = useMemo(() => {
|
|
return CalendarManager.getEventsInRange(events, monthStart, monthEnd, {
|
|
showPast: true,
|
|
});
|
|
}, [events, monthStart, monthEnd]);
|
|
|
|
// Get events for a specific date
|
|
// Filtered events based on the active filter tab
|
|
const filteredEvents = useMemo(() => {
|
|
if (eventFilter === 'all') return events;
|
|
const filterMap: Record<string, string[]> = {
|
|
exams: ['EXAM', 'TEST', 'ASSESSMENT'],
|
|
events: ['EVENT', 'ACTIVITY', 'MEETING', 'TRIP'],
|
|
holidays: ['HOLIDAY', 'BREAK'],
|
|
};
|
|
const types = filterMap[eventFilter] || [];
|
|
return events.filter(e => types.includes(e.type?.toUpperCase() || ''));
|
|
}, [events, eventFilter]);
|
|
|
|
const getEventsForDate = useCallback((date: Date): CalendarEvent[] => {
|
|
return CalendarManager.getEventsForDate(filteredEvents, date);
|
|
}, [filteredEvents]);
|
|
|
|
// Handle event creation
|
|
const handleCreateEvent = useCallback(
|
|
async (eventData: Omit<CalendarEvent, 'id' | 'createdAt'>) => {
|
|
try {
|
|
const newEvent: CalendarEvent = {
|
|
...eventData,
|
|
id: `event-${Date.now()}`,
|
|
createdAt: new Date(),
|
|
};
|
|
|
|
setEvents((prev) => [...prev, newEvent]);
|
|
if (onEventCreate) {
|
|
await onEventCreate(newEvent);
|
|
}
|
|
toast.success('Ereignis erfolgreich erstellt');
|
|
setIsCreating(false);
|
|
} catch (error) {
|
|
toast.error('Failed to create event');
|
|
console.error(error);
|
|
}
|
|
},
|
|
[onEventCreate]
|
|
);
|
|
|
|
// Handle event update
|
|
const handleUpdateEvent = useCallback(
|
|
async (event: CalendarEvent) => {
|
|
try {
|
|
setEvents((prev) =>
|
|
prev.map((e) => (e.id === event.id ? { ...event, updatedAt: new Date() } : e))
|
|
);
|
|
if (onEventUpdate) {
|
|
await onEventUpdate(event);
|
|
}
|
|
toast.success('Ereignis aktualisiert successfully');
|
|
} catch (error) {
|
|
toast.error('Failed to update event');
|
|
console.error(error);
|
|
}
|
|
},
|
|
[onEventUpdate]
|
|
);
|
|
|
|
// Handle event deletion
|
|
const handleDeleteEvent = useCallback(
|
|
async (eventId: string) => {
|
|
try {
|
|
setEvents((prev) => prev.filter((e) => e.id !== eventId));
|
|
if (onEventDelete) {
|
|
await onEventDelete(eventId);
|
|
}
|
|
setSelectedEvent(null);
|
|
toast.success('Ereignis gelöscht successfully');
|
|
} catch (error) {
|
|
toast.error('Failed to delete event');
|
|
console.error(error);
|
|
}
|
|
},
|
|
[onEventDelete]
|
|
);
|
|
|
|
// Handle drag-and-drop
|
|
const handleDragStart = (e: React.DragEvent, event: CalendarEvent) => {
|
|
setDraggedEvent(event);
|
|
e.dataTransfer.effectAllowed = 'move';
|
|
};
|
|
|
|
const handleDragOver = (e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = 'move';
|
|
};
|
|
|
|
const handleDrop = (e: React.DragEvent, targetDate: Date) => {
|
|
e.preventDefault();
|
|
if (!draggedEvent) return;
|
|
|
|
const newEvent = {
|
|
...draggedEvent,
|
|
date: targetDate,
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
handleUpdateEvent(newEvent);
|
|
setDraggedEvent(null);
|
|
toast.success('Event moved successfully');
|
|
};
|
|
|
|
// Handle right-click context menu
|
|
const handleContextMenu = (
|
|
e: React.MouseEvent,
|
|
event: CalendarEvent
|
|
) => {
|
|
e.preventDefault();
|
|
contextMenuRef.current = { x: e.clientX, y: e.clientY };
|
|
setSelectedEvent(event);
|
|
};
|
|
|
|
// Render calendar cell
|
|
const renderCalendarCell = (date: Date) => {
|
|
const dateEvents = getEventsForDate(date);
|
|
const isCurrentMonth = isSameMonth(date, currentDate);
|
|
const isToday = isDateToday(date);
|
|
|
|
return (
|
|
<motion.div
|
|
key={format(date, 'yyyy-MM-dd')}
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
onDragOver={handleDragOver}
|
|
onDrop={(e) => handleDrop(e, date)}
|
|
className={cn(
|
|
'min-h-24 p-2 border rounded-lg transition-colors',
|
|
isCurrentMonth ? 'bg-background' : 'bg-muted/30',
|
|
isToday && 'ring-2 ring-primary border-primary',
|
|
'hover:bg-accent/50 cursor-pointer'
|
|
)}
|
|
>
|
|
{/* Date header */}
|
|
<div className={cn(
|
|
'text-xs font-semibold mb-1',
|
|
isCurrentMonth ? 'text-foreground' : 'text-muted-foreground',
|
|
isToday && 'text-primary'
|
|
)}>
|
|
{format(date, 'd')}
|
|
</div>
|
|
|
|
{/* Events */}
|
|
<div className="space-y-1">
|
|
{dateEvents.slice(0, 2).map((event) => (
|
|
<motion.div
|
|
key={event.id}
|
|
draggable
|
|
onDragStart={(e) => handleDragStart(e, event)}
|
|
onContextMenu={(e) => handleContextMenu(e, event)}
|
|
onClick={() => setSelectedEvent(event)}
|
|
className={cn(
|
|
'text-xs p-1 rounded cursor-grab active:cursor-grabbing',
|
|
'truncate hover:opacity-80 transition-opacity',
|
|
CalendarManager.getColorForType(event.type)
|
|
)}
|
|
whileHover={{ scale: 1.05 }}
|
|
whileDrag={{ scale: 1.1, opacity: 0.7 }}
|
|
>
|
|
<div className="flex items-center gap-1">
|
|
<span>{CalendarManager.getIconForType(event.type)}</span>
|
|
<span className="truncate">{event.title}</span>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
{dateEvents.length > 2 && (
|
|
<div className="text-xs text-muted-foreground pl-1">
|
|
+{dateEvents.length - 2} more
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Add event button */}
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
className="mt-1 h-6 w-full text-xs opacity-0 hover:opacity-100"
|
|
onClick={() => {
|
|
setIsCreating(true);
|
|
}}
|
|
>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
Add
|
|
</Button>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className={cn('space-y-4', className)}>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setCurrentDate(addMonths(currentDate, -1))}
|
|
>
|
|
<ChevronLeft className="h-4 w-4" />
|
|
</Button>
|
|
<h2 className="text-lg font-semibold min-w-40">
|
|
{format(currentDate, 'MMMM yyyy', { locale: dateLocale })}
|
|
</h2>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setCurrentDate(addMonths(currentDate, 1))}
|
|
>
|
|
<ChevronRight className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setCurrentDate(new Date())}
|
|
>
|
|
Heute
|
|
</Button>
|
|
</div>
|
|
|
|
{/* View tabs */}
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<Tabs value={view} onValueChange={(v) => setView(v as any)}>
|
|
<TabsList>
|
|
<TabsTrigger value="month">Monat</TabsTrigger>
|
|
<TabsTrigger value="week">Woche</TabsTrigger>
|
|
<TabsTrigger value="agenda">Agenda</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
|
|
{/* Event filter tabs (replaces exam-calendar and school-events) */}
|
|
<Tabs value={eventFilter} onValueChange={(v) => setEventFilter(v as any)}>
|
|
<TabsList>
|
|
<TabsTrigger value="all">Alle</TabsTrigger>
|
|
<TabsTrigger value="exams">Prüfungen</TabsTrigger>
|
|
<TabsTrigger value="events">Veranstaltungen</TabsTrigger>
|
|
<TabsTrigger value="holidays">Ferien</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={() => setIsCreating(true)}
|
|
className="gap-2"
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
Neues Ereignis
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Calendar grid */}
|
|
{view === 'month' && (
|
|
<div className="border rounded-lg overflow-hidden bg-card">
|
|
{/* Weekday headers */}
|
|
<div className="grid grid-cols-7 bg-muted">
|
|
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((day) => (
|
|
<div key={day} className="p-3 text-sm font-semibold text-center">
|
|
{day}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Calendar cells */}
|
|
<div className="grid grid-cols-7 gap-px bg-border">
|
|
{days.map((day) => (
|
|
<div key={format(day, 'yyyy-MM-dd')} className="bg-background">
|
|
{renderCalendarCell(day)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Agenda view */}
|
|
{view === 'agenda' && (
|
|
<div className="space-y-2">
|
|
{monthEvents.map((event) => (
|
|
<motion.div
|
|
key={event.id}
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
className="p-3 border rounded-lg hover:bg-accent/50 cursor-pointer transition-colors"
|
|
onClick={() => setSelectedEvent(event)}
|
|
onContextMenu={(e) => handleContextMenu(e, event)}
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-lg">{CalendarManager.getIconForType(event.type)}</span>
|
|
<h3 className="font-semibold">{event.title}</h3>
|
|
<Badge variant="secondary">{event.type}</Badge>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{format(event.date, 'PPP', { locale: dateLocale })} um {CalendarManager.formatTimeRange(event)}
|
|
</p>
|
|
{event.description && (
|
|
<p className="text-sm mt-2">{event.description}</p>
|
|
)}
|
|
</div>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="sm">
|
|
<MoreVertical className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent>
|
|
<DropdownMenuItem onClick={() => setSelectedEvent(event)}>
|
|
<Edit className="h-4 w-4 mr-2" />
|
|
Edit
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={() => handleDeleteEvent(event.id)}>
|
|
<Trash2 className="h-4 w-4 mr-2 text-red-500" />
|
|
Delete
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Event detail dialog */}
|
|
<AnimatePresence>
|
|
{selectedEvent && (
|
|
<EventDetailDialog
|
|
event={selectedEvent}
|
|
onClose={() => setSelectedEvent(null)}
|
|
onUpdate={handleUpdateEvent}
|
|
onDelete={handleDeleteEvent}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* Create/Edit event dialog */}
|
|
{isCreating && (
|
|
<EventCreatorDialog
|
|
onClose={() => setIsCreating(false)}
|
|
onCreate={handleCreateEvent}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Event Detail Dialog Component
|
|
function EventDetailDialog({
|
|
event,
|
|
onClose,
|
|
onUpdate,
|
|
onDelete,
|
|
}: {
|
|
event: CalendarEvent;
|
|
onClose: () => void;
|
|
onUpdate: (event: CalendarEvent) => void;
|
|
onDelete: (id: string) => void;
|
|
}) {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
|
|
return (
|
|
<Dialog open onOpenChange={onClose}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<span className="text-2xl">{CalendarManager.getIconForType(event.type)}</span>
|
|
{event.title}
|
|
</DialogTitle>
|
|
<Badge className="w-fit">{event.type}</Badge>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<Label className="text-xs text-muted-foreground">Datum & Zeit</Label>
|
|
<p className="text-sm">
|
|
{format(event.date, 'PPP')} um {CalendarManager.formatTimeRange(event)}
|
|
</p>
|
|
</div>
|
|
|
|
{event.location && (
|
|
<div className="flex gap-2 items-start">
|
|
<MapPin className="h-4 w-4 text-muted-foreground mt-1" />
|
|
<div>
|
|
<Label className="text-xs text-muted-foreground">Ort</Label>
|
|
<p className="text-sm">{event.location}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{event.description && (
|
|
<div>
|
|
<Label className="text-xs text-muted-foreground">Beschreibung</Label>
|
|
<p className="text-sm">{event.description}</p>
|
|
</div>
|
|
)}
|
|
|
|
{event.attendees && event.attendees.length > 0 && (
|
|
<div className="flex gap-2 items-start">
|
|
<Users className="h-4 w-4 text-muted-foreground mt-1" />
|
|
<div>
|
|
<Label className="text-xs text-muted-foreground">Teilnehmer</Label>
|
|
<p className="text-sm">{event.attendees.join(', ')}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2">
|
|
<Button variant="outline" onClick={onClose}>
|
|
Close
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => {
|
|
onDelete(event.id);
|
|
onClose();
|
|
}}
|
|
className="text-red-500 hover:text-red-600"
|
|
>
|
|
<Trash2 className="h-4 w-4 mr-2" />
|
|
Delete
|
|
</Button>
|
|
<Button onClick={() => setIsEditing(true)}>
|
|
<Edit className="h-4 w-4 mr-2" />
|
|
Edit
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
// Event Creator Dialog Component
|
|
function EventCreatorDialog({
|
|
onClose,
|
|
onCreate,
|
|
}: {
|
|
onClose: () => void;
|
|
onCreate: (event: Omit<CalendarEvent, 'id' | 'createdAt'>) => Promise<void>;
|
|
}) {
|
|
const [formData, setFormData] = useState({
|
|
title: '',
|
|
type: 'custom' as CalendarEventType,
|
|
date: new Date(),
|
|
startTime: '09:00',
|
|
endTime: '10:00',
|
|
description: '',
|
|
location: '',
|
|
allDay: false,
|
|
});
|
|
|
|
const handleSubmit = async () => {
|
|
if (!formData.title.trim()) {
|
|
toast.error('Please enter a title');
|
|
return;
|
|
}
|
|
|
|
await onCreate({
|
|
...formData,
|
|
date: formData.date,
|
|
allDay: formData.allDay,
|
|
});
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<Dialog open onOpenChange={onClose}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Create Neues Ereignis</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="title">Titel</Label>
|
|
<Input
|
|
id="title"
|
|
value={formData.title}
|
|
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
|
placeholder="Ereignistitel"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<Label htmlFor="type">Typ</Label>
|
|
<select
|
|
id="type"
|
|
value={formData.type}
|
|
onChange={(e) => setFormData({ ...formData, type: e.target.value as CalendarEventType })}
|
|
className="w-full px-3 py-2 border rounded-md"
|
|
>
|
|
<option value="exam">Prüfung</option>
|
|
<option value="lesson">Unterricht</option>
|
|
<option value="assignment">Hausaufgabe</option>
|
|
<option value="school_event">School Event</option>
|
|
<option value="custom">Sonstiges</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="date">Datum</Label>
|
|
<Input
|
|
id="date"
|
|
type="date"
|
|
value={format(formData.date, 'yyyy-MM-dd')}
|
|
onChange={(e) => setFormData({ ...formData, date: parseISO(e.target.value) })}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
checked={formData.allDay}
|
|
onChange={(e) => setFormData({ ...formData, allDay: e.target.checked })}
|
|
/>
|
|
All day event
|
|
</Label>
|
|
</div>
|
|
|
|
{!formData.allDay && (
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<Label htmlFor="startTime">Startzeit</Label>
|
|
<Input
|
|
id="startTime"
|
|
type="time"
|
|
value={formData.startTime}
|
|
onChange={(e) => setFormData({ ...formData, startTime: e.target.value })}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="endTime">Endzeit</Label>
|
|
<Input
|
|
id="endTime"
|
|
type="time"
|
|
value={formData.endTime}
|
|
onChange={(e) => setFormData({ ...formData, endTime: e.target.value })}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<Label htmlFor="location">Ort</Label>
|
|
<Input
|
|
id="location"
|
|
value={formData.location}
|
|
onChange={(e) => setFormData({ ...formData, location: e.target.value })}
|
|
placeholder="Ereignisort"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="description">Beschreibung</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
placeholder="Ereignisbeschreibung"
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={onClose}>
|
|
Abbrechen
|
|
</Button>
|
|
<Button onClick={handleSubmit}>Ereignis erstellen</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
export default ProfessionalCalendar;
|