This commit is contained in:
despiegk 2024-10-31 07:55:06 +01:00
parent cf28f78529
commit af105583f1
3 changed files with 111 additions and 26 deletions

View File

@ -16,6 +16,7 @@ function App() {
<OurCalendar <OurCalendar
webdavConfig={webdavConfig} webdavConfig={webdavConfig}
calendarFile="/calendars/kristof.json" calendarFile="/calendars/kristof.json"
circleFile="/calendars/cirlce1.json"
/> />
{/* <WebdavFileManager /> */} {/* <WebdavFileManager /> */}
<Toaster /> <Toaster />

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 } from 'lucide-react' import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, MoreHorizontal, 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'
@ -39,15 +39,111 @@ import {
timeOptions, timeOptions,
WebDAVConfig WebDAVConfig
} from '@/lib/calendar-data' } from '@/lib/calendar-data'
import { CircleDataManager, Member } from '@/lib/circle-data'
const dataManager = CalendarDataManager.getInstance(); const dataManager = CalendarDataManager.getInstance();
const circleManager = CircleDataManager.getInstance();
interface CalendarProps { interface CalendarProps {
webdavConfig: WebDAVConfig; webdavConfig: WebDAVConfig;
calendarFile: string; calendarFile: string;
circleFile: string;
} }
export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) { function AttendeeSelector({ selectedAttendees, onAttendeeChange }: {
selectedAttendees: string[];
onAttendeeChange: (attendees: string[]) => void;
}) {
const [searchTerm, setSearchTerm] = React.useState('');
const [members, setMembers] = React.useState<Member[]>([]);
const [suggestions, setSuggestions] = React.useState<Member[]>([]);
React.useEffect(() => {
const fetchMembers = async () => {
try {
const fetchedMembers = await circleManager.fetchMembers();
setMembers(fetchedMembers);
} catch (error) {
console.error('Error fetching members:', error);
}
};
fetchMembers();
}, []);
React.useEffect(() => {
if (searchTerm.trim() === '') {
setSuggestions([]);
return;
}
const filtered = members.filter(member =>
member.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!selectedAttendees.includes(member.email)
);
setSuggestions(filtered);
}, [searchTerm, members, selectedAttendees]);
const handleAddAttendee = (member: Member) => {
onAttendeeChange([...selectedAttendees, member.email]);
setSearchTerm('');
setSuggestions([]);
};
const handleRemoveAttendee = (email: string) => {
onAttendeeChange(selectedAttendees.filter(e => e !== email));
};
const getMemberName = (email: string) => {
const member = members.find(m => m.email === email);
return member ? member.name : email;
};
return (
<div className="space-y-2">
<div className="flex flex-wrap gap-2 mb-2">
{selectedAttendees.map(email => (
<div
key={email}
className="flex items-center gap-1 bg-blue-100 dark:bg-blue-800 px-2 py-1 rounded"
>
<span>{getMemberName(email)}</span>
<button
onClick={() => handleRemoveAttendee(email)}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
<X className="h-3 w-3" />
</button>
</div>
))}
</div>
<div className="relative">
<Input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Type to search members..."
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
/>
{suggestions.length > 0 && (
<div className="absolute z-10 w-full mt-1 bg-white dark:bg-gray-800 border dark:border-gray-600 rounded-md shadow-lg">
{suggestions.map(member => (
<div
key={member.id}
className="px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer"
onClick={() => handleAddAttendee(member)}
>
<div>{member.name}</div>
<div className="text-sm text-gray-500 dark:text-gray-400">{member.email}</div>
</div>
))}
</div>
)}
</div>
</div>
);
}
export function OurCalendar({ webdavConfig, calendarFile, circleFile }: CalendarProps) {
const [currentDate, setCurrentDate] = React.useState(new Date()) const [currentDate, setCurrentDate] = React.useState(new Date())
const [events, setEvents] = React.useState<Event[]>([]) const [events, setEvents] = React.useState<Event[]>([])
const [selectedEvent, setSelectedEvent] = React.useState<Event | null>(null) const [selectedEvent, setSelectedEvent] = React.useState<Event | null>(null)
@ -60,7 +156,8 @@ export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) {
React.useEffect(() => { React.useEffect(() => {
dataManager.setConfig(webdavConfig, calendarFile); dataManager.setConfig(webdavConfig, calendarFile);
}, [webdavConfig, calendarFile]); circleManager.setConfig(webdavConfig, circleFile);
}, [webdavConfig, calendarFile, circleFile]);
const startDate = startOfWeek(startOfMonth(currentDate)) const startDate = startOfWeek(startOfMonth(currentDate))
const endDate = endOfWeek(endOfMonth(currentDate)) const endDate = endOfWeek(endOfMonth(currentDate))
@ -90,7 +187,6 @@ export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) {
throw new Error(`Failed to fetch calendar data: ${response.status} ${response.statusText}`) throw new Error(`Failed to fetch calendar data: ${response.status} ${response.statusText}`)
} else { } else {
const data = await response.json() const data = await response.json()
// Ensure all events have the new fields
const updatedEvents = data.events.map((event: Event) => ({ const updatedEvents = data.events.map((event: Event) => ({
...event, ...event,
attendees: event.attendees || [], attendees: event.attendees || [],
@ -112,11 +208,10 @@ export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) {
} }
const checkForUpdates = async () => { const checkForUpdates = async () => {
if (selectedEvent) return; // Skip update check while editing if (selectedEvent) return;
try { try {
const fetchedEvents = await dataManager.fetchEvents(); const fetchedEvents = await dataManager.fetchEvents();
// Ensure all events have the new fields
const updatedEvents = fetchedEvents.map(event => ({ const updatedEvents = fetchedEvents.map(event => ({
...event, ...event,
attendees: event.attendees || [], attendees: event.attendees || [],
@ -271,7 +366,7 @@ export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) {
{...provided.droppableProps} {...provided.droppableProps}
className={cn( className={cn(
"border rounded-lg p-2 min-h-[100px] dark:border-gray-600", "border rounded-lg p-2 min-h-[100px] dark:border-gray-600",
!isSameMonth(day, currentDate) && "bg-gray-100 dark:bg-gray-700", !isSameMonth(day, currentDate) && "bg-gray-100 dark:bg-gray-700",
isSameMonth(day, currentDate) && "dark:bg-gray-800" isSameMonth(day, currentDate) && "dark:bg-gray-800"
)} )}
> >
@ -333,7 +428,7 @@ function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Ev
const [duration, setDuration] = React.useState(initialData?.duration?.toString() || '60') const [duration, setDuration] = React.useState(initialData?.duration?.toString() || '60')
const [category, setCategory] = React.useState(initialData?.category || '') const [category, setCategory] = React.useState(initialData?.category || '')
const [content, setContent] = React.useState(initialData?.content || '') const [content, setContent] = React.useState(initialData?.content || '')
const [attendees, setAttendees] = React.useState(initialData?.attendees?.join('\n') || '') const [attendees, setAttendees] = React.useState<string[]>(initialData?.attendees || [])
const [isFullDay, setIsFullDay] = React.useState(initialData?.isFullDay || false) const [isFullDay, setIsFullDay] = React.useState(initialData?.isFullDay || false)
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
@ -349,7 +444,7 @@ function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Ev
category, category,
color, color,
content, content,
attendees: attendees.split('\n').filter(email => email.trim() !== ''), attendees,
isFullDay isFullDay
}) })
} }
@ -453,14 +548,10 @@ function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Ev
</Select> </Select>
</div> </div>
<div> <div>
<Label htmlFor="attendees" className="dark:text-white">Attendees (one email per line)</Label> <Label htmlFor="attendees" className="dark:text-white">Attendees</Label>
<Textarea <AttendeeSelector
id="attendees" selectedAttendees={attendees}
value={attendees} onAttendeeChange={setAttendees}
onChange={(e) => setAttendees(e.target.value)}
rows={3}
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
placeholder="john@example.com&#10;jane@example.com"
/> />
</div> </div>
<div> <div>
@ -509,11 +600,11 @@ function EventDetailsDialog({ event, onClose, onEdit, onDelete, isDarkMode }: {
</> </>
)} )}
<p><strong>Category:</strong> <span className={`px-2 py-1 rounded ${event.color} text-white`}>{event.category}</span></p> <p><strong>Category:</strong> <span className={`px-2 py-1 rounded ${event.color} text-white`}>{event.category}</span></p>
{(event.attendees || []).length > 0 && ( {event.attendees.length > 0 && (
<div> <div>
<strong>Attendees:</strong> <strong>Attendees:</strong>
<ul className="list-disc list-inside"> <ul className="list-disc list-inside">
{(event.attendees || []).map((email, index) => ( {event.attendees.map((email, index) => (
<li key={index}>{email}</li> <li key={index}>{email}</li>
))} ))}
</ul> </ul>

View File

@ -37,13 +37,6 @@ export const defaultMembers: Member[] = [
email: "mike.w@example.com", email: "mike.w@example.com",
role: "stakeholder", role: "stakeholder",
description: "Key stakeholder representing finance department" description: "Key stakeholder representing finance department"
},
{
id: "4",
name: "Lisa Chen",
email: "lisa.chen@example.com",
role: "contributor",
description: "Technical contributor and documentation specialist"
} }
]; ];