diff --git a/src/components/CommunityPortal/Calendar/CommunityCalendar.jsx b/src/components/CommunityPortal/Calendar/CommunityCalendar.jsx
index 5461abca13..e7201a98f9 100644
--- a/src/components/CommunityPortal/Calendar/CommunityCalendar.jsx
+++ b/src/components/CommunityPortal/Calendar/CommunityCalendar.jsx
@@ -2,176 +2,94 @@ import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import ReactCalendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';
+import axios from 'axios';
+import { ENDPOINTS } from '../../../utils/URL';
import CalendarActivitySection from './CalendarActivitySection';
import styles from './CommunityCalendar.module.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faClock, faLocationDot, faTag, faCircleCheck } from '@fortawesome/free-solid-svg-icons';
-const mockEvents = [
- {
- id: 1,
- title: 'Event 1',
- type: 'Workshop',
- location: 'Virtual',
- time: '10:00 AM',
- date: new Date(2025, 0, 27),
- status: 'New',
- description: 'Detailed description of Event 1.',
- registeredCount: 1,
- capacity: 20,
- },
- {
- id: 2,
- title: 'Event 2',
- type: 'Meeting',
- location: 'In person',
- time: '2:00 PM',
- date: new Date(2025, 0, 31),
- status: 'Needs Attendees',
- description: 'Detailed description of Event 2.',
- registeredCount: 2,
- capacity: 10,
- },
- {
- id: 3,
- title: 'Event 3',
- type: 'Workshop',
- location: 'Virtual',
- time: '12:00 PM',
- date: new Date(2025, 0, 28),
- status: 'New',
- description: 'Detailed description of Event 3.',
- registeredCount: 1,
- capacity: 100,
- },
- {
- id: 4,
- title: 'Event 4 (Full)',
- type: 'Webinar',
- location: 'Virtual',
- time: '3:00 AM',
- date: new Date(2025, 0, 3),
- status: 'Full',
- description: 'Detailed description of Event 4.',
- registeredCount: 10,
- capacity: 10,
- },
- {
- id: 5,
- title: 'Event 5',
- type: 'Social Gathering',
- location: 'In person',
- time: '11:00 AM',
- date: new Date(2025, 0, 28),
- status: 'Filling Fast',
- description: 'Detailed description of Event 5.',
- registeredCount: 49,
- capacity: 50,
- },
-];
-
-const STATUSES = ['New', 'Needs Attendees', 'Filling Fast', 'Full Event'];
-const EVENT_TYPES = ['Workshop', 'Webinar', 'Meeting', 'Social Gathering'];
-const LOCATIONS = ['Virtual', 'In person'];
-const TIMES = ['10:00 AM', '1:00 PM', '3:00 PM', '5:00 PM'];
-
-function CommunityCalendar() {
- const [filter, setFilter] = useState({ type: 'all', location: 'all', status: 'all' });
- const [selectedDate, setSelectedDate] = useState(new Date());
- const [selectedEvent, setSelectedEvent] = useState(null);
- const [showEventModal, setShowEventModal] = useState(false);
- const [events, setEvents] = useState(mockEvents);
-
- // Derive status from count/capacity
- const getDerivedStatus = useCallback(event => {
- if (event.registeredCount === undefined || !event.capacity) {
- return event.status;
- }
-
- const count = event.registeredCount;
- const capacity = event.capacity;
+const MOCK_EVENTS = [];
- if (count >= capacity) return 'Full'; // max capacity
- if (count >= capacity * 0.7) return 'Filling Fast'; // 70% threshold
- if (capacity * 0.1 < count && count <= capacity * 0.2) return 'Needs Attendees'; // between 10% and 20% of capacity
+const normalizeStatus = status => {
+ if (!status) return 'New';
- return 'New';
- }, []);
+ const s = status.toLowerCase();
- const getDerivedStatusClassNames = event => {
- const status = getDerivedStatus(event);
- if (status === 'Full') return 'Full';
- if (status === 'Filling Fast') return 'FillingFast';
- if (status === 'Needs Attendees') return 'NeedsAttendees';
- if (status === 'New') return 'New';
- return '';
- };
+ if (s.includes('need')) return 'Needs Attendees';
+ if (s.includes('fill')) return 'Filling Fast';
+ if (s.includes('full')) return 'Full Event';
+ if (s.includes('new')) return 'New';
- // Function to determine the CSS class based on the derived status
- const getEventStatusClass = useCallback(dynamicStatus => {
- let statusClassName;
-
- switch (dynamicStatus) {
- case 'Full':
- statusClassName = styles.statusFull;
- break;
- case 'Filling Fast':
- statusClassName = styles.statusFillingFast;
- break;
- case 'New':
- statusClassName = styles.statusNew;
- break;
- case 'Needs Attendees':
- statusClassName = styles.statusNeedsAttendees;
- break;
- default:
- statusClassName = '';
- }
- return statusClassName;
- }, []);
+ return 'New';
+};
- // Handler for registration button click
- const handleRegister = useCallback(
- eventToRegister => {
- if (getDerivedStatus(eventToRegister) === 'Full') {
- alert('This event is full!');
- return;
- }
-
- const updatedEvent = {
- ...eventToRegister,
- registeredCount: (eventToRegister.registeredCount || 0) + 1,
- };
+export default function CommunityCalendar() {
+ const [events, setEvents] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [filter, setFilter] = useState({ type: 'all', location: 'all', status: 'all' });
+ const [selectedDate, setSelectedDate] = useState(new Date());
+ const [selectedEvent, setSelectedEvent] = useState(null);
+ const [showEventModal, setShowEventModal] = useState(false);
+ const [hoveredEventId, setHoveredEventId] = useState(null);
+ const darkMode = useSelector(state => state.theme.darkMode);
+ const currentDate = new Date();
- // Changing event status to "Full" when capacity is reached and Event Title to match it
- if (updatedEvent.registeredCount === updatedEvent.capacity) {
- updatedEvent.status = 'Full';
- updatedEvent.title = updatedEvent.title + ' (Full)';
+ useEffect(() => {
+ const fetchEvents = async () => {
+ setIsLoading(true);
+ try {
+ const response = await axios.get(ENDPOINTS.EVENTS);
+
+ const apiEvents = response.data?.events || response.data || [];
+
+ if (!apiEvents || apiEvents.length === 0) {
+ console.warn('API returned empty → using mock events');
+ setEvents(MOCK_EVENTS);
+ } else {
+ setEvents(apiEvents);
+ }
+ } catch (err) {
+ console.warn('API failed → using mock events');
+ setEvents(MOCK_EVENTS);
+ } finally {
+ setIsLoading(false);
}
+ };
- setEvents(prevEvents =>
- prevEvents.map(ev => (ev.id === eventToRegister.id ? updatedEvent : ev)),
- );
+ fetchEvents();
+ }, []);
- setSelectedEvent(updatedEvent);
+ const mappedEvents = useMemo(() => {
+ return events.map(event => {
+ const eventDate = new Date(event.date);
+ const timeString = eventDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
+
+ return {
+ ...event,
+ id: event.id || `${event.title}-${eventDate.getTime()}`,
+ date: eventDate,
+ type: event.type || 'General',
+ status: normalizeStatus(event.status),
+ time: event.time || timeString,
+ description: event.description || `Join us for ${event.title}`,
+ location: event.location || 'Online',
+ };
+ });
+ }, [events]);
- console.log(
- `Registered for ${updatedEvent.title}! Count updated to ${updatedEvent.registeredCount}.`,
- );
- },
- [getDerivedStatus],
+ const filteredEvents = useMemo(
+ () =>
+ mappedEvents.filter(
+ e =>
+ (filter.type === 'all' || e.type === filter.type) &&
+ (filter.location === 'all' || e.location === filter.location) &&
+ (filter.status === 'all' || e.status === filter.status),
+ ),
+ [mappedEvents, filter],
);
- // Memoized filtered events - only recalculates when filter or events change
- const filteredEvents = useMemo(() => {
- return events.filter(
- event =>
- (filter.type === 'all' || event.type === filter.type) &&
- (filter.location === 'all' || event.location === filter.location) &&
- (filter.status === 'all' || getDerivedStatus(event) === filter.status),
- );
- }, [filter, events, getDerivedStatus]);
-
// Enhanced event caching by date - memoized for performance
const eventCache = useMemo(() => {
const map = new Map();
@@ -192,18 +110,6 @@ function CommunityCalendar() {
return map;
}, [filteredEvents]);
- // Memoized unique filter values for dropdowns
- const uniqueFilterValues = useMemo(() => {
- const types = [...new Set(events.map(event => event.type))];
- const locations = [...new Set(events.map(event => event.location))];
-
- // Use the dynamic statuses for the filter dropdown
- const dynamicStatuses = ['Full', 'Filling Fast', 'Open', 'New', 'Needs Attendees'];
- const statuses = [...new Set(dynamicStatuses)];
-
- return { types, locations, statuses };
- }, [events]);
-
const handleFilterChange = useCallback(
filterType => e => {
setFilter(prev => ({
@@ -219,7 +125,8 @@ function CommunityCalendar() {
// Memoized helper function to get events for a specific date
const getEventsForDate = useCallback(
date => {
- return eventCache.get(date.toDateString()) || [];
+ if (!date) return [];
+ return eventCache.get(new Date(date).toDateString()) || [];
},
[eventCache],
);
@@ -257,22 +164,13 @@ function CommunityCalendar() {
},
[getEventsForDate],
);
-
const handleEventClick = useCallback(event => {
+ setSelectedDate(new Date(event.date));
+
setSelectedEvent(event);
+
setShowEventModal(true);
}, []);
-
- const handleEventKeyPress = useCallback(
- (e, event) => {
- if (e.key === 'Enter' || e.key === ' ') {
- e.preventDefault();
- handleEventClick(event);
- }
- },
- [handleEventClick],
- );
-
const closeEventModal = useCallback(() => {
setShowEventModal(false);
setSelectedEvent(null);
@@ -305,24 +203,20 @@ function CommunityCalendar() {
useEffect(() => {
const eventsForDate = getEventsForDate(selectedDate);
+
if (eventsForDate.length === 0) {
if (selectedEvent !== null) {
setSelectedEvent(null);
}
- if (showEventModal) {
- setShowEventModal(false);
- }
return;
}
const hasSelectedEvent = eventsForDate.some(event => event.id === selectedEvent?.id);
+
if (!hasSelectedEvent) {
setSelectedEvent(eventsForDate[0]);
- if (showEventModal) {
- setShowEventModal(false);
- }
}
- }, [getEventsForDate, selectedDate, selectedEvent, showEventModal]);
+ }, [getEventsForDate, selectedDate]);
const statusMap = {
New: 'statusNew',
@@ -331,43 +225,183 @@ function CommunityCalendar() {
'Full Event': 'statusFull',
};
- // Memoized tile content function
+ const statusIconMap = {
+ New: '⭐',
+ 'Needs Attendees': '🙋',
+ 'Filling Fast': '⚡',
+ 'Full Event': '⛔',
+ Full: '⛔',
+ };
+
+ function WeeklyTimeGrid({ events, selectedDate, onEventClick, darkMode }) {
+ const hours = Array.from({ length: 24 }, (_, i) => i);
+
+ const startOfWeek = useMemo(() => {
+ const d = new Date(selectedDate);
+ d.setDate(d.getDate() - d.getDay());
+ return d;
+ }, [selectedDate]);
+
+ const weekDays = useMemo(() => {
+ return Array.from({ length: 7 }, (_, i) => {
+ const day = new Date(startOfWeek);
+ day.setDate(startOfWeek.getDate() + i);
+ return day;
+ });
+ }, [startOfWeek]);
+
+ return (
+
+
+
+ {weekDays.map(date => (
+
+
+ {date.toLocaleDateString('en-US', { weekday: 'short' })}
+
+
+ {date.getDate()}
+
+
+ ))}
+
+
+
+ {hours.map(hour => (
+
+
+ {hour === 0
+ ? '12 AM'
+ : hour > 12
+ ? `${hour - 12} PM`
+ : hour === 12
+ ? '12 PM'
+ : `${hour} AM`}
+
+
+ {weekDays.map(date => {
+ const cellEvents = events.filter(e => {
+ const eventDate = new Date(e.date);
+ const [hStr] = e.time.split(':');
+ let h = parseInt(hStr, 10);
+ const isPM = e.time.toLowerCase().includes('pm');
+ const isAM = e.time.toLowerCase().includes('am');
+ if (isPM && h !== 12) h += 12;
+ if (isAM && h === 12) h = 0;
+
+ return eventDate.toDateString() === date.toDateString() && h === hour;
+ });
+
+ return (
+
+ {cellEvents.map(ev => (
+
+ ))}
+
+ );
+ })}
+
+ ))}
+
+
+ );
+ }
+
+ // Render event tiles
const tileContent = useCallback(
({ date, view }) => {
- if (view === 'month') {
- const eventsForTile = getEventsForDate(date).map(event => {
- const statusClass = getEventStatusClass(getDerivedStatus(event));
- return (
- handleEventClick(event)}
- onKeyDown={e => handleEventKeyPress(e, event)}
- role="button"
- tabIndex={0}
- aria-label={`Click to view details for ${event.title}`}
- style={{
- borderRadius: '4px',
- padding: '2px 6px',
- }}
+ if (view !== 'month') return null;
+ const events = getEventsForDate(date);
+ if (!events.length) return null;
+
+ const visible = events.slice(0, 3);
+ const hiddenCount = events.length - 3;
+
+ return (
+
+ {visible.map(e => {
+ const statusKey = statusMap[e.status] || 'statusNew';
+
+ return (
+
+ );
+ })}
+ {hiddenCount > 0 && (
+
- );
- });
- return eventsForTile.length > 0 ? (
-
{eventsForTile}
- ) : null;
- }
- return null;
+ +{hiddenCount} more
+
+ )}
+
+ );
},
- [
- getEventsForDate,
- handleEventClick,
- handleEventKeyPress,
- getEventStatusClass,
- getDerivedStatus,
- ],
+ [getEventsForDate, handleEventClick, darkMode, hoveredEventId],
);
// Memoized tile class name function
@@ -390,12 +424,14 @@ function CommunityCalendar() {
setFilter(prev => ({ ...prev, type: e.target.value }));
}, []); */
- /* const handleLocationFilterChange = useCallback(e => {
- setFilter(prev => ({ ...prev, location: e.target.value }));
- }, []); */
-
- // Memoized dark mode selector
- const darkMode = useSelector(state => state.theme.darkMode);
+ const uniqueFilterValues = useMemo(
+ () => ({
+ types: [...new Set(mappedEvents.map(e => e.type))],
+ locations: [...new Set(mappedEvents.map(e => e.location))],
+ statuses: [...new Set(mappedEvents.map(e => e.status))],
+ }),
+ [mappedEvents],
+ );
// Memoized CSS classes
const calendarClasses = useMemo(
@@ -537,7 +573,11 @@ function CommunityCalendar() {
-
+
{event.title}
-
+
-
@@ -608,7 +647,9 @@ function CommunityCalendar() {
icon={faCircleCheck}
className={styles.metaIcon}
/>
- {event.status}
+
+ {statusIconMap[event.status] || ''} {event.status}
+
@@ -649,11 +690,16 @@ function CommunityCalendar() {
))}
@@ -685,71 +731,47 @@ function CommunityCalendar() {
-
-
-
- {getDerivedStatus(selectedEvent)}
-
-
+
+
+ {statusIconMap[selectedEvent.status] || ''} {selectedEvent.status}
+
+
-
-
-
- {selectedEvent.type}
-
-
-
- {selectedEvent.location}
-
-
-
- {selectedEvent.date.toLocaleDateString()}
-
-
-
- {selectedEvent.time}
-
- {/* Display count/capacity for context */}
-
-
-
- {selectedEvent.registeredCount || 0} / {selectedEvent.capacity || 'N/A'}
-
-
+
+
+ Type:
+ {selectedEvent.type}
-
-
-
-
{selectedEvent.description}
+
+ Location:
+ {selectedEvent.location}
+
+
+ Date:
+ {selectedEvent.date.toLocaleDateString()}
+
+
+ Time:
+ {selectedEvent.time}
-
-
-
+
+
Description:
+
{selectedEvent.description}
+
+
+
+
+
)}
);
}
-
-export default CommunityCalendar;
diff --git a/src/components/CommunityPortal/Calendar/CommunityCalendar.module.css b/src/components/CommunityPortal/Calendar/CommunityCalendar.module.css
index 246a997d51..932fa20c97 100644
--- a/src/components/CommunityPortal/Calendar/CommunityCalendar.module.css
+++ b/src/components/CommunityPortal/Calendar/CommunityCalendar.module.css
@@ -15,6 +15,7 @@
.calendarActivitySection,
.calendarSection {
+ position: relative;
flex: 1;
border-radius: 8px;
padding: 20px;
@@ -60,6 +61,8 @@
}
.reactCalendar {
+ position: relative;
+ z-index: 1;
width: 100%;
height: auto;
font-size: 1.2rem;
@@ -144,17 +147,17 @@
height: 80px;
overflow: auto;
border: 1px solid #555454;
- box-sizing: border-box;
+ box-sizing: border-box;
+ vertical-align: top;
+ padding: 2px;
}
.reactCalendar :global(.react-calendar__navigation) {
background-color: #f9f9f9;
border-bottom: 1px solid #ddd;
margin-bottom: 0 !important;
-
}
-
.reactCalendar :global(.react-calendar__tile) {
display: grid;
height: 100px;
@@ -213,38 +216,37 @@
}
.reactCalendar :global(.react-calendar__tile) .tileEvents {
- font-size: 0.9rem;
- color: #555;
- margin-top: 4px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
- line-height: 1.1;
+ width: 100%;
}
-.reactCalendar :global(.react-calendar__tile) :global(.eventItem) {
- background-color: #e0f7fa;
- padding: 5px;
- border-radius: 4px;
- margin-bottom: 5px;
- white-space: nowrap;
+.tileEvents {
+ position: relative;
}
/* Ensure tileEvents text is readable on selected date */
-.reactCalendar :global(.react-calendar__tile.selectedDate) .tileEvents {
- color: #1a202c;
-}
.reactCalendarDarkMode :global(.react-calendar__tile) .tileEvents {
color: #e2e8f0;
}
+.reactCalendar :global(.react-calendar__tile.selectedDate) .tileEvents {
+ color: #1a202c;
+}
+
.reactCalendarDarkMode :global(.react-calendar__tile.selectedDate) .tileEvents {
color: #fff;
}
-
+.eventItem {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
.reactCalendar .eventItem {
display: block;
@@ -255,9 +257,20 @@
border-radius: 3px;
background-color: #e0f7fa;
cursor: pointer;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+}
+
+.reactCalendar :global(.eventItem) {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ max-width: 100%;
+ padding: 2px 6px;
+ margin-bottom: 2px;
+ border-radius: 999px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ background-color: #e0f7fa;
+ cursor: pointer;
}
.reactCalendar :global(.react-calendar__tile) .eventItem.statusNew {
@@ -394,8 +407,23 @@
color: #ffcdd2;
}
-.reactCalendar .eventItem.clickable:hover {
- transform: scale(1.05) !important;
+.overflowPopup .eventItem {
+ margin-bottom: 8px;
+ font-size: 0.9rem;
+ padding: 6px 10px;
+ border-radius: 6px;
+ color: #fff;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s ease;
+}
+
+.overflowPopup .eventItem:hover {
+ transform: translateY(-1px);
+}
+
+.reactCalendar .eventItem:hover {
+ transform: translate(-1px);
z-index: 10;
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
}
@@ -501,6 +529,18 @@
align-items: flex-start;
}
+.modalHeader h2 {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: #2d3748;
+}
+
+.calendarActivitySectionDarkMode h2 {
+ color: #fff;
+ margin-bottom: 10px;
+}
+
.selectedDatePanelDarkMode .selectedDateHeader h2 {
color: #f7fafc;
}
@@ -628,22 +668,22 @@
.eventTooltip {
position: absolute;
- bottom: 100%;
- left: 50%;
- transform: translateX(-50%);
- margin-bottom: 5px;
+ top: 0;
+ left: 100%;
+ transform: translate(8px, -10px);
+ z-index: 999;
width: 220px;
padding: 10px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
- z-index: 10;
- pointer-events: none;
display: flex;
flex-direction: column;
gap: 4px;
- color: #333;
+ font-size: 0.75rem;
+ line-height: 1.3;
+ pointer-events: none;
}
.eventTooltipDark {
@@ -674,10 +714,10 @@
justify-content: center;
align-items: center;
z-index: 1000;
- animation: fadeIn 0.2s ease-out;
+ animation: fade-in 0.2s ease-out;
}
-@keyframes fadeIn {
+@keyframes fade-in {
from {
opacity: 0;
}
@@ -700,7 +740,7 @@
z-index: 2;
}
-@keyframes slideIn {
+@keyframes slide-in {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
@@ -712,11 +752,12 @@
}
}
-/* .eventModalDark {
+.eventModalDark {
background: #1a202c;
color: #e2e8f0;
border: 1px solid #4a5568;
-} */
+ box-shadow: 0 10px 40px rgb(0 0 0 / 60%);
+}
.modalHeader {
display: flex;
@@ -732,13 +773,6 @@
border-radius: 12px 12px 0 0;
}
-.modalHeader h2 {
- margin: 0;
- font-size: 1.5rem;
- font-weight: 600;
- color: #2d3748;
-}
-
.eventModalDark .modalHeader h2 {
color: #fff;
}
@@ -883,6 +917,16 @@
letter-spacing: 0.5px;
}
+.eventDescription label {
+ display: block;
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: #718096;
+ margin-bottom: 8px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
.eventModalDark .detailItem label {
color: #cbd5e0;
}
@@ -901,16 +945,6 @@
margin-bottom: 24px;
}
-.eventDescription label {
- display: block;
- font-size: 0.8rem;
- font-weight: 600;
- color: #718096;
- margin-bottom: 8px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
-}
-
.eventModalDark .eventDescription label {
color: #cbd5e0;
}
@@ -922,6 +956,11 @@
color: #4a5568;
}
+.activityNoEventsMessage p {
+ margin: 0;
+ font-size: 1rem;
+}
+
.eventModalDark .eventDescription p {
color: #f7fafc;
}
@@ -988,7 +1027,6 @@
background: linear-gradient(135deg, #2d3748 0%, #4a5568 100%);
}
-/* Better dark mode contrast for all text elements */
.eventModalDark * {
text-shadow: 0 1px 2px rgb(0 0 0 / 10%);
}
@@ -1025,11 +1063,6 @@
color: #f7fafc;
}
-.calendarActivitySectionDarkMode h2 {
- color: #fff;
- margin-bottom: 10px;
-}
-
.calendarActivityList {
list-style: none;
padding: 0;
@@ -1074,6 +1107,47 @@
color: #fff;
}
+.eventButton {
+ width: 100%;
+ text-align: left;
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.eventButton:hover {
+ background-color: #f0f0f0;
+}
+
+.calendarActivityItemDarkMode .eventButton:hover {
+ background-color: #4a5568;
+}
+
+.calendarActivityItem.clickable {
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.calendarActivityItem.clickable:hover {
+ background-color: #f0f0f0;
+}
+
+.calendarActivityItemDarkMode.clickable:hover {
+ background-color: #4a5568;
+}
+
+.activityNoEventsMessage {
+ padding: 20px;
+ text-align: center;
+ color: #666;
+}
+
+.calendarActivitySectionDarkMode .activityNoEventsMessage {
+ color: #cbd5e0;
+}
+
.activityMessage {
margin: 0 0 4px;
color: #1a202c;
@@ -1155,6 +1229,16 @@
}
.reactCalendarDarkMode :global(.react-calendar__navigation) {
+ background-color: #1b2a41;
+ border-bottom: 1px solid #444;
+}
+
+.reactCalendarDarkMode :global(.react-calendar__navigation button) {
+ color: #fff;
+}
+
+.reactCalendarDarkMode :global(.react-calendar__navigation button:enabled:hover),
+.reactCalendarDarkMode :global(.react-calendar__navigation button:enabled:focus) {
background-color: #2d3748;
border-bottom-color: #4a5568;
}
@@ -1280,21 +1364,6 @@
border-bottom-color: #4a5568;
}
-.overflowPopup .eventItem {
- margin-bottom: 8px;
- font-size: 0.9rem;
- padding: 6px 10px;
- border-radius: 6px;
- color: #fff;
- font-weight: 600;
- cursor: pointer;
- transition: transform 0.2s ease;
-}
-
-.overflowPopup .eventItem:hover {
- transform: translateY(-1px);
-}
-
.detailLabel {
font-size: 0.8rem;
font-weight: 600;
@@ -1305,7 +1374,23 @@
display: block;
}
+/* Week Grid Styles */
+.weekGridContainer {
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ height: 700px;
+ overflow: hidden;
+ margin-bottom: 20px;
+}
+.weekGridBody {
+ flex: 1;
+ overflow-y: scroll;
+ background: #fff;
+}
@media (width <= 1024px) {
.calendarContainer{
@@ -1318,3 +1403,136 @@
}
+.dateLabel {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+.hourRow {
+ display: grid;
+ grid-template-columns: 80px repeat(7, 1fr);
+ min-height: 80px;
+ border-bottom: 1px solid #f1f5f9;
+}
+
+.timeLabel {
+ font-size: 0.75rem;
+ color: #94a3b8;
+ text-align: right;
+ padding-right: 12px;
+ padding-top: 8px;
+ font-weight: 500;
+ border-right: 1px solid #e2e8f0;
+}
+
+.gridCell {
+ border-left: 1px solid #f1f5f9;
+ position: relative;
+ padding: 4px;
+ transition: background 0.2s;
+}
+
+.gridCell:hover {
+ background-color: #f8fafc;
+}
+
+.gridEvent {
+ background: #dcfce7;
+ border-left: 4px solid #22c55e;
+ border-radius: 6px;
+ padding: 6px;
+ font-size: 0.75rem;
+ cursor: pointer;
+ box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
+ margin-bottom: 4px;
+}
+
+.gridEventTitle {
+ font-weight: 600;
+ color: #166534;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Dark Mode Support */
+.weekGridContainerDark {
+ background: #1a2332;
+ border-color: #2d3748;
+}
+
+.weekGridContainerDark .weekGridBody {
+ background: #0f172a;
+}
+
+.weekGridHeaderDark {
+ background: #232d3f;
+ border-bottom-color: #2d3748;
+}
+
+.dayLabelDark {
+ color: #a0aec0;
+}
+
+.dateLabelDark {
+ color: #fff;
+}
+
+.hourRowDark {
+ border-bottom-color: #2d3748;
+}
+
+.timeLabelDark {
+ color: #718096;
+ border-right-color: #2d3748;
+}
+
+.gridCellDark {
+ border-left-color: #2d3748;
+}
+
+.gridCellDark:hover {
+ background-color: rgb(255 255 255 / 3%);
+}
+
+.gridEventDark {
+ background: #064e3b;
+ border-left: 4px solid #10b981;
+ box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
+}
+
+.gridEventTitleDark {
+ color: #d1fae5;
+}
+
+.gridEventTimeDark {
+ color: #a7f3d0;
+}
+
+.eventContent {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ overflow: hidden;
+}
+
+.eventIcon {
+ font-size: 0.75rem;
+ flex-shrink: 0;
+ display: inline-flex;
+ align-items: center;
+}
+
+.eventTitleText {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.statusInline {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-weight: 600;
+}