...
This commit is contained in:
parent
af105583f1
commit
8a98ada8f3
@ -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
|
||||||
|
182
react-shadcn-starter/src/components/eventform.tsx
Normal file
182
react-shadcn-starter/src/components/eventform.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user