This commit is contained in:
despiegk 2024-10-31 08:00:48 +01:00
parent af105583f1
commit 8a98ada8f3
2 changed files with 186 additions and 161 deletions

View File

@ -1,6 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { addDays, format, startOfWeek, endOfWeek, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, parseISO } from 'date-fns' import { addDays, format, startOfWeek, endOfWeek, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, parseISO } from 'date-fns'
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, MoreHorizontal, Plus, Moon, Sun, Clock, X } from 'lucide-react' import { ChevronLeft, ChevronRight, Plus, Moon, Sun, Clock, X } from 'lucide-react'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -8,8 +8,6 @@ import { v4 as uuidv4 } from 'uuid'
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Calendar } from "@/components/ui/calendar"
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -21,16 +19,8 @@ import {
} from "@/components/ui/dialog" } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { useToast } from "@/components/ui/use-toast"
import { Switch } from "@/components/ui/switch" import { Switch } from "@/components/ui/switch"
import { useToast } from "@/components/ui/use-toast"
import { import {
Event, Event,
categories, categories,
@ -40,6 +30,7 @@ import {
WebDAVConfig WebDAVConfig
} from '@/lib/calendar-data' } from '@/lib/calendar-data'
import { CircleDataManager, Member } from '@/lib/circle-data' import { CircleDataManager, Member } from '@/lib/circle-data'
import { EventForm } from './eventform'
const dataManager = CalendarDataManager.getInstance(); const dataManager = CalendarDataManager.getInstance();
const circleManager = CircleDataManager.getInstance(); const circleManager = CircleDataManager.getInstance();
@ -50,7 +41,7 @@ interface CalendarProps {
circleFile: string; circleFile: string;
} }
function AttendeeSelector({ selectedAttendees, onAttendeeChange }: { export function AttendeeSelector({ selectedAttendees, onAttendeeChange }: {
selectedAttendees: string[]; selectedAttendees: string[];
onAttendeeChange: (attendees: string[]) => void; onAttendeeChange: (attendees: string[]) => void;
}) { }) {
@ -421,154 +412,6 @@ export function OurCalendar({ webdavConfig, calendarFile, circleFile }: Calendar
) )
} }
function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Event) => void, initialData?: Event, isDarkMode: boolean }) {
const [title, setTitle] = React.useState(initialData?.title || '')
const [date, setDate] = React.useState<Date | undefined>(initialData ? parseISO(initialData.date) : undefined)
const [time, setTime] = React.useState(initialData?.time || '10:00')
const [duration, setDuration] = React.useState(initialData?.duration?.toString() || '60')
const [category, setCategory] = React.useState(initialData?.category || '')
const [content, setContent] = React.useState(initialData?.content || '')
const [attendees, setAttendees] = React.useState<string[]>(initialData?.attendees || [])
const [isFullDay, setIsFullDay] = React.useState(initialData?.isFullDay || false)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (title && date && category) {
const color = categories.find(c => c.name === category)?.color || 'bg-gray-500 dark:bg-gray-700'
onSubmit({
id: initialData?.id || uuidv4(),
title,
date: date.toISOString().split('T')[0],
time: isFullDay ? '00:00' : time,
duration: parseInt(duration),
category,
color,
content,
attendees,
isFullDay
})
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="title" className="dark:text-white">Title</Label>
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
/>
</div>
<div>
<Label htmlFor="date" className="dark:text-white">Date</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground",
"dark:bg-gray-700 dark:text-white dark:border-gray-600"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
initialFocus
className={isDarkMode ? "dark" : ""}
/>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center space-x-2">
<Switch
id="isFullDay"
checked={isFullDay}
onCheckedChange={setIsFullDay}
className="dark:bg-gray-600"
/>
<Label htmlFor="isFullDay" className="dark:text-white">Full Day Event</Label>
</div>
{!isFullDay && (
<div>
<Label htmlFor="time" className="dark:text-white">Time</Label>
<Select value={time} onValueChange={setTime}>
<SelectTrigger className="dark:bg-gray-700 dark:text-white dark:border-gray-600">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<SelectContent className="dark:bg-gray-800 dark:text-white max-h-[200px]">
{timeOptions.map((timeOption) => (
<SelectItem key={timeOption} value={timeOption}>
{timeOption}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{!isFullDay && (
<div>
<Label htmlFor="duration" className="dark:text-white">Duration (minutes)</Label>
<Select value={duration} onValueChange={setDuration}>
<SelectTrigger className="dark:bg-gray-700 dark:text-white dark:border-gray-600">
<SelectValue placeholder="Select duration" />
</SelectTrigger>
<SelectContent className="dark:bg-gray-800 dark:text-white">
{[15, 30, 45, 60, 90, 120, 180, 240].map((mins) => (
<SelectItem key={mins} value={mins.toString()}>
{dataManager.formatDuration(mins)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div>
<Label htmlFor="category" className="dark:text-white">Category</Label>
<Select value={category} onValueChange={setCategory}>
<SelectTrigger className="dark:bg-gray-700 dark:text-white dark:border-gray-600">
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent className="dark:bg-gray-800 dark:text-white">
{categories.map((cat) => (
<SelectItem key={cat.name} value={cat.name}>
{cat.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="attendees" className="dark:text-white">Attendees</Label>
<AttendeeSelector
selectedAttendees={attendees}
onAttendeeChange={setAttendees}
/>
</div>
<div>
<Label htmlFor="content" className="dark:text-white">Content (Markdown)</Label>
<Textarea
id="content"
value={content}
onChange={(e) => setContent(e.target.value)}
rows={5}
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
/>
</div>
<Button type="submit" className="dark:bg-blue-600 dark:text-white">{initialData ? 'Update' : 'Add'} Event</Button>
</form>
)
}
function EventDetailsDialog({ event, onClose, onEdit, onDelete, isDarkMode }: { function EventDetailsDialog({ event, onClose, onEdit, onDelete, isDarkMode }: {
event: Event event: Event
onClose: () => void onClose: () => void

View File

@ -0,0 +1,182 @@
import * as React from 'react'
import { format, parseISO } from 'date-fns'
import { CalendarIcon } from 'lucide-react'
import { v4 as uuidv4 } from 'uuid'
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Switch } from "@/components/ui/switch"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Event, categories, timeOptions, CalendarDataManager } from '@/lib/calendar-data'
import { AttendeeSelector } from './calendar'
const dataManager = CalendarDataManager.getInstance();
interface EventFormProps {
onSubmit: (event: Event) => void;
initialData?: Event;
isDarkMode: boolean;
}
export function EventForm({ onSubmit, initialData, isDarkMode }: EventFormProps) {
const [title, setTitle] = React.useState(initialData?.title || '')
const [date, setDate] = React.useState<Date>(initialData ? parseISO(initialData.date) : new Date())
const [time, setTime] = React.useState(initialData?.time || '10:00')
const [duration, setDuration] = React.useState(initialData?.duration?.toString() || '60')
const [category, setCategory] = React.useState(initialData?.category || '')
const [content, setContent] = React.useState(initialData?.content || '')
const [attendees, setAttendees] = React.useState<string[]>(initialData?.attendees || [])
const [isFullDay, setIsFullDay] = React.useState(initialData?.isFullDay || false)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (title && date && category) {
const color = categories.find(c => c.name === category)?.color || 'bg-gray-500 dark:bg-gray-700'
onSubmit({
id: initialData?.id || uuidv4(),
title,
date: date.toISOString().split('T')[0],
time: isFullDay ? '00:00' : time,
duration: parseInt(duration),
category,
color,
content,
attendees,
isFullDay
})
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="title" className="dark:text-white">Title</Label>
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
/>
</div>
<div>
<Label htmlFor="date" className="dark:text-white">Date</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground",
"dark:bg-gray-700 dark:text-white dark:border-gray-600"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={(newDate) => newDate && setDate(newDate)}
initialFocus
className={isDarkMode ? "dark" : ""}
/>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center space-x-2">
<Switch
id="isFullDay"
checked={isFullDay}
onCheckedChange={setIsFullDay}
className="dark:bg-gray-600"
/>
<Label htmlFor="isFullDay" className="dark:text-white">Full Day Event</Label>
</div>
{!isFullDay && (
<div>
<Label htmlFor="time" className="dark:text-white">Time</Label>
<Select value={time} onValueChange={setTime}>
<SelectTrigger className="dark:bg-gray-700 dark:text-white dark:border-gray-600">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<SelectContent className="dark:bg-gray-800 dark:text-white max-h-[200px]">
{timeOptions.map((timeOption) => (
<SelectItem key={timeOption} value={timeOption}>
{timeOption}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{!isFullDay && (
<div>
<Label htmlFor="duration" className="dark:text-white">Duration (minutes)</Label>
<Select value={duration} onValueChange={setDuration}>
<SelectTrigger className="dark:bg-gray-700 dark:text-white dark:border-gray-600">
<SelectValue placeholder="Select duration" />
</SelectTrigger>
<SelectContent className="dark:bg-gray-800 dark:text-white">
{[15, 30, 45, 60, 90, 120, 180, 240].map((mins) => (
<SelectItem key={mins} value={mins.toString()}>
{dataManager.formatDuration(mins)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div>
<Label htmlFor="category" className="dark:text-white">Category</Label>
<Select value={category} onValueChange={setCategory}>
<SelectTrigger className="dark:bg-gray-700 dark:text-white dark:border-gray-600">
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent className="dark:bg-gray-800 dark:text-white">
{categories.map((cat) => (
<SelectItem key={cat.name} value={cat.name}>
{cat.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="attendees" className="dark:text-white">Attendees</Label>
<AttendeeSelector
selectedAttendees={attendees}
onAttendeeChange={setAttendees}
/>
</div>
<div>
<Label htmlFor="content" className="dark:text-white">Content (Markdown)</Label>
<Textarea
id="content"
value={content}
onChange={(e) => setContent(e.target.value)}
rows={5}
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
/>
</div>
<Button type="submit" className="dark:bg-blue-600 dark:text-white">{initialData ? 'Update' : 'Add'} Event</Button>
</form>
)
}