Prev/next week buttons in header on desktop
This commit is contained in:
@@ -23,9 +23,9 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"tailwindcss": "^4",
|
|
||||||
"postcss": "^8.0.0",
|
|
||||||
"autoprefixer": "^10.0.0",
|
"autoprefixer": "^10.0.0",
|
||||||
|
"postcss": "^8.0.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { useWindowSize } from '@/hooks/useWindowSize';
|
|||||||
import EventModal from '@/components/EventModal';
|
import EventModal from '@/components/EventModal';
|
||||||
import eventsData from '@/data/events';
|
import eventsData from '@/data/events';
|
||||||
import { Event, EventsByDate } from '@/types';
|
import { Event, EventsByDate } from '@/types';
|
||||||
|
import CalendarHeader from '@/components/CalendarHeader';
|
||||||
|
|
||||||
const Calendar = () => {
|
const Calendar = () => {
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
@@ -28,10 +29,7 @@ const Calendar = () => {
|
|||||||
const [direction, setDirection] = useState<'left' | 'right'>('left');
|
const [direction, setDirection] = useState<'left' | 'right'>('left');
|
||||||
const [isEventDragging, setIsEventDragging] = useState(false);
|
const [isEventDragging, setIsEventDragging] = useState(false);
|
||||||
|
|
||||||
const [currentDate, setCurrentDate] = useState(() => {
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
const now = new Date();
|
|
||||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
||||||
});
|
|
||||||
|
|
||||||
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
||||||
const [events, setEvents] = useState<EventsByDate>(eventsData);
|
const [events, setEvents] = useState<EventsByDate>(eventsData);
|
||||||
@@ -98,13 +96,13 @@ const Calendar = () => {
|
|||||||
return Array.from({ length: 7 }, (_, i) => addDays(start, i));
|
return Array.from({ length: 7 }, (_, i) => addDays(start, i));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePreviousWeek = useCallback(() => {
|
const handlePrevWeek = () => {
|
||||||
setCurrentDate(prev => addDays(prev, -7));
|
setCurrentDate(prev => addDays(prev, -7));
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const handleNextWeek = useCallback(() => {
|
const handleNextWeek = () => {
|
||||||
setCurrentDate(prev => addDays(prev, 7));
|
setCurrentDate(prev => addDays(prev, 7));
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -112,7 +110,7 @@ const Calendar = () => {
|
|||||||
|
|
||||||
if (event.key === 'ArrowLeft') {
|
if (event.key === 'ArrowLeft') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handlePreviousWeek();
|
handlePrevWeek();
|
||||||
} else if (event.key === 'ArrowRight') {
|
} else if (event.key === 'ArrowRight') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleNextWeek();
|
handleNextWeek();
|
||||||
@@ -121,12 +119,12 @@ const Calendar = () => {
|
|||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [handlePreviousWeek, handleNextWeek, selectedEvent]);
|
}, [handlePrevWeek, handleNextWeek, selectedEvent]);
|
||||||
|
|
||||||
const handleSwipe = (dir: 'left' | 'right') => {
|
const handleSwipe = (dir: 'left' | 'right') => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setDirection(dir);
|
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') => {
|
const handleDayChange = useCallback((direction: 'left' | 'right') => {
|
||||||
setCurrentDate(prev => {
|
setCurrentDate(prev => {
|
||||||
const newDate = addDays(prev, direction === 'left' ? -1 : 1);
|
const newDate = addDays(prev, direction === 'left' ? -1 : 1);
|
||||||
// Reset dragging state to enable future swipes
|
|
||||||
setIsEventDragging(false);
|
setIsEventDragging(false);
|
||||||
// Smooth transition
|
|
||||||
setOffset(direction === 'left' ? -window.innerWidth : window.innerWidth);
|
setOffset(direction === 'left' ? -window.innerWidth : window.innerWidth);
|
||||||
setTimeout(() => setOffset(0), 10);
|
setTimeout(() => setOffset(0), 10);
|
||||||
return newDate;
|
return newDate;
|
||||||
@@ -229,9 +225,10 @@ const Calendar = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex flex-col">
|
<div className="h-screen flex flex-col">
|
||||||
<Header
|
<CalendarHeader
|
||||||
currentDate={currentDate}
|
currentDate={currentDate}
|
||||||
isMobile={isMobile}
|
onPrevWeek={handlePrevWeek}
|
||||||
|
onNextWeek={handleNextWeek}
|
||||||
/>
|
/>
|
||||||
{isMobile && <WeekHeader currentDate={currentDate} />}
|
{isMobile && <WeekHeader currentDate={currentDate} />}
|
||||||
|
|
||||||
@@ -375,6 +372,9 @@ const DayColumn = ({
|
|||||||
return cleanup;
|
return cleanup;
|
||||||
}, [date]);
|
}, [date]);
|
||||||
|
|
||||||
|
console.log('Date value:', date);
|
||||||
|
console.log('Is valid date:', date instanceof Date && !isNaN(date));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={columnRef}
|
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 WeekHeader = ({ currentDate }: { currentDate: Date }) => {
|
||||||
const weekDays = useMemo(() => {
|
const weekDays = useMemo(() => {
|
||||||
const start = startOfWeek(currentDate, { weekStartsOn: 0 }); // Start week on Sunday
|
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;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
time: string;
|
time: string;
|
||||||
date: string;
|
date: Date;
|
||||||
description?: string;
|
description?: string;
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
// add other properties as needed
|
// add other properties as needed
|
||||||
|
|||||||
Reference in New Issue
Block a user