...
This commit is contained in:
parent
cf28f78529
commit
af105583f1
@ -16,6 +16,7 @@ function App() {
|
||||
<OurCalendar
|
||||
webdavConfig={webdavConfig}
|
||||
calendarFile="/calendars/kristof.json"
|
||||
circleFile="/calendars/cirlce1.json"
|
||||
/>
|
||||
{/* <WebdavFileManager /> */}
|
||||
<Toaster />
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
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 ReactMarkdown from 'react-markdown'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
@ -39,15 +39,111 @@ import {
|
||||
timeOptions,
|
||||
WebDAVConfig
|
||||
} from '@/lib/calendar-data'
|
||||
import { CircleDataManager, Member } from '@/lib/circle-data'
|
||||
|
||||
const dataManager = CalendarDataManager.getInstance();
|
||||
const circleManager = CircleDataManager.getInstance();
|
||||
|
||||
interface CalendarProps {
|
||||
webdavConfig: WebDAVConfig;
|
||||
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 [events, setEvents] = React.useState<Event[]>([])
|
||||
const [selectedEvent, setSelectedEvent] = React.useState<Event | null>(null)
|
||||
@ -60,7 +156,8 @@ export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) {
|
||||
|
||||
React.useEffect(() => {
|
||||
dataManager.setConfig(webdavConfig, calendarFile);
|
||||
}, [webdavConfig, calendarFile]);
|
||||
circleManager.setConfig(webdavConfig, circleFile);
|
||||
}, [webdavConfig, calendarFile, circleFile]);
|
||||
|
||||
const startDate = startOfWeek(startOfMonth(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}`)
|
||||
} else {
|
||||
const data = await response.json()
|
||||
// Ensure all events have the new fields
|
||||
const updatedEvents = data.events.map((event: Event) => ({
|
||||
...event,
|
||||
attendees: event.attendees || [],
|
||||
@ -112,11 +208,10 @@ export function OurCalendar({ webdavConfig, calendarFile }: CalendarProps) {
|
||||
}
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
if (selectedEvent) return; // Skip update check while editing
|
||||
if (selectedEvent) return;
|
||||
|
||||
try {
|
||||
const fetchedEvents = await dataManager.fetchEvents();
|
||||
// Ensure all events have the new fields
|
||||
const updatedEvents = fetchedEvents.map(event => ({
|
||||
...event,
|
||||
attendees: event.attendees || [],
|
||||
@ -333,7 +428,7 @@ function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Ev
|
||||
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(initialData?.attendees?.join('\n') || '')
|
||||
const [attendees, setAttendees] = React.useState<string[]>(initialData?.attendees || [])
|
||||
const [isFullDay, setIsFullDay] = React.useState(initialData?.isFullDay || false)
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@ -349,7 +444,7 @@ function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Ev
|
||||
category,
|
||||
color,
|
||||
content,
|
||||
attendees: attendees.split('\n').filter(email => email.trim() !== ''),
|
||||
attendees,
|
||||
isFullDay
|
||||
})
|
||||
}
|
||||
@ -453,14 +548,10 @@ function EventForm({ onSubmit, initialData, isDarkMode }: { onSubmit: (event: Ev
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="attendees" className="dark:text-white">Attendees (one email per line)</Label>
|
||||
<Textarea
|
||||
id="attendees"
|
||||
value={attendees}
|
||||
onChange={(e) => setAttendees(e.target.value)}
|
||||
rows={3}
|
||||
className="dark:bg-gray-700 dark:text-white dark:border-gray-600"
|
||||
placeholder="john@example.com jane@example.com"
|
||||
<Label htmlFor="attendees" className="dark:text-white">Attendees</Label>
|
||||
<AttendeeSelector
|
||||
selectedAttendees={attendees}
|
||||
onAttendeeChange={setAttendees}
|
||||
/>
|
||||
</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>
|
||||
{(event.attendees || []).length > 0 && (
|
||||
{event.attendees.length > 0 && (
|
||||
<div>
|
||||
<strong>Attendees:</strong>
|
||||
<ul className="list-disc list-inside">
|
||||
{(event.attendees || []).map((email, index) => (
|
||||
{event.attendees.map((email, index) => (
|
||||
<li key={index}>{email}</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -37,13 +37,6 @@ export const defaultMembers: Member[] = [
|
||||
email: "mike.w@example.com",
|
||||
role: "stakeholder",
|
||||
description: "Key stakeholder representing finance department"
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "Lisa Chen",
|
||||
email: "lisa.chen@example.com",
|
||||
role: "contributor",
|
||||
description: "Technical contributor and documentation specialist"
|
||||
}
|
||||
];
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user