Prev/next week buttons in header on desktop
This commit is contained in:
@@ -23,9 +23,9 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"tailwindcss": "^4",
|
||||
"postcss": "^8.0.0",
|
||||
"autoprefixer": "^10.0.0",
|
||||
"postcss": "^8.0.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useWindowSize } from '@/hooks/useWindowSize';
|
||||
import EventModal from '@/components/EventModal';
|
||||
import eventsData from '@/data/events';
|
||||
import { Event, EventsByDate } from '@/types';
|
||||
import CalendarHeader from '@/components/CalendarHeader';
|
||||
|
||||
const Calendar = () => {
|
||||
const { width } = useWindowSize();
|
||||
@@ -28,10 +29,7 @@ const Calendar = () => {
|
||||
const [direction, setDirection] = useState<'left' | 'right'>('left');
|
||||
const [isEventDragging, setIsEventDragging] = useState(false);
|
||||
|
||||
const [currentDate, setCurrentDate] = useState(() => {
|
||||
const now = new Date();
|
||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
});
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
|
||||
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
||||
const [events, setEvents] = useState<EventsByDate>(eventsData);
|
||||
@@ -98,13 +96,13 @@ const Calendar = () => {
|
||||
return Array.from({ length: 7 }, (_, i) => addDays(start, i));
|
||||
};
|
||||
|
||||
const handlePreviousWeek = useCallback(() => {
|
||||
const handlePrevWeek = () => {
|
||||
setCurrentDate(prev => addDays(prev, -7));
|
||||
}, []);
|
||||
};
|
||||
|
||||
const handleNextWeek = useCallback(() => {
|
||||
const handleNextWeek = () => {
|
||||
setCurrentDate(prev => addDays(prev, 7));
|
||||
}, []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -112,7 +110,7 @@ const Calendar = () => {
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
handlePreviousWeek();
|
||||
handlePrevWeek();
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
handleNextWeek();
|
||||
@@ -121,12 +119,12 @@ const Calendar = () => {
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handlePreviousWeek, handleNextWeek, selectedEvent]);
|
||||
}, [handlePrevWeek, handleNextWeek, selectedEvent]);
|
||||
|
||||
const handleSwipe = (dir: 'left' | 'right') => {
|
||||
if (isMobile) {
|
||||
setDirection(dir);
|
||||
setCurrentDate(prev => dir === 'left' ? addDays(prev, 1) : addDays(prev, -1));
|
||||
setCurrentDate(prev => addDays(prev, dir === 'left' ? 7 : -7));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,9 +176,7 @@ const Calendar = () => {
|
||||
const handleDayChange = useCallback((direction: 'left' | 'right') => {
|
||||
setCurrentDate(prev => {
|
||||
const newDate = addDays(prev, direction === 'left' ? -1 : 1);
|
||||
// Reset dragging state to enable future swipes
|
||||
setIsEventDragging(false);
|
||||
// Smooth transition
|
||||
setOffset(direction === 'left' ? -window.innerWidth : window.innerWidth);
|
||||
setTimeout(() => setOffset(0), 10);
|
||||
return newDate;
|
||||
@@ -229,9 +225,10 @@ const Calendar = () => {
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col">
|
||||
<Header
|
||||
currentDate={currentDate}
|
||||
isMobile={isMobile}
|
||||
<CalendarHeader
|
||||
currentDate={currentDate}
|
||||
onPrevWeek={handlePrevWeek}
|
||||
onNextWeek={handleNextWeek}
|
||||
/>
|
||||
{isMobile && <WeekHeader currentDate={currentDate} />}
|
||||
|
||||
@@ -375,6 +372,9 @@ const DayColumn = ({
|
||||
return cleanup;
|
||||
}, [date]);
|
||||
|
||||
console.log('Date value:', date);
|
||||
console.log('Is valid date:', date instanceof Date && !isNaN(date));
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={columnRef}
|
||||
@@ -505,31 +505,6 @@ const DraggableEvent = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Header = ({
|
||||
currentDate,
|
||||
isMobile
|
||||
}: {
|
||||
currentDate: Date;
|
||||
isMobile: boolean;
|
||||
}) => {
|
||||
const weekRange = useMemo(() => {
|
||||
if (!isMobile) return '';
|
||||
const start = startOfWeek(currentDate, { weekStartsOn: 0 });
|
||||
const end = addDays(start, 6);
|
||||
return `${format(start, 'MMM d')} - ${format(end, 'MMM d')}`;
|
||||
}, [currentDate, isMobile]);
|
||||
|
||||
return (
|
||||
<div className="p-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold text-black">
|
||||
{isMobile ? weekRange : format(currentDate, 'MMMM yyyy')}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WeekHeader = ({ currentDate }: { currentDate: Date }) => {
|
||||
const weekDays = useMemo(() => {
|
||||
const start = startOfWeek(currentDate, { weekStartsOn: 0 }); // Start week on Sunday
|
||||
|
||||
39
src/components/CalendarHeader.tsx
Normal file
39
src/components/CalendarHeader.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { format } from 'date-fns';
|
||||
|
||||
interface CalendarHeaderProps {
|
||||
currentDate: Date;
|
||||
onPrevWeek: () => void;
|
||||
onNextWeek: () => void;
|
||||
}
|
||||
|
||||
function CalendarHeader({ currentDate, onPrevWeek, onNextWeek }: CalendarHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<button
|
||||
onClick={onPrevWeek}
|
||||
className="p-2 rounded-full hover:bg-gray-100"
|
||||
>
|
||||
{/* Left arrow icon */}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<h2 className="text-xl font-semibold">
|
||||
{format(currentDate, 'MMMM yyyy')}
|
||||
</h2>
|
||||
|
||||
<button
|
||||
onClick={onNextWeek}
|
||||
className="p-2 rounded-full hover:bg-gray-100"
|
||||
>
|
||||
{/* Right arrow icon */}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CalendarHeader;
|
||||
69
src/components/DayColumn.tsx
Normal file
69
src/components/DayColumn.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { format, addDays } from 'date-fns';
|
||||
import { DraggableEvent } from './DraggableEvent';
|
||||
|
||||
interface DayColumnProps {
|
||||
date: Date;
|
||||
events: Event[];
|
||||
isMobile: boolean;
|
||||
index: number;
|
||||
onEventClick: (event: Event) => void;
|
||||
onDragStart: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
onDragEnd: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
handleEventMove: (eventId: string, newDate: Date) => void;
|
||||
onDayChange: (dir: 'left' | 'right') => void;
|
||||
isClient: boolean;
|
||||
}
|
||||
|
||||
const DayColumn: React.FC<DayColumnProps> = ({
|
||||
date,
|
||||
events,
|
||||
isMobile,
|
||||
index,
|
||||
onEventClick,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
handleEventMove,
|
||||
onDayChange,
|
||||
isClient
|
||||
}) => {
|
||||
const columnRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const sortedEvents = [...events].sort((a, b) => a.start.getTime() - b.start.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={index}
|
||||
>
|
||||
<div className="font-bold mb-2 text-black text-xl">
|
||||
{format(date, 'EEE, MMM d')}
|
||||
</div>
|
||||
|
||||
<div className="relative z-10">
|
||||
{sortedEvents
|
||||
.filter(event => event && event.id)
|
||||
.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>
|
||||
);
|
||||
};
|
||||
|
||||
export default DayColumn;
|
||||
@@ -2,7 +2,7 @@ export interface Event {
|
||||
id: string;
|
||||
title: string;
|
||||
time: string;
|
||||
date: string;
|
||||
date: Date;
|
||||
description?: string;
|
||||
imageUrl?: string;
|
||||
// add other properties as needed
|
||||
|
||||
Reference in New Issue
Block a user