Browse Source

Refactor out components

main
Kevin Mok 4 days ago
parent
commit
b26a707692
  1. 147
      src/components/Calendar.tsx
  2. 89
      src/components/DayColumn.tsx
  3. 43
      src/components/WeekHeader.tsx

147
src/components/Calendar.tsx

@ -20,6 +20,8 @@ import eventsData from '@/data/events';
import { Event, EventsByDate } from '@/types';
import CalendarHeader from '@/components/CalendarHeader';
import DraggableEvent from './DraggableEvent';
import WeekHeader from '@/components/WeekHeader';
import DayColumn from '@/components/DayColumn';
const Calendar = () => {
const { width } = useWindowSize();
@ -296,149 +298,4 @@ const Calendar = () => {
);
};
interface DayColumnProps {
date: Date;
events: Event[];
isMobile: boolean;
index: number;
onEventClick: (eventData: { event: Event; date: string }) => void;
onDragStart: () => void;
onDragEnd: () => void;
handleEventMove: (eventId: string, newDate: Date) => void;
onDayChange: (direction: 'left' | 'right') => void;
isClient: boolean;
}
const DayColumn = ({
date,
events,
onDragStart,
onDragEnd,
onEventClick,
handleEventMove,
onDayChange,
isClient
}: DayColumnProps) => {
const columnRef = useRef<HTMLDivElement>(null);
const { width } = useWindowSize();
const [isMobile, setIsMobile] = useState(false);
const parseTimeToMinutes = (time: string) => {
const [timePart, modifier] = time.split(' ');
let [hours, minutes] = timePart.split(':').map(Number);
if (modifier === 'PM' && hours !== 12) {
hours += 12; // Convert PM times to 24-hour format
}
if (modifier === 'AM' && hours === 12) {
hours = 0; // Handle midnight (12:00 AM)
}
return hours * 60 + minutes;
};
const sortedEvents = useMemo(() => {
const seenIds = new Set<string>();
return [...events]
.sort((a, b) => parseTimeToMinutes(a.time) - parseTimeToMinutes(b.time))
.filter(event => {
if (seenIds.has(event.id)) {
//console.warn(`Duplicate event ID detected: ${event.id}`);
return false;
}
seenIds.add(event.id);
return true;
});
}, [events]);
useEffect(() => {
setIsMobile(width < 768); // Update isMobile after hydration
}, [width]);
const dayOffset = differenceInDays(date, startOfWeek(date));
const isActive = isMobile ? true : true;
useEffect(() => {
const element = columnRef.current;
if (!element) return;
const cleanup = dropTargetForElements({
element,
getData: () => ({ date: format(date, 'yyyy-MM-dd') }),
});
return cleanup;
}, [date]);
console.log('Date value:', date);
console.log('Is valid date:', date instanceof Date && !isNaN(date.getTime()));
return (
<motion.div
ref={columnRef}
className={`flex-1 ${isMobile ? 'min-w-[calc(100vw-32px)]' : ''} bg-gray-50 rounded-lg p-2 relative`}
data-day={dayOffset}
>
<div className="font-bold mb-2 text-purple-500 text-med">
{format(date, 'EEE, MMM d')}
</div>
<div className="relative z-10">
{sortedEvents
.filter(event => event && event.id) // Filter out invalid events
.map((event, index) => (
<DraggableEvent
key={event.id}
event={event}
date={format(date, 'yyyy-MM-dd')}
onEventClick={onEventClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDayChange={(dir) => {
onDayChange(dir);
const newDate = addDays(date, dir === 'left' ? -1 : 1);
handleEventMove(event.id, newDate);
}}
isClient={isClient}
/>
))}
</div>
</motion.div>
);
};
const WeekHeader = ({ currentDate }: { currentDate: Date }) => {
const weekDays = useMemo(() => {
const start = startOfWeek(currentDate, { weekStartsOn: 0 }); // Start week on Sunday
return Array.from({ length: 7 }, (_, i) => addDays(start, i));
}, [currentDate]);
return (
<div className="flex justify-between px-2 py-3 bg-gradient-to-r from-blue-500 to-purple-500">
{weekDays.map((day, index) => (
<motion.div
key={day.toISOString()}
className="flex flex-col items-center flex-1"
initial={false}
animate={{ backgroundColor: 'transparent' }}
transition={{ duration: 0.3 }}
>
<div className={`w-full px-2 py-2 rounded-md flex flex-col items-center gap-1 ${
isSameDay(day, currentDate)
? 'bg-gradient-to-br from-indigo-600 to-violet-600 text-white'
: 'text-purple-100'
}`}>
<div className="text-sm font-medium">
{format(day, 'EEE')}
</div>
<div className="w-8 h-8 rounded-full flex items-center justify-center">
{format(day, 'd')}
</div>
</div>
</motion.div>
))}
</div>
);
};
export default Calendar;

89
src/components/DayColumn.tsx

@ -1,53 +1,88 @@
import React, { useRef } from 'react';
import { useRef, useEffect, useMemo, useState } from 'react';
import { motion } from 'framer-motion';
import { format, addDays } from 'date-fns';
import { format, differenceInDays, startOfWeek, addDays } from 'date-fns';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import DraggableEvent from './DraggableEvent';
import { Event } from '../types';
import { useWindowSize } from '@/hooks/useWindowSize';
import { Event } from '@/types';
interface DayColumnProps {
date: Date;
events: Event[];
isMobile: boolean;
index: number;
onEventClick: (eventData: { event: Event; date: string }) => void;
onDragStart: () => void;
onDragEnd: () => void;
handleEventMove: (eventId: string, newDate: Date) => void;
onDayChange: (dir: 'left' | 'right') => void;
onDayChange: (direction: 'left' | 'right') => void;
isClient: boolean;
}
const DayColumn: React.FC<DayColumnProps> = ({
date,
events,
isMobile,
index,
onEventClick,
onDragStart,
const DayColumn = ({
date,
events,
onDragStart,
onDragEnd,
onEventClick,
handleEventMove,
onDayChange,
isClient
}) => {
}: DayColumnProps) => {
const columnRef = useRef<HTMLDivElement>(null);
const { width } = useWindowSize();
const [isMobile, setIsMobile] = useState(false);
const parseTimeToMinutes = (time: string) => {
const [timePart, modifier] = time.split(' ');
let [hours, minutes] = timePart.split(':').map(Number);
const sortedEvents = [...events].sort((a, b) => {
const timeA = new Date(a.time).getTime();
const timeB = new Date(b.time).getTime();
return timeA - timeB;
});
if (modifier === 'PM' && hours !== 12) {
hours += 12;
}
if (modifier === 'AM' && hours === 12) {
hours = 0;
}
const handleMove = (dir: 'up' | 'down') => {
// ... existing code ...
return hours * 60 + minutes;
};
const sortedEvents = useMemo(() => {
const seenIds = new Set<string>();
return [...events]
.sort((a, b) => parseTimeToMinutes(a.time) - parseTimeToMinutes(b.time))
.filter(event => {
if (seenIds.has(event.id)) {
return false;
}
seenIds.add(event.id);
return true;
});
}, [events]);
useEffect(() => {
setIsMobile(width < 768);
}, [width]);
const dayOffset = differenceInDays(date, startOfWeek(date));
useEffect(() => {
const element = columnRef.current;
if (!element) return;
const cleanup = dropTargetForElements({
element,
getData: () => ({ date: format(date, 'yyyy-MM-dd') }),
});
return cleanup;
}, [date]);
return (
<motion.div
ref={columnRef}
className={`flex-1 ${isMobile ? 'min-w-[calc(100vw-32px)]' : ''} bg-gray-50 rounded-lg p-2 relative shadow-md z-10`}
data-day={index}
className={`flex-1 ${isMobile ? 'min-w-[calc(100vw-32px)]' : ''} bg-gray-50 rounded-lg p-2 relative`}
data-day={dayOffset}
>
<div className="font-bold mb-2 text-purple-600 text-sm font-sans">
<div className="font-bold mb-2 text-purple-500 text-med">
{format(date, 'EEE, MMM d')}
</div>
@ -59,9 +94,9 @@ const DayColumn: React.FC<DayColumnProps> = ({
key={event.id}
event={event}
date={format(date, 'yyyy-MM-dd')}
onEventClick={(eventData) => onEventClick({ event: eventData.event, date: eventData.date })}
onDragStart={() => onDragStart()}
onDragEnd={() => onDragEnd()}
onEventClick={onEventClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDayChange={(dir) => {
onDayChange(dir);
const newDate = addDays(date, dir === 'left' ? -1 : 1);

43
src/components/WeekHeader.tsx

@ -0,0 +1,43 @@
import { useMemo } from 'react';
import { motion } from 'framer-motion';
import { format, addDays, startOfWeek, isSameDay } from 'date-fns';
interface WeekHeaderProps {
currentDate: Date;
}
const WeekHeader = ({ currentDate }: WeekHeaderProps) => {
const weekDays = useMemo(() => {
const start = startOfWeek(currentDate, { weekStartsOn: 0 });
return Array.from({ length: 7 }, (_, i) => addDays(start, i));
}, [currentDate]);
return (
<div className="flex justify-between px-2 py-3 bg-gradient-to-r from-blue-500 to-purple-500">
{weekDays.map((day, index) => (
<motion.div
key={day.toISOString()}
className="flex flex-col items-center flex-1"
initial={false}
animate={{ backgroundColor: 'transparent' }}
transition={{ duration: 0.3 }}
>
<div className={`w-full px-2 py-2 rounded-md flex flex-col items-center gap-1 ${
isSameDay(day, currentDate)
? 'bg-gradient-to-br from-indigo-600 to-violet-600 text-white'
: 'text-purple-100'
}`}>
<div className="text-sm font-medium">
{format(day, 'EEE')}
</div>
<div className="w-8 h-8 rounded-full flex items-center justify-center">
{format(day, 'd')}
</div>
</div>
</motion.div>
))}
</div>
);
};
export default WeekHeader;
Loading…
Cancel
Save