Refactor out components
This commit is contained in:
@@ -20,6 +20,8 @@ import eventsData from '@/data/events';
|
|||||||
import { Event, EventsByDate } from '@/types';
|
import { Event, EventsByDate } from '@/types';
|
||||||
import CalendarHeader from '@/components/CalendarHeader';
|
import CalendarHeader from '@/components/CalendarHeader';
|
||||||
import DraggableEvent from './DraggableEvent';
|
import DraggableEvent from './DraggableEvent';
|
||||||
|
import WeekHeader from '@/components/WeekHeader';
|
||||||
|
import DayColumn from '@/components/DayColumn';
|
||||||
|
|
||||||
const Calendar = () => {
|
const Calendar = () => {
|
||||||
const { width } = useWindowSize();
|
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;
|
export default Calendar;
|
||||||
|
|||||||
@@ -1,53 +1,88 @@
|
|||||||
import React, { useRef } from 'react';
|
import { useRef, useEffect, useMemo, useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
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 DraggableEvent from './DraggableEvent';
|
||||||
import { Event } from '../types';
|
import { useWindowSize } from '@/hooks/useWindowSize';
|
||||||
|
import { Event } from '@/types';
|
||||||
|
|
||||||
interface DayColumnProps {
|
interface DayColumnProps {
|
||||||
date: Date;
|
date: Date;
|
||||||
events: Event[];
|
events: Event[];
|
||||||
isMobile: boolean;
|
|
||||||
index: number;
|
|
||||||
onEventClick: (eventData: { event: Event; date: string }) => void;
|
onEventClick: (eventData: { event: Event; date: string }) => void;
|
||||||
onDragStart: () => void;
|
onDragStart: () => void;
|
||||||
onDragEnd: () => void;
|
onDragEnd: () => void;
|
||||||
handleEventMove: (eventId: string, newDate: Date) => void;
|
handleEventMove: (eventId: string, newDate: Date) => void;
|
||||||
onDayChange: (dir: 'left' | 'right') => void;
|
onDayChange: (direction: 'left' | 'right') => void;
|
||||||
isClient: boolean;
|
isClient: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DayColumn: React.FC<DayColumnProps> = ({
|
const DayColumn = ({
|
||||||
date,
|
date,
|
||||||
events,
|
events,
|
||||||
isMobile,
|
onDragStart,
|
||||||
index,
|
|
||||||
onEventClick,
|
|
||||||
onDragStart,
|
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
|
onEventClick,
|
||||||
handleEventMove,
|
handleEventMove,
|
||||||
onDayChange,
|
onDayChange,
|
||||||
isClient
|
isClient
|
||||||
}) => {
|
}: DayColumnProps) => {
|
||||||
const columnRef = useRef<HTMLDivElement>(null);
|
const columnRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
|
||||||
const sortedEvents = [...events].sort((a, b) => {
|
const parseTimeToMinutes = (time: string) => {
|
||||||
const timeA = new Date(a.time).getTime();
|
const [timePart, modifier] = time.split(' ');
|
||||||
const timeB = new Date(b.time).getTime();
|
let [hours, minutes] = timePart.split(':').map(Number);
|
||||||
return timeA - timeB;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleMove = (dir: 'up' | 'down') => {
|
if (modifier === 'PM' && hours !== 12) {
|
||||||
// ... existing code ...
|
hours += 12;
|
||||||
|
}
|
||||||
|
if (modifier === 'AM' && hours === 12) {
|
||||||
|
hours = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={columnRef}
|
ref={columnRef}
|
||||||
className={`flex-1 ${isMobile ? 'min-w-[calc(100vw-32px)]' : ''} bg-gray-50 rounded-lg p-2 relative shadow-md z-10`}
|
className={`flex-1 ${isMobile ? 'min-w-[calc(100vw-32px)]' : ''} bg-gray-50 rounded-lg p-2 relative`}
|
||||||
data-day={index}
|
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')}
|
{format(date, 'EEE, MMM d')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -59,9 +94,9 @@ const DayColumn: React.FC<DayColumnProps> = ({
|
|||||||
key={event.id}
|
key={event.id}
|
||||||
event={event}
|
event={event}
|
||||||
date={format(date, 'yyyy-MM-dd')}
|
date={format(date, 'yyyy-MM-dd')}
|
||||||
onEventClick={(eventData) => onEventClick({ event: eventData.event, date: eventData.date })}
|
onEventClick={onEventClick}
|
||||||
onDragStart={() => onDragStart()}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={() => onDragEnd()}
|
onDragEnd={onDragEnd}
|
||||||
onDayChange={(dir) => {
|
onDayChange={(dir) => {
|
||||||
onDayChange(dir);
|
onDayChange(dir);
|
||||||
const newDate = addDays(date, dir === 'left' ? -1 : 1);
|
const newDate = addDays(date, dir === 'left' ? -1 : 1);
|
||||||
|
|||||||
43
src/components/WeekHeader.tsx
Normal file
43
src/components/WeekHeader.tsx
Normal file
@@ -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;
|
||||||
Reference in New Issue
Block a user