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
109 changes: 59 additions & 50 deletions src/components/CommunityPortal/Activities/ActivityList.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// Activity List Component
import { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useSelector, useStore } from 'react-redux';

Check warning on line 3 in src/components/CommunityPortal/Activities/ActivityList.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'useSelector'.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ0XgL0ELvrw8DEQXjCb&open=AZ0XgL0ELvrw8DEQXjCb&pullRequest=4568
import styles from './ActivityList.module.css';
import { mockActivities } from './mockActivities';
// import { useHistory } from 'react-router-dom';

function ActivityList() {
const darkMode = useSelector(state => state.theme.darkMode);
let darkMode = false;

try {
const store = useStore();
darkMode = store?.getState()?.theme?.darkMode ?? false;
} catch (e) {
darkMode = false;
}

Check warning on line 15 in src/components/CommunityPortal/Activities/ActivityList.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ0XgL0ELvrw8DEQXjCc&open=AZ0XgL0ELvrw8DEQXjCc&pullRequest=4568

const [activities, setActivities] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Expand All @@ -17,6 +24,7 @@
});
const [locationSuggestions, setLocationSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [sortOrder, setSortOrder] = useState('earliest');

useEffect(() => {
if (darkMode) {
Expand All @@ -35,22 +43,10 @@
try {
setLoading(true);
setError(null);

// TODO: Replace with actual API endpoint
// const response = await fetch('/api/activities');
// if (!response.ok) {
// throw new Error('Failed to fetch activities');
// }
// const data = await response.json();
// setActivities(data);

// Simulating API call - remove this when real API is available
// For now, we'll use mock data directly
throw new Error('API not implemented yet');
} catch (err) {
console.warn('Failed to fetch activities from API, using mock data:', err.message);
setError(err.message);
// Fallback to mock data
setActivities(mockActivities);
} finally {
setLoading(false);
Expand All @@ -60,43 +56,29 @@
fetchActivities();
}, []);

// Get location suggestions with STRICT prefix-based matching only
const getLocationSuggestions = input => {
if (!input.trim()) {
return [];
}
if (!input.trim()) return [];

// Get unique locations
const uniqueLocations = [...new Set(activities.map(a => a.location))];
const lowerInput = input.toLowerCase();

// ONLY return locations that START with the input (prefix matching)
const prefixMatches = uniqueLocations.filter(loc => loc.toLowerCase().startsWith(lowerInput));

// Limit to top 10 results
return prefixMatches.slice(0, 10);
return uniqueLocations.filter(loc => loc.toLowerCase().startsWith(lowerInput)).slice(0, 10);
};

const handleFilterChange = e => {
const { name, value } = e.target;
setFilter({ ...filter, [name]: value });

// Update location suggestions when location input changes
if (name === 'location') {
const suggestions = getLocationSuggestions(value);
setLocationSuggestions(suggestions);
setShowSuggestions(true);
}
};

const filteredActivities = activities.filter(activity => {
return (
(!filter.type || activity.type === filter.type) &&
(!filter.date || activity.date === filter.date) &&
(!filter.location ||
activity.location.toLowerCase().startsWith(filter.location.toLowerCase()))
);
});
const handleSortChange = e => {
setSortOrder(e.target.value);
};

const handleSuggestionClick = location => {
setFilter({ ...filter, location });
Expand All @@ -114,6 +96,21 @@
setShowSuggestions(false);
};

const filteredActivities = activities
.filter(activity => {
return (
(!filter.type || activity.type === filter.type) &&
(!filter.date || activity.date === filter.date) &&
(!filter.location ||
activity.location.toLowerCase().startsWith(filter.location.toLowerCase()))
);
})
.sort((a, b) => {
const dateA = new Date(a.date);
const dateB = new Date(b.date);
return sortOrder === 'earliest' ? dateA - dateB : dateB - dateA;
});

return (
<div className={`${styles.activityListContainer} ${darkMode ? 'bg-oxford-blue' : ''}`}>
<h1 className={`${styles.heading} ${darkMode ? 'text-light' : ''}`}>Activity List</h1>
Expand Down Expand Up @@ -142,6 +139,18 @@
/>
</label>

<label className={darkMode ? 'text-light' : ''}>
Sort By:
<select

Check warning on line 144 in src/components/CommunityPortal/Activities/ActivityList.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Ambiguous spacing before next element select

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ0XgL0ELvrw8DEQXjCd&open=AZ0XgL0ELvrw8DEQXjCd&pullRequest=4568
value={sortOrder}
onChange={handleSortChange}
className={darkMode ? styles.darkModeInput : ''}
>
<option value="earliest">Start Time: Earliest to Latest</option>
<option value="latest">Start Time: Latest to Earliest</option>
</select>
</label>

<label className={darkMode ? 'text-light' : ''}>
Location:
<div style={{ position: 'relative' }}>
Expand All @@ -157,29 +166,22 @@
setShowSuggestions(true);
}
}}
onBlur={() => {
// Delay to allow click on suggestion
setTimeout(() => setShowSuggestions(false), 200);
}}
onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
placeholder="Enter location"
autoComplete="off"
className={darkMode ? styles.darkModeInput : ''}
/>

{showSuggestions && locationSuggestions.length > 0 && (
<div
className={`${styles.suggestions} ${darkMode ? styles.darkSuggestions : ''}`}
role="listbox"
aria-label="Location suggestions"
>
<div className={`${styles.suggestions} ${darkMode ? styles.darkSuggestions : ''}`}>
{locationSuggestions.map((location, index) => (
<div
key={index}
className={styles.suggestionItem}
role="option"
role="button"
tabIndex={0}
aria-selected="false"
className={styles.suggestionItem}
onMouseDown={e => {
e.preventDefault(); // Prevent blur from firing
e.preventDefault();
handleSuggestionClick(location);
}}
onKeyDown={e => {
Expand All @@ -196,6 +198,7 @@
)}
</div>
</label>

<div className={styles.clearButtonWrapper}>
<button
type="button"
Expand All @@ -207,15 +210,21 @@
</button>
</div>
</div>

<div className={`${styles.activityList} ${darkMode ? styles.darkModeList : ''}`}>
{loading ? (
<p className={darkMode ? 'text-light' : ''}>Loading activities...</p>
) : filteredActivities.length > 0 ? (
<ul>
{filteredActivities.map(activity => (
<li key={activity.id} className={darkMode ? styles.darkModeItem : ''}>
<strong>{activity.name}</strong> - {activity.type} - {activity.date} -{' '}
{activity.location}
<li
key={activity.id}
className={`${styles.activityItem} ${darkMode ? styles.darkModeItem : ''}`}
>
<strong>{activity.name}</strong>
<span>
{activity.type} – {activity.date} – {activity.location}
</span>
</li>
))}
</ul>
Expand Down
57 changes: 12 additions & 45 deletions src/components/CommunityPortal/Activities/ActivityList.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Enhanced Aesthetic CSS for Activity List */
/* ===== ORIGINAL STYLES (UNCHANGED) ===== */

/* Activity List Container */
.activityListContainer {
Expand All @@ -20,12 +20,12 @@
font-weight: 600;
}

/* Dark mode heading - uses global text-light class */
/* Dark mode heading */
.activityListContainer:global(.bg-oxford-blue) .heading {
color: #ffffff !important;
}

/* Body background helper when Activity List is in dark mode */
/* Body background helper */
:global(.activity-list-dark-body),
:global(.activity-list-dark-body #root),
:global(.activity-list-dark-body .App) {
Expand All @@ -45,7 +45,6 @@
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

/* Dark mode filters - navy-blue theme matching Dashboard */
.darkModeFilters {
background: #1e2a3a;
box-shadow: none;
Expand All @@ -60,7 +59,8 @@
color: #1f2937;
}

.filters input {
.filters input,
.filters select {
padding: 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
Expand All @@ -71,15 +71,13 @@
color: #333;
}

/* Dark mode inputs - navy-blue theme (#1e2a3a bg, #3a4a5c borders) */
.darkModeInput {
background-color: #1e2a3a !important;
color: #ffffff !important;
border: 1px solid #3a4a5c !important;
color-scheme: dark;
}

/* Dark mode date input - style the calendar icon */
.darkModeInput[type="date"]::-webkit-calendar-picker-indicator {
filter: invert(1);
cursor: pointer;
Expand All @@ -91,18 +89,16 @@
box-shadow: 0 0 8px rgba(59, 130, 246, 0.3);
}

/* Dark mode input focus */
.darkModeInput:focus {
border-color: #5c9ce6 !important;
box-shadow: 0 0 8px rgba(92, 156, 230, 0.5) !important;
}

/* Dark mode input placeholder */
.darkModeInput::placeholder {
color: #a0a0a0;
}

/* Clear All button */
/* Clear button */
.clearButton {
padding: 10px 20px;
background-color: #ef4444;
Expand Down Expand Up @@ -135,7 +131,6 @@
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}

/* Dark mode activity list - remove border-radius, margin, box-shadow artifacts */
.darkModeList {
background: #1e2a3a;
box-shadow: none;
Expand All @@ -149,7 +144,7 @@
padding: 0;
}

.activityList li {
.activityItem {
display: flex;
flex-direction: column;
gap: 10px;
Expand All @@ -158,10 +153,8 @@
border: 1px solid #e5e7eb;
border-radius: 8px;
background-color: #f9fafb;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}

/* Dark mode list items - navy-blue theme */
.darkModeItem {
background-color: #1e2a3a !important;
border: 1px solid #3a4a5c !important;
Expand All @@ -174,27 +167,24 @@
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

/* Dark mode list item hover - button color theme (#3d4f62) */
.darkModeItem:hover {
background-color: #3d4f62 !important;
box-shadow: none !important;
}

.activityList li strong {
.activityItem strong {
color: #1e3a8a;
font-size: 1.2rem;
}

/* Dark mode strong text */
.darkModeItem strong {
color: #5c9ce6 !important;
}

.activityList li span {
.activityItem span {
color: #4b5563;
}

/* Dark mode span text */
.darkModeItem span {
color: #d1d5db !important;
}
Expand All @@ -206,7 +196,7 @@
margin: 20px 0;
}

/* Autocomplete Suggestions Dropdown */
/* Suggestions */
.suggestions {
position: absolute;
top: 100%;
Expand Down Expand Up @@ -242,29 +232,11 @@
background-color: #f3f4f6;
}

.suggestionItem:focus {
outline: 2px solid #3b82f6;
outline-offset: -2px;
background-color: #f3f4f6;
}

.darkSuggestions .suggestionItem:hover {
background-color: #334155;
}

.darkSuggestions .suggestionItem:focus {
outline-color: #60a5fa;
background-color: #334155;
}

.suggestionItem:not(:last-child) {
border-bottom: 1px solid #e5e7eb;
}

.darkSuggestions .suggestionItem:not(:last-child) {
border-bottom-color: #475569;
}

/* Responsive */
@media (max-width: 768px) {
.filters {
flex-direction: column;
Expand All @@ -279,9 +251,4 @@
.activityList {
padding: 20px;
}

.activityList li {
flex-direction: column;
padding: 10px;
}
}
}
Loading
Loading