From 283cee237b9ae482d1b56a9f8303df75d4975885 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Rolla Date: Sat, 11 Oct 2025 01:42:45 -0400 Subject: [PATCH 01/15] Final enhancements to the feedback page. --- .../Activities/activityId/Activity.jsx | 353 +++++++++------ .../Activities/activityId/Activity.module.css | 377 ++++++++++++++++ .../CommentSection/CommentSection.jsx | 35 +- .../CommentSection/CommentSection.module.css | 86 ++++ .../activityId/FeedBackSection/Feedback.jsx | 425 ++++++++++++++++++ .../FeedBackSection/Feedback.module.css | 273 +++++++++++ .../FeedBackSection/FeedbackModal.jsx | 59 +++ .../FeedBackSection/FeedbackModal.module.css | 136 ++++++ .../Activities/activityId/FeedbackData.js | 34 ++ src/routes.jsx | 5 + 10 files changed, 1647 insertions(+), 136 deletions(-) create mode 100644 src/components/CommunityPortal/Activities/activityId/Activity.module.css create mode 100644 src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.module.css create mode 100644 src/components/CommunityPortal/Activities/activityId/FeedBackSection/Feedback.jsx create mode 100644 src/components/CommunityPortal/Activities/activityId/FeedBackSection/Feedback.module.css create mode 100644 src/components/CommunityPortal/Activities/activityId/FeedBackSection/FeedbackModal.jsx create mode 100644 src/components/CommunityPortal/Activities/activityId/FeedBackSection/FeedbackModal.module.css create mode 100644 src/components/CommunityPortal/Activities/activityId/FeedbackData.js diff --git a/src/components/CommunityPortal/Activities/activityId/Activity.jsx b/src/components/CommunityPortal/Activities/activityId/Activity.jsx index 7b4ea58ae9..7e0270369c 100644 --- a/src/components/CommunityPortal/Activities/activityId/Activity.jsx +++ b/src/components/CommunityPortal/Activities/activityId/Activity.jsx @@ -1,6 +1,12 @@ import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; import CommentSection from './CommentSection/CommentSection'; -import './Activity.css'; +import Switch from '@mui/material/Switch'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Typography from '@mui/material/Typography'; +import Feedback from './FeedBackSection/Feedback'; +import { feedbackData } from './FeedbackData'; +import styles from './Activity.module.css'; const data = { eventName: 'Event Name', @@ -13,7 +19,7 @@ const data = { eventCapacity: '7/20', eventRating: 4, eventStatus: 'Activated', - eventStatusClass: 'status-active', + createdAt: '2025-09-20', eventDescription: 'this is activity comments. this is activity comments. this is activity comments. this is activity comments. this is activity comments.', eventParticipates: [{ name: 'Summer' }, { name: 'Jimmy' }], @@ -47,30 +53,85 @@ const data = { }; function Activity() { - const [tab, setTab] = useState('Description'); + const darkMode = useSelector(state => state.theme?.darkMode); + const [tab, setTab] = useState('Feedback'); const [event, setEvent] = useState(data); - - const handleTabClick = tabName => { - setTab(tabName); - }; + const [viewSelected, setViewSelected] = useState('Host'); + const [reviewsChecked, setReviewsChecked] = useState(true); + const [suggestionChecked, setSuggestionChecked] = useState(false); + const [feedbackList, setFeedbackList] = useState(feedbackData); + const [showFeedbackModal, setShowFeedbackModal] = useState(false); + const views = ['Host', 'Participant']; useEffect(() => { setEvent(data); - }, [data]); + }, []); return ( -
-
-
-
Participated
-
-

{event.eventType}

-

{event.eventName}

-

{event.eventLocation}

- - {event.eventLink} - -
+
+
+
+
Participated
+ +
+
+
+

+ {event.eventType} +

+

+ {event.eventName} +

+

+ {event.eventLocation} +

+ + {event.eventLink} + +
+ +
+ {views.map(sec => ( + + ))} +
+
+ +
Date
@@ -89,18 +150,18 @@ function Activity() {
Capacity
- {event.eventCapacity} + {event.eventCapacity}
Overall Rating
{Array.from({ length: event.eventRating }, (_, i) => ( - + ))} {Array.from({ length: 5 - event.eventRating }, (_, i) => ( - + ))} @@ -108,141 +169,177 @@ function Activity() {
Status
- {event.eventStatus} + {event.eventStatus}
+ + {viewSelected === 'Host' && ( +
+ setReviewsChecked(e.target.checked)} + color="primary" + /> + } + label={ + + Allow Reviews + + } + /> +
+ setSuggestionChecked(e.target.checked)} + color="primary" + /> + } + label={ + + Suggestions Only + + } + /> +
+ )}
-
- - -
+ + {viewSelected !== 'Host' && ( +
+ +
+ )}
-
-
September 2024
- + +
+
September 2024
+
- - - - - - - + {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => ( + + ))} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {[0, 1, 2, 3, 4].map(row => ( + + {Array.from({ length: 7 }).map((_, col) => { + const day = row * 7 + col + 1; + return ( + + ); + })} + + ))}
SMTWTFS{day}
1234567
891011121314
15161718192021
22232425262728
2930
+ {day <= 31 ? day : ''} +
-
- - - - +
+ {['Description', 'Participates', 'Comments', 'FAQs', 'Feedback'].map(name => ( + + ))}
+ {tab === 'Description' && ( -
+

{event.eventDescription}

)} + {tab === 'Participates' && ( -
- {event.eventParticipates.map(participant => ( -
- 0.5 ? 'purple' : 'blue'}`}> - {participant.name[0]} +
+ {event.eventParticipates.map((p, i) => ( +
+ 0.5 ? styles.purple : styles.blue + }`} + > + {p.name[0]} -
{participant.name}
+
{p.name}
))}
)} + {tab === 'FAQs' && ( -
+
{event.eventFAQs.map(faq => ( -
-

{faq.question}

-

{faq.answer}

+
+

+ {faq.question} +

+

+ {faq.answer} +

))}
)} - {tab === 'Comments' && } + + {tab === 'Comments' && ( + + )} + + {tab === 'Feedback' && viewSelected === 'Host' && ( + + )} + + {tab === 'Feedback' && viewSelected !== 'Host' && ( + + )}
); diff --git a/src/components/CommunityPortal/Activities/activityId/Activity.module.css b/src/components/CommunityPortal/Activities/activityId/Activity.module.css new file mode 100644 index 0000000000..4bab0d922d --- /dev/null +++ b/src/components/CommunityPortal/Activities/activityId/Activity.module.css @@ -0,0 +1,377 @@ +.activityContainer { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + background-color: #f9f9f9; + color: #111; + height: 100%; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.activityContainerDark { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + background-color: #1b2a41; + color: white; + height: 100%; + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Event Card */ +.activityEventCard { + border: 1px solid #ccc; + border-radius: 10px; + padding: 16px; + font-family: Arial, sans-serif; + background-color: #fff; + max-width: 1100px; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.activityEventCardDark { + border: 1px solid #ccc; + border-radius: 10px; + padding: 16px; + font-family: Arial, sans-serif; + background-color: #1b2a41; + max-width: 1100px; + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Header */ +.activityEventHeader { + display: flex; + gap: 20px; +} + +.titlesAndViews { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.viewToggler { + padding: 20px; +} + +.viewToggler button { + color: #000; +} + +.viewTogglerDark button { + color: #fff; +} + +/* Image Placeholder */ +.activityEventImage { + width: 180px; + height: 180px; + background-color: #e0e0e0; + text-align: center; + line-height: 180px; + font-weight: bold; + color: #555; + border-radius: 8px; +} + +/* Details */ +.activityEventDetails { + flex-grow: 2; +} + +.activityEventDetailsDark { + flex-grow: 2; + color: #ffffff; +} + +.activityEventType { + font-size: 12px; + color: gray; +} + +.activityEventTypeDark { + font-size: 12px; + color: rgb(255, 255, 255); +} + +.activityEventTitle { + margin: 5px 0; +} + +.activityEventTitleDark { + margin: 5px 0; + color: #ffffff; +} + +.switchToggle { + color: #000; +} + +.switchToggleDark { + color: #fff; +} + +.activityEventLocation { + font-size: 14px; +} + +.activityEventLocationDark { + font-size: 14px; + color: #fff +} + +.activityEventLink { + color: #1a73e8; + text-decoration: none; +} + +/* Info Rows */ +.activityEventInfo { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-top: 10px; +} + +.activityEventInfoDark strong { + color: #ffffff; +} + +.activityEventInfo > div { + min-width: 120px; +} + +.activityCapacity { + color: red; +} + +.activityStatusActive { + color: green; + font-weight: bold; +} + +/* Buttons */ +.activityEventButtons { + margin-top: 10px; +} + +.contactBtn { + padding: 5px 12px; + margin-right: 10px; + border: none; + border-radius: 4px; + background-color: #dedede; + cursor: pointer; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.contactBtn:hover { + background-color: #ccc; +} +/* Calendar */ +.activityCalendarBox { + min-width: 220px; + text-align: center; +} + +.activityCalendarHeader { + font-weight: bold; + margin-bottom: 5px; +} + +.activityCalendarTable { + width: 100%; + border-collapse: collapse; +} + +.activityCalendarTable th, +.activityCalendarTable td { + width: 14.2%; + padding: 4px; + border: 1px solid #ddd; +} +.activityCalendarTableDark th { + background-color: #212529; +} + +.activityActiveDate { + background-color: #f48fb1; + border-radius: 50%; + color: #fff; +} + +/* Tabs */ +.activityEventTabs { + display: flex; + gap: 20px; + border-top: 1px solid #ddd; + margin-top: 20px; + padding-top: 10px; +} + +.activityEventTabsDark button { + color: white; +} + +.activityTab { + padding: 6px 12px; + cursor: pointer; + transition: color 0.2s, border-color 0.2s; +} + +.activityTabActive { + border-bottom: 2px solid #000; + font-weight: bold; +} + +/* Participants */ +.activityParticipatesSection { + margin-top: 20px; + display: flex; + flex-direction: row; + gap: 20px; +} + +.activityParticipant { + display: flex; + gap: 5px; +} + +/* FAQs */ +.activityFaqsSection { + margin-top: 20px; +} + +.activityFaqText { + color: #555; +} + +.activityFaqDarkText { + color: #ccc; +} + +.activityFaqTitle { + color: #000; +} + +.activityFaqDarkTitle { + color: #fff; +} + +.activityEventDescription p { + color: #030303; + padding: 10px; +} + +.activityEventDescriptionDark p { + color: #fff; + padding: 10px; +} + +/* Icons */ +.activityIcon { + width: 30px; + height: 30px; + border-radius: 50%; + color: #fff; + text-align: center; + line-height: 30px; + font-weight: bold; +} + +/* Responsive */ +@media (max-width: 800px) { + .activityEventCard { + max-width: 98vw; + padding: 8px; + } + + .activityEventHeader { + flex-direction: column; + gap: 10px; + align-items: stretch; + } + + .activityEventImage { + width: 120px; + height: 160px; + line-height: 120px; + font-size: 18px; + margin: 10px auto; + } + + .activityCalendarBox { + min-width: unset; + width: 100%; + margin-top: 10px; + } + + .activityEventDetails { + width: 100%; + } + + .activityEventInfo { + flex-direction: column; + gap: 8px; + } + + .activityEventTabs { + justify-content: center; + gap: 10px; + flex-wrap: wrap; + font-size: 15px; + } + + .activityParticipatesSection { + flex-direction: column; + gap: 10px; + } +} + +@media (max-width: 500px) { + .activityEventCard { + padding: 4px; + font-size: 14px; + } + + .activityEventTitle { + font-size: 18px; + } + + .activityEventImage { + height: 180px; + line-height: 80px; + font-size: 15px; + } + + .activityCalendarHeader { + font-size: 14px; + } + + .activityCalendarTable th, + .activityCalendarTable td { + padding: 2px; + font-size: 12px; + } + + .activityIcon { + width: 24px; + height: 24px; + line-height: 24px; + font-size: 13px; + } + + .activityEventTabs { + font-size: 13px; + justify-content: space-evenly; + gap: 0; + } + + .activityTab { + padding: 6px 6px; + } +} diff --git a/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.jsx b/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.jsx index e152a16f82..7ad7e7cb45 100644 --- a/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.jsx +++ b/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.jsx @@ -1,19 +1,38 @@ -import './CommentSection.css'; +import styles from './CommentSection.module.css'; -function CommentSection({ comments }) { +function CommentSection({ comments, darkMode }) { return (
-
+
{comments.map(comment => ( -
-
- 0.5 ? 'purple' : 'blue'}`}> +
+
+ 0.5 ? styles.purple : styles.blue + }`} + > {comment.name[0]}
-
+
{comment.comment} -
+
{comment.name} - {comment.time}
diff --git a/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.module.css b/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.module.css new file mode 100644 index 0000000000..1fb2d97923 --- /dev/null +++ b/src/components/CommunityPortal/Activities/activityId/CommentSection/CommentSection.module.css @@ -0,0 +1,86 @@ +.activityCommentFooter { + font-size: 12px; + color: gray; + margin-top: 5px; + transition: color 0.3s ease; +} + +.activityCommentFooterDark { + color: #aaa; +} + +.activityComment { + display: flex; + align-items: flex-start; + gap: 10px; + transition: background-color 0.3s ease; +} + +.activityCommentDark { + background-color: transparent; +} + +.activityCommentsSection { + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 20px; + transition: all 0.3s ease; +} + +.activityCommentsSectionDark { + background-color: transparent; +} + +.activityCommentUser { + display: flex; + gap: 5px; +} + +.activityCommentText { + flex: 1; + background-color: #f4f4f4; + padding: 10px; + border-radius: 6px; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.activityCommentTextDark { + background-color: #3a506b; + color: #ffffff; +} + +.activityIcon { + width: 30px; + height: 30px; + border-radius: 50%; + color: #fff; + text-align: center; + line-height: 30px; + font-weight: bold; + transition: transform 0.2s ease; +} + +.activityIcon:hover { + transform: scale(1.1); +} + +.purple { + background-color: purple; +} + +.blue { + background-color: #3366ff; +} + +@media (max-width: 800px) { + .activityComment { + flex-direction: column; + gap: 5px; + } + + .activityCommentsSection { + gap: 10px; + padding: 15px; + } +} diff --git a/src/components/CommunityPortal/Activities/activityId/FeedBackSection/Feedback.jsx b/src/components/CommunityPortal/Activities/activityId/FeedBackSection/Feedback.jsx new file mode 100644 index 0000000000..28e4690940 --- /dev/null +++ b/src/components/CommunityPortal/Activities/activityId/FeedBackSection/Feedback.jsx @@ -0,0 +1,425 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import styles from './Feedback.module.css'; +import { FaSearch } from 'react-icons/fa'; +import { MdArrowUpward, MdArrowDownward } from 'react-icons/md'; +import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; +import FeedbackModal from './FeedbackModal'; + +const nowISO = () => new Date().toISOString(); + +function Feedback({ + reviewsEnabled = true, + suggestionsOnly = false, + isHost = false, + eventCreatedAt = null, + showModal = false, + setShowModal = null, + feedbackList, + setFeedbackList, +}) { + // local list (in real app you'd fetch) + const [searchTerm, setSearchTerm] = useState(''); + const [filterBy, setFilterBy] = useState('date'); + const [sortOrder, setSortOrder] = useState('desc'); + const [visibilityFilter, setVisibilityFilter] = useState('all'); + const [showSuggestionsOnly, setShowSuggestionsOnly] = useState(false); + + // modal form state (participant) + const [modalOpen, setModalOpen] = useState(showModal); + const [modalRating, setModalRating] = useState(0); + const [modalComment, setModalComment] = useState(''); + const [modalSuggestionText, setModalSuggestionText] = useState(''); + const [modalPrivate, setModalPrivate] = useState(false); + const [visibleCount, setVisibleCount] = useState(2); + + // reflect incoming modal prop + useEffect(() => { + if (typeof showModal === 'boolean') setModalOpen(showModal); + }, [showModal]); + + useEffect(() => { + // If parent gave setShowModal, keep synchronized + if (setShowModal) { + setShowModal(modalOpen); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [modalOpen]); + + useEffect(() => { + // Placeholder for API call to fetch feedback data + // For now we use dummyFeedback above. + }, []); + + // dark mode for styling + const darkMode = useSelector(state => state.theme?.darkMode); + + // helper: determine if event was created within one month + const eventWithinFirstMonth = useMemo(() => { + if (!eventCreatedAt) return false; + const created = new Date(eventCreatedAt); + const oneMonthLater = new Date(created); + oneMonthLater.setMonth(oneMonthLater.getMonth() + 1); + return new Date() <= oneMonthLater; + }, [eventCreatedAt]); + + const handleSearch = e => setSearchTerm(e.target.value); + const handleFilterChange = e => setFilterBy(e.target.value); + const handleSortChange = () => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + + const renderStars = feedback => + Array.from({ length: 5 }, (_, i) => ( + + {i < (feedback.rating || 0) ? : } + + )); + + // Filtering & sorting for host view + const filteredFeedback = feedbackList + .filter(fb => { + // visibility filter + if (visibilityFilter !== 'all' && fb.visibility !== visibilityFilter) return false; + // search match + const q = searchTerm.trim().toLowerCase(); + if (!q) return true; + return ( + (fb.comment || '').toLowerCase().includes(q) || + (fb.name || '').toLowerCase().includes(q) || + (fb.rating !== null && String(fb.rating).includes(q)) + ); + }) + .sort((a, b) => { + if (filterBy === 'date') { + return sortOrder === 'asc' + ? new Date(a.date) - new Date(b.date) + : new Date(b.date) - new Date(a.date); + } + if (filterBy === 'rating') { + return sortOrder === 'asc' + ? (a.rating || 0) - (b.rating || 0) + : (b.rating || 0) - (a.rating || 0); + } + return 0; + }); + + // Participant submit handlers (local only) + const handleSubmitFeedback = () => { + if (!reviewsEnabled && !suggestionsOnly) return; // can't submit + const isSuggestion = suggestionsOnly; + const visibility = eventWithinFirstMonth ? 'host-only' : modalPrivate ? 'host-only' : 'public'; + const newItem = { + id: feedbackList.length + 1, + name: 'You', + rating: isSuggestion ? null : modalRating, + comment: isSuggestion ? modalSuggestionText || modalComment : modalComment, + date: nowISO().slice(0, 10), + visibility: isSuggestion ? 'suggestion' : visibility, + }; + setFeedbackList(prev => [newItem, ...prev]); + // reset modal + setModalComment(''); + setModalSuggestionText(''); + setModalRating(5); + setModalPrivate(false); + setModalOpen(false); + if (setShowModal) setShowModal(false); + }; + + const handleOpenModal = () => { + setModalOpen(true); + if (setShowModal) setShowModal(true); + }; + + // Host-only and suggestion lists + const suggestionList = feedbackList.filter(fb => fb.visibility === 'suggestion'); + + return ( +
+ {/* Host view (management) */} + {isHost && ( + <> +
+
+ + +
+ +
+ + + + + +
+
+ + {/* Toggle between Reviews and Suggestions view */} +
+ + +
+ + {showSuggestionsOnly ? ( +
+ {suggestionList.length === 0 ? ( +
No suggestions yet.
+ ) : ( + suggestionList.map(s => ( +
+ User +
+
+ {s.name} + {s.date} +
+

+ {s.comment} +

+
+
+ )) + )} +
+ ) : ( +
+ {filteredFeedback.length === 0 ? ( +
No feedback matches your filters.
+ ) : ( + filteredFeedback.slice(0, visibleCount).map(feedback => ( +
+ User +
+
+ {feedback.name} + {feedback.date} + {/* Visibility badge */} + + {feedback.visibility === 'host-only' + ? 'Private' + : feedback.visibility === 'suggestion' + ? 'Suggestion' + : 'Public'} + +
+ + {/* rating */} + {feedback.rating !== null && ( +
{renderStars(feedback)}
+ )} + +

+ {feedback.comment} +

+
+
+ )) + )} +
+ )} + {visibleCount < feedbackList.length && ( +
+ +
+ )} + + )} + + {/* Participant view / modal */} + {!isHost && ( + <> + {/* Notice if reviews disabled */} + {!reviewsEnabled && !suggestionsOnly && ( +
Reviews are currently disabled for this event.
+ )} + + {/* Button to open modal if parent didn't provide one */} + {!modalOpen && (reviewsEnabled || suggestionsOnly) && ( +
+ +
+ )} + + {/* Modal-like simple panel (replace with your modal component if you have one) */} + {modalOpen && ( + { + setModalOpen(false); + if (setShowModal) setShowModal(false); + }} + onSubmit={handleSubmitFeedback} + show={modalOpen} + importantLabel={ + eventWithinFirstMonth + ? 'Your feedback is only visible to the host for the first month.' + : null + } + disableSubmit={ + (!suggestionsOnly && !modalComment && !modalRating) || + (suggestionsOnly && !modalSuggestionText) || + (!reviewsEnabled && !suggestionsOnly) + } + > + {suggestionsOnly ? ( +
+ +