feat: Enhance calendar component with glassmorphism design and modular architecture
- Refactored Calendar.tsx into modular component structure - Added glassmorphism theming with CSS custom properties - Implemented reusable calendar subcomponents: - CalendarGrid: Month view with improved day/event display - CalendarHeader: Navigation and view controls - EventList: List view for events - TrendingEvents: Location-based trending events - UpcomingEvents: Quick upcoming events preview - Enhanced responsive design for mobile devices - Added Playwright testing framework for automated testing - Updated Docker development commands in CLAUDE.md - Improved accessibility and user experience 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
253
src/components/calendar/utils.ts
Normal file
253
src/components/calendar/utils.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
import { Event, CalendarDay } from './types';
|
||||
|
||||
/**
|
||||
* Get the month name for display
|
||||
*/
|
||||
export const getMonthName = (date: Date, short: boolean = false): string => {
|
||||
const monthNames = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
|
||||
if (short) {
|
||||
return monthNames[date.getMonth()].slice(0, 3);
|
||||
}
|
||||
|
||||
return monthNames[date.getMonth()];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get day names for calendar header
|
||||
*/
|
||||
export const getDayNames = (short: boolean = false): string[] => {
|
||||
if (short) {
|
||||
return ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
||||
}
|
||||
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if two dates are on the same day
|
||||
*/
|
||||
export const isSameDay = (date1: Date, date2: Date): boolean => {
|
||||
return date1.toDateString() === date2.toDateString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a date is today
|
||||
*/
|
||||
export const isToday = (date: Date): boolean => {
|
||||
return isSameDay(date, new Date());
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate calendar days grid for a given month
|
||||
*/
|
||||
export const generateCalendarDays = (currentDate: Date, events: Event[]): CalendarDay[] => {
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
|
||||
// Get first day of month and number of days
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastDay.getDate();
|
||||
const startingDayOfWeek = firstDay.getDay();
|
||||
|
||||
// Calculate previous month days to show
|
||||
const prevMonth = new Date(year, month - 1, 0);
|
||||
const daysInPrevMonth = prevMonth.getDate();
|
||||
|
||||
const calendarDays: CalendarDay[] = [];
|
||||
|
||||
// Calculate total cells needed
|
||||
const totalCells = Math.ceil((daysInMonth + startingDayOfWeek) / 7) * 7;
|
||||
|
||||
for (let i = 0; i < totalCells; i++) {
|
||||
let dayNumber: number;
|
||||
let isCurrentMonth: boolean;
|
||||
let currentDayDate: Date;
|
||||
|
||||
if (i < startingDayOfWeek) {
|
||||
// Previous month days
|
||||
dayNumber = daysInPrevMonth - (startingDayOfWeek - i - 1);
|
||||
isCurrentMonth = false;
|
||||
currentDayDate = new Date(year, month - 1, dayNumber);
|
||||
} else if (i >= startingDayOfWeek + daysInMonth) {
|
||||
// Next month days
|
||||
dayNumber = i - startingDayOfWeek - daysInMonth + 1;
|
||||
isCurrentMonth = false;
|
||||
currentDayDate = new Date(year, month + 1, dayNumber);
|
||||
} else {
|
||||
// Current month days
|
||||
dayNumber = i - startingDayOfWeek + 1;
|
||||
isCurrentMonth = true;
|
||||
currentDayDate = new Date(year, month, dayNumber);
|
||||
}
|
||||
|
||||
// Get events for this day
|
||||
const dayEvents = events.filter(event => {
|
||||
const eventDate = new Date(event.start_time);
|
||||
return isSameDay(eventDate, currentDayDate);
|
||||
});
|
||||
|
||||
calendarDays.push({
|
||||
day: dayNumber,
|
||||
isCurrentMonth,
|
||||
isToday: isToday(currentDayDate),
|
||||
date: currentDayDate,
|
||||
events: dayEvents
|
||||
});
|
||||
}
|
||||
|
||||
return calendarDays;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get events for a specific day
|
||||
*/
|
||||
export const getEventsForDay = (events: Event[], date: Date): Event[] => {
|
||||
return events.filter(event => {
|
||||
const eventDate = new Date(event.start_time);
|
||||
return isSameDay(eventDate, date);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
*/
|
||||
export const formatDate = (date: Date, options?: Intl.DateTimeFormatOptions): string => {
|
||||
const defaultOptions: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
};
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', { ...defaultOptions, ...options }).format(date);
|
||||
};
|
||||
|
||||
/**
|
||||
* Format time for display
|
||||
*/
|
||||
export const formatTime = (date: Date): string => {
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
/**
|
||||
* Format date and time for display
|
||||
*/
|
||||
export const formatDateTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return {
|
||||
date: formatDate(date),
|
||||
time: formatTime(date),
|
||||
dayOfWeek: date.toLocaleDateString('en-US', { weekday: 'long' })
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get category color classes
|
||||
*/
|
||||
export const getCategoryColor = (category?: string): string => {
|
||||
const colors = {
|
||||
music: 'from-purple-500 to-pink-500',
|
||||
arts: 'from-blue-500 to-cyan-500',
|
||||
community: 'from-green-500 to-emerald-500',
|
||||
business: 'from-gray-500 to-slate-500',
|
||||
food: 'from-orange-500 to-red-500',
|
||||
sports: 'from-indigo-500 to-purple-500',
|
||||
default: 'from-gray-400 to-gray-500'
|
||||
};
|
||||
return colors[category as keyof typeof colors] || colors.default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get category icon
|
||||
*/
|
||||
export const getCategoryIcon = (category?: string): string => {
|
||||
const icons = {
|
||||
music: '🎵',
|
||||
arts: '🎨',
|
||||
community: '🤝',
|
||||
business: '💼',
|
||||
food: '🍷',
|
||||
sports: '⚽',
|
||||
default: '📅'
|
||||
};
|
||||
return icons[category as keyof typeof icons] || icons.default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Group events by date
|
||||
*/
|
||||
export const groupEventsByDate = (events: Event[]): Record<string, Event[]> => {
|
||||
const grouped: Record<string, Event[]> = {};
|
||||
|
||||
events.forEach(event => {
|
||||
const dateKey = new Date(event.start_time).toDateString();
|
||||
if (!grouped[dateKey]) {
|
||||
grouped[dateKey] = [];
|
||||
}
|
||||
grouped[dateKey].push(event);
|
||||
});
|
||||
|
||||
return grouped;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort events by start time
|
||||
*/
|
||||
export const sortEventsByDate = (events: Event[]): Event[] => {
|
||||
return events.sort((a, b) =>
|
||||
new Date(a.start_time).getTime() - new Date(b.start_time).getTime()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter future events
|
||||
*/
|
||||
export const getFutureEvents = (events: Event[]): Event[] => {
|
||||
const today = new Date();
|
||||
return events.filter(event => new Date(event.start_time) >= today);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get relative date text (Today, Tomorrow, etc.)
|
||||
*/
|
||||
export const getRelativeDateText = (date: Date): string => {
|
||||
const today = new Date();
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
if (isSameDay(date, today)) {
|
||||
return 'Today';
|
||||
} else if (isSameDay(date, tomorrow)) {
|
||||
return 'Tomorrow';
|
||||
} else {
|
||||
return formatDate(date, {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Truncate text with ellipsis
|
||||
*/
|
||||
export const truncateText = (text: string, maxLength: number): string => {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get CSS variable value
|
||||
*/
|
||||
export const getCSSVariable = (variableName: string): string => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(variableName).trim();
|
||||
};
|
||||
Reference in New Issue
Block a user