Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { createEvent } from '../../../../actions/communityPortal/eventActions';
import '../../../Header/DarkMode.module.css';

function CreateEventModal({ isOpen, toggle }) {
function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {

Check warning on line 8 in src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'onEventCreated' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ228tXgBsV3QcQ5ASXl&open=AZ228tXgBsV3QcQ5ASXl&pullRequest=5182
const dispatch = useDispatch();
const darkMode = useSelector(state => state.theme.darkMode);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -169,11 +169,11 @@
try {
const result = await dispatch(createEvent(eventData));
if (result?.success) {
onEventCreated();
handleToggle();
// The events list will be refreshed when the component re-renders
}
} catch (error) {
// Error handling is done in the action
setErrors('Unable to create a new event. Please try again later.');
} finally {
setLoading(false);
}
Expand Down
180 changes: 123 additions & 57 deletions src/components/CommunityPortal/Reports/Participation/MyCases.jsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,127 @@
import { useState } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useSelector } from 'react-redux';
import axios from 'axios';
import styles from './MyCases.module.css';
import mockEvents from './mockData';
import CreateEventModal from './CreateEventModal';
import { ENDPOINTS } from '../../../../utils/URL';

const INITIAL_DISPLAY = 10;

const normalizeEvent = e => ({
id: e._id,
eventType: e.type || 'Workshop',
eventDate: e.startTime,
eventTime: e.startTime
? new Date(e.startTime).toLocaleString('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: true,
month: 'short',
day: 'numeric',
year: 'numeric',
})
: '',
eventName: e.title,
attendees: e.currentAttendees ?? 0,
location: e.location || 'TBD',
});

