diff --git a/src/components/CommunityPortal/CPDashboard.jsx b/src/components/CommunityPortal/CPDashboard.jsx index 1e13f76809..dd01ea1e0e 100644 --- a/src/components/CommunityPortal/CPDashboard.jsx +++ b/src/components/CommunityPortal/CPDashboard.jsx @@ -1,13 +1,40 @@ import { useState, useEffect } from 'react'; import { Container, Row, Col, Card, CardBody, Button, Input } from 'reactstrap'; +import { FaCalendarAlt, FaMapMarkerAlt, FaUserAlt, FaSearch, FaTimes } from 'react-icons/fa'; import styles from './CPDashboard.module.css'; -import { FaCalendarAlt, FaMapMarkerAlt, FaUserAlt } from 'react-icons/fa'; import { ENDPOINTS } from '../../utils/URL'; import axios from 'axios'; +const FixedRatioImage = ({ src, alt, fallback }) => ( +
+ {alt} { + if (e.currentTarget.src !== fallback) e.currentTarget.src = fallback; + }} + style={{ + width: '100%', + height: '100%', + objectFit: 'cover', + display: 'block', + }} + /> +
+); + export function CPDashboard() { const [events, setEvents] = useState([]); - const [search, setSearch] = useState(''); + const [searchInput, setSearchInput] = useState(''); + const [searchQuery, setSearchQuery] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [pagination, setPagination] = useState({ @@ -20,32 +47,6 @@ export function CPDashboard() { const FALLBACK_IMG = 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=600&q=60'; - const FixedRatioImage = ({ src, alt, fallback }) => ( -
- {alt} { - if (e.currentTarget.src !== fallback) e.currentTarget.src = fallback; - }} - style={{ - width: '100%', - height: '100%', - objectFit: 'cover', - display: 'block', - }} - /> -
- ); - useEffect(() => { const fetchEvents = async () => { setIsLoading(true); @@ -53,7 +54,11 @@ export function CPDashboard() { try { const response = await axios.get(ENDPOINTS.EVENTS); console.log('Fetched events:', response.data.events); - setEvents(response.data.events); + setEvents(response.data.events || []); + setPagination(prev => ({ + ...prev, + total: response.data.events?.length || 0, + })); } catch (err) { console.error('Here', err); setError('Failed to load events'); @@ -65,6 +70,20 @@ export function CPDashboard() { fetchEvents(); }, []); + const handleSearchClick = () => { + const trimmed = searchInput.trim(); + setSearchQuery(trimmed); + setPagination(prev => ({ ...prev, currentPage: 1 })); + }; + + const handleSearchKeyDown = e => { + if (e.key === 'Enter') { + const trimmed = searchInput.trim(); + setSearchQuery(trimmed); + setPagination(prev => ({ ...prev, currentPage: 1 })); + } + }; + const formatDate = dateStr => { if (!dateStr) return 'Date TBD'; const date = new Date(dateStr); @@ -77,30 +96,84 @@ export function CPDashboard() { }); }; - const filteredEvents = events.filter(event => - event.title?.toLowerCase().includes(search.toLowerCase()), - ); + const filteredEvents = events.filter(event => { + if (!searchQuery) return true; + const term = searchQuery.toLowerCase(); + + return ( + event.title?.toLowerCase().includes(term) || + event.location?.toLowerCase().includes(term) || + event.organizer?.toLowerCase().includes(term) + ); + }); - const totalPages = Math.ceil(filteredEvents.length / pagination.limit); + const totalPages = Math.ceil(filteredEvents.length / pagination.limit) || 1; const displayedEvents = filteredEvents.slice( (pagination.currentPage - 1) * pagination.limit, pagination.currentPage * pagination.limit, ); + const goToPage = newPage => { + if (newPage < 1 || newPage > totalPages) return; + setPagination(prev => ({ ...prev, currentPage: newPage })); + }; + + if (isLoading) { + return ( + +

Loading events...

+
+ ); + } + + if (error) { + return ( + +

{error}

+
+ ); + } + return (

All Events

+
setSearch(e.target.value)} - className={styles['dashboard-search']} + value={searchInput} + onChange={e => setSearchInput(e.target.value)} + onKeyDown={handleSearchKeyDown} + className={styles['dashboard-search-input']} /> + + {searchInput && ( + + )} + +
@@ -122,24 +195,28 @@ export function CPDashboard() { +
Online Only
+
+
+
@@ -152,22 +229,23 @@ export function CPDashboard() {

Events

+ - {events.length > 0 ? ( - events.map(event => ( + {displayedEvents.length > 0 ? ( + displayedEvents.map(event => (
- {event.title}
{event.title}

- {event.date} + {formatDate(event.date)}

{event.location} @@ -183,6 +261,30 @@ export function CPDashboard() {

No events available
)}
+ + {/* Simple pagination controls if needed */} + {totalPages > 1 && ( +
+ + + Page {pagination.currentPage} of {totalPages} + + +
+ )} +
diff --git a/src/components/CommunityPortal/CPDashboard.module.css b/src/components/CommunityPortal/CPDashboard.module.css index fcde3d24aa..92bc8be79b 100644 --- a/src/components/CommunityPortal/CPDashboard.module.css +++ b/src/components/CommunityPortal/CPDashboard.module.css @@ -1,4 +1,4 @@ -body { +:global(body) { font-family: 'Poppins', sans-serif; background: #fff; margin: 0; @@ -6,8 +6,7 @@ body { } .dashboard-container { - padding: 20px 15px; - max-width: 1400px; + padding: 40px 10px; margin: 0 auto; background: #ffffff; border-radius: 16px; @@ -15,7 +14,7 @@ body { font-size: 16px; line-height: 1.6; letter-spacing: 0.2px; - width: min(95%, 1400px); + width: 100%; } .dashboard-header { @@ -23,10 +22,41 @@ body { justify-content: space-between; align-items: center; margin-bottom: 30px; - padding: 20px; + padding: 16px 24px; background: linear-gradient(120deg, #ffffff, #f8f9fa); border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +} + +.dashboard-content{ + width: 100%; + padding-left: 20px; + margin: 0; +} + +.dashboard-search { + border: none; + padding: 8px 16px; + min-width: 260px; + border-radius: 999px 0 0 999px; + outline: none; +} + +.dashboard-search:focus { + box-shadow: none; +} + +.dashboard-search-button { + border-radius: 0 999px 999px 0; + border: none; + display: flex; + align-items: center; + justify-content: center; + padding: 0 18px; +} + +.dashboard-search-icon { + font-size: 1rem; } .dashboard-header h1 { @@ -45,6 +75,57 @@ body { transition: all 0.3s ease; } +/* pill input */ +.dashboard-search-input { + padding: 8px 90px 8px 16px; /* extra right padding for icons */ + border-radius: 999px; + border: 2px solid #1b3c55; + min-width: 260px; + outline: none; +} + +/* clear X button */ +.dashboard-clear-btn { + position: absolute; + right: 35px; + top: 45%; + transform: translateY(-50%); + border: none; + background: transparent; + cursor: pointer; + font-size: 0.9rem; + color: #1b3c55; +} + +/* blue circular search button inside the bar */ +.dashboard-search-icon-btn { + position: absolute; + right: 1%; + top: 48%; + transform: translateY(-50%); + width: 34px; + height: 34px; + border-radius: 50%; + cursor: pointer; + background: transparent; /* 🔹 icon background now white */ + display: flex; + align-items: center; + justify-content: center; + color: #1b3c55; /* blue magnifying-glass icon */ + font-size: 0.9rem; +} + + +.dashboard-search-container { + position: relative; + display: inline-flex; + align-items: center; + border-radius: 999px; + overflow: hidden; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.1); + background: #ffffff; +} + .dashboard-search-container input:focus { border-color: #34495e; box-shadow: 0 0 8px rgba(52, 73, 94, 0.3); @@ -52,8 +133,10 @@ body { } .dashboard-sidebar { - padding: 30px; - background: #f9f9f9; + padding: 20px; + display: flex; + justify-content: flex-start; + background: #ffffff; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } @@ -61,29 +144,29 @@ body { .filter-section h4 { font-size: 1.8rem; color: #2c3e50; - /* margin-bottom: 20px; */ font-weight: 600; } -.filter-section-divider { - height: fit-content; - margin: 10px 10px;; - border-radius: 1px; - display: flex; - flex-direction: column; -} - -.filter-item{ - padding: 8px 15px; +.filter-item input:not([type="checkbox"]):not([type="radio"]), +.filter-item select { + padding: 12px 15px; margin-top: 10px; - /* border: 1px solid #ddd; */ border-radius: 8px; width: 100%; + height: auto; font-size: 1rem; - /* background: #ffffff; */ transition: all 0.3s ease; } +.filter-item input[type="radio"], +.filter-item input[type="checkbox"] { + display: inline-block; /* <– keeps input on same line as text */ + width: auto; + padding: 0; + margin: 5px 0px 0px 120px; /* top margin to line up under "Dates" */ + vertical-align: middle; +} + .filter-item input:focus, .filter-item select:focus { border-color: #2c3e50; @@ -92,11 +175,7 @@ body { } .dashboard-main { - margin-top: 25px; - display: flex; - flex-wrap: wrap; - gap: 20px; - background-color: #e5e5e5; + width: 65%; padding: 30px; border-radius: 12px; box-shadow: 0 4px 12px #0000001a; @@ -119,6 +198,12 @@ body { transition: transform 0.3s ease, box-shadow 0.3s ease; } +.event-card-col { + display: flex; +} + + + .event-card:hover { transform: scale(1.05); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); @@ -171,6 +256,11 @@ body { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } -.date-filter { - margin-top: 15px; +/* Hide browser's built-in clear icon for search inputs */ +.dashboard-search-input::-webkit-search-cancel-button, +.dashboard-search-input::-webkit-search-decoration, +.dashboard-search-input::-webkit-search-results-button, +.dashboard-search-input::-webkit-search-results-decoration { + -webkit-appearance: none; + appearance: none; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4937082d4b..6530f96a47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2074,12 +2074,21 @@ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" "@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" @@ -5178,6 +5187,13 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, d dependencies: ms "^2.1.3" +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -7355,20 +7371,15 @@ jest-message-util@30.2.0: slash "^3.0.0" stack-utils "^2.0.6" -jest-message-util@^29.7.0: +jest-matcher-utils@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" jest-mock@30.2.0: version "30.2.0" @@ -7379,14 +7390,20 @@ jest-mock@30.2.0: "@types/node" "*" jest-util "30.2.0" -jest-mock@^29.7.0: +jest-message-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== dependencies: + "@babel/code-frame" "^7.12.13" "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" jest-pnp-resolver@^1.2.3: version "1.2.3" @@ -9876,6 +9893,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3: hash-base "^3.1.2" inherits "^2.0.4" +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + robust-predicates@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" @@ -10244,6 +10269,14 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"