function MyCases() {
const [view, setView] = useState('card');
const [filter, setFilter] = useState('all');
const [expanded, setExpanded] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [showAll, setShowAll] = useState(false);

const [allEvents, setAllEvents] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [fetchError, setFetchError] = useState(null);

const isExporting =
typeof document !== 'undefined' && document.documentElement?.dataset?.exporting === 'true'; // Sonar: prefer .dataset
typeof document !== 'undefined' && document.documentElement?.dataset?.exporting === 'true';

const filterEvents = events => {
const now = new Date();
const darkMode = useSelector(state => state.theme.darkMode);

const nowTime = now.getTime();
const fetchEvents = useCallback(async () => {
setIsLoading(true);
setFetchError(null);
try {
const response = await axios.get(ENDPOINTS.EVENTS);
const raw = response.data?.events || response.data || [];
setAllEvents(raw.map(normalizeEvent));
} catch {
setFetchError('Failed to load events.');
} finally {
setIsLoading(false);
}
}, []);

const upcomingEvents = events.filter(event => {
const eventTime = new Date(event.eventDate).getTime();
return eventTime >= nowTime;
});
useEffect(() => {
fetchEvents();
}, [fetchEvents]);

const handleFilterChange = e => {
setFilter(e.target.value);
setShowAll(false);
};

const handleEventCreated = () => {
fetchEvents();
setShowAll(false);
};

const applyFilter = allEvts => {
const now = new Date();

if (filter === 'today') {
return upcomingEvents.filter(event => {
const eventDate = new Date(event.eventDate);
return allEvts.filter(e => {
const d = new Date(e.eventDate);
return (
eventDate.getDate() === now.getDate() &&
eventDate.getMonth() === now.getMonth() &&
eventDate.getFullYear() === now.getFullYear()
d.getDate() === now.getDate() &&
d.getMonth() === now.getMonth() &&
d.getFullYear() === now.getFullYear()
);
});
}
if (filter === 'thisWeek') {
const startOfWeek = new Date(now.setDate(now.getDate() - now.getDay()));
const startOfWeek = new Date(now);
startOfWeek.setDate(now.getDate() - now.getDay());
startOfWeek.setHours(0, 0, 0, 0);
const endOfWeek = new Date(startOfWeek);
endOfWeek.setDate(endOfWeek.getDate() + 6);
return upcomingEvents.filter(event => {
const eventDate = new Date(event.eventTime);
return eventDate >= startOfWeek && eventDate <= endOfWeek;
endOfWeek.setDate(startOfWeek.getDate() + 6);
endOfWeek.setHours(23, 59, 59, 999);
return allEvts.filter(e => {
const d = new Date(e.eventDate);
return d >= startOfWeek && d <= endOfWeek && d >= new Date();
});
}
if (filter === 'thisMonth') {
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
return upcomingEvents.filter(event => {
const eventDate = new Date(event.eventTime);
return eventDate >= startOfMonth && eventDate <= endOfMonth;
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
return allEvts.filter(e => {
const d = new Date(e.eventDate);
return d >= startOfMonth && d <= endOfMonth && d >= new Date();
});
}
return upcomingEvents;
return allEvts.filter(e => {
const d = new Date(e.eventDate);
return d > new Date();
});
};

const darkMode = useSelector(state => state.theme.darkMode);
const filteredEvents = filterEvents(mockEvents);
const filteredSorted = applyFilter(allEvents).sort(
(a, b) => new Date(a.eventDate).getTime() - new Date(b.eventDate).getTime(),
);

filteredEvents.sort((a, b) => new Date(a.eventDate).getTime() - new Date(b.eventDate).getTime());
const displayEvents =
isExporting || showAll ? filteredSorted : filteredSorted.slice(0, INITIAL_DISPLAY);

// Sonar: extract nested ternary into independent statement
let visibleEvents = filteredEvents;
if (!isExporting) {
visibleEvents = expanded ? filteredEvents.slice(0, 40) : filteredEvents.slice(0, 10);
}
const hasMore = !showAll && filteredSorted.length > INITIAL_DISPLAY;

const placeholderAvatar = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
const placeholderAvatar = 'https://picsum.photos/id/201/200/300';

const renderCardView = () => (
<div
className={`case-cards-global ${styles.caseCards} ${
expanded || isExporting ? styles.expanded : ''
}`}
>
{visibleEvents.map(event => (
<div className={`case-cards-global ${styles.caseCards} ${isExporting ? styles.expanded : ''}`}>
{displayEvents.map(event => (
<div
className={`case-card-global ${styles.caseCard} ${darkMode ? styles.caseCardDark : ''}`}
key={event.id}
Expand Down Expand Up @@ -107,12 +156,8 @@ function MyCases() {
);

const renderListView = () => (
<ul
className={`case-list-global ${styles.caseList} ${
expanded || isExporting ? styles.expanded : ''
}`}
>
{visibleEvents.map(event => (
<ul className={`case-list-global ${styles.caseList} ${isExporting ? styles.expanded : ''}`}>
{displayEvents.map(event => (
<li
className={`case-list-item-global ${styles.caseListItem} ${
darkMode ? styles.caseListItemDark : ''
Expand All @@ -134,6 +179,9 @@ function MyCases() {
</div>
);

if (isLoading) return <p>Loading events...</p>;
if (fetchError) return <p>{fetchError}</p>;

return (
<div
className={`my-cases-global ${styles.myCasesPage} ${darkMode ? styles.myCasesPageDark : ''}`}
Expand Down Expand Up @@ -173,40 +221,58 @@ function MyCases() {
darkMode ? styles.filterDropdownDarkMode : ''
}`}
value={filter}
onChange={e => setFilter(e.target.value)}
onChange={handleFilterChange}
>
<option value="all">All Time</option>
<option value="today">Today</option>
<option value="thisWeek">This Week</option>
<option value="thisMonth">This Month</option>
</select>
</div>

<button
type="button"
className={`${styles.createNew} ${darkMode ? styles.createNewDarkMode : ''}`}
onClick={() => setIsCreateModalOpen(true)}
>
+ Create New
</button>
{filteredEvents.length > 10 && !isExporting && (
<button
type="button"
className={`more-btn-global ${styles.moreBtn}`}
onClick={() => setExpanded(!expanded)}
>
{expanded ? 'Show Less' : 'More'}
</button>

{!isExporting && view !== 'calendar' && (
<>
{hasMore && (
<button
type="button"
className={`more-btn-global ${styles.moreBtn}`}
onClick={() => setShowAll(true)}
>
More
</button>
)}
{showAll && filteredSorted.length > INITIAL_DISPLAY && (
<button
type="button"
className={`more-btn-global ${styles.moreBtn}`}
onClick={() => setShowAll(false)}
>
Less
</button>
)}
</>
)}
</div>
</header>
<main className={`${styles.content}`}>

<main className={styles.content}>
{view === 'card' && renderCardView()}
{view === 'list' && renderListView()}
{view === 'calendar' && renderCalendarView()}
</main>

<CreateEventModal
isOpen={isCreateModalOpen}
toggle={() => setIsCreateModalOpen(!isCreateModalOpen)}
onEventCreated={handleEventCreated}
/>
</div>
);
Expand Down
Loading