Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
text-align: center;
border-radius: 4px;
padding: 10px;
display: flex;
align-items: center;
}

.caution-icon {
display: inline-block;
width: 16px;
height: 16px;
background-size: cover;
margin-right: 8px;
}

.question-responses-container {
Expand All @@ -48,4 +58,4 @@
height: 200px;
margin-bottom: 2em;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect, useState } from 'react';
import { styled } from '@mui/material/styles';
import { Autocomplete, Avatar, Button, Checkbox, Chip, TextField, Typography } from '@mui/material';
import { Autocomplete, Avatar, Button, Checkbox, Chip, TextField, Typography, Box } from '@mui/material';
import FeedbackResponseCard from './feedback_response_card/FeedbackResponseCard';
import { getQuestionsAndAnswers } from '../../api/feedbackanswer';
import { getFeedbackRequestById } from '../../api/feedback';
Expand All @@ -20,7 +20,6 @@ import { getAvatarURL } from '../../api/api';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import SkeletonLoader from '../skeleton_loader/SkeletonLoader';

import './ViewFeedbackResponses.css';

const PREFIX = 'MuiCardContent';
Expand Down Expand Up @@ -50,15 +49,13 @@ const Root = styled('div')({
marginRight: '3em',
width: '350px',
['@media (max-width: 800px)']: {
// eslint-disable-line no-useless-computed-key
marginRight: 0,
width: '100%'
}
},
[`& .${classes.responderField}`]: {
minWidth: '500px',
['@media (max-width: 800px)']: {
// eslint-disable-line no-useless-computed-key
minWidth: 0,
width: '100%'
}
Expand All @@ -75,8 +72,7 @@ const ViewFeedbackResponses = () => {
const [searchText, setSearchText] = useState('');
const [responderOptions, setResponderOptions] = useState([]);
const [selectedResponders, setSelectedResponders] = useState([]);
const [filteredQuestionsAndAnswers, setFilteredQuestionsAndAnswers] =
useState([]);
const [filteredQuestionsAndAnswers, setFilteredQuestionsAndAnswers] = useState([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
Expand All @@ -90,7 +86,29 @@ const ViewFeedbackResponses = () => {
? requests
: [requests]
: [];
return await getQuestionsAndAnswers(requests, cookie);
const res = await getQuestionsAndAnswers(requests, cookie);

if (res) {
const sanitizedResponses = res.map(question => ({
...question,
answers: question.answers.map(answer => ({
...answer,
answer: isEmptyOrWhitespace(answer.answer) ? ' ⚠️ No response submitted' : String(answer.answer),
}))
}));

sanitizedResponses.sort((a, b) => a.questionNumber - b.questionNumber);
setQuestionsAndAnswers(sanitizedResponses);

} else {
window.snackDispatch({
type: UPDATE_TOAST,
payload: {
severity: 'error',
toast: 'Failed to retrieve questions and answers'
}
});
}
}

if (!csrf || !query.request) {
Expand Down Expand Up @@ -120,59 +138,47 @@ const ViewFeedbackResponses = () => {
});
}
});
retrieveQuestionsAndAnswers(query.request, csrf).then(res => {
if (res) {
res.sort((a, b) => a.questionNumber - b.questionNumber);
setQuestionsAndAnswers(res);
} else {
window.snackDispatch({
type: UPDATE_TOAST,
payload: {
severity: 'error',
toast: 'Failed to retrieve questions and answers'
}
});
}
});
retrieveQuestionsAndAnswers(query.request, csrf);
}, [csrf, query.request]);

// Sets the options for filtering by responders
useEffect(() => {
let allResponders = [];
questionsAndAnswers.forEach(({ answers }) => {
const responders = answers.map(answer => answer.responder);
allResponders.push(...responders);
});
allResponders = [...new Set(allResponders)]; // Remove duplicate responders
allResponders = [...new Set(allResponders)];
setResponderOptions(allResponders);
}, [state, questionsAndAnswers]);

// Populate all responders as selected by default
useEffect(() => {
setSelectedResponders(responderOptions);
}, [responderOptions]);

useEffect(() => {
let responsesToDisplay = [...questionsAndAnswers];

responsesToDisplay = responsesToDisplay.map(response => {
// Filter based on selected responders
let filteredAnswers = response.answers.filter(answer =>
selectedResponders.includes(answer.responder)
);

if (filteredAnswers.length === 0) {
filteredAnswers = [{ answer: 'No input due to recipient filter', responder: null }];
}

if (searchText.trim()) {
// Filter based on search text
filteredAnswers = filteredAnswers.filter(
({ answer }) =>
answer &&
answer.toLowerCase().includes(searchText.trim().toLowerCase())
);
}

return { ...response, answers: filteredAnswers };
});

setFilteredQuestionsAndAnswers(responsesToDisplay);
}, [searchText, selectedResponders]); // eslint-disable-line react-hooks/exhaustive-deps
}, [searchText, selectedResponders]);

useEffect(() => {
if (isLoading && filteredQuestionsAndAnswers.length > 0) {
Expand All @@ -184,7 +190,14 @@ const ViewFeedbackResponses = () => {
setSelectedResponders(responderOptions);
};


const isEmptyOrWhitespace = (text) => {
return typeof text !== 'string' || !text.trim();
};


return selectCanViewFeedbackAnswerPermission(state) ? (

<Root className="view-feedback-responses-page">
<Typography
variant="h4"
Expand Down Expand Up @@ -283,6 +296,9 @@ const ViewFeedbackResponses = () => {
))}
{!isLoading &&
filteredQuestionsAndAnswers?.map(question => {
const questionText = question.question;
const hasResponses = question.answers.length > 0;

return (
<div
className="question-responses-container"
Expand All @@ -292,26 +308,21 @@ const ViewFeedbackResponses = () => {
className="question-text"
style={{ marginBottom: '0.5em', fontWeight: 'bold' }}
>
Q{question.questionNumber}: {question.question}
{questionText}
</Typography>
{question.answers.length === 0 && (
<div className="no-responses-found">
<Typography variant="body1" style={{ color: 'gray' }}>
No matching responses found
</Typography>
</div>
)}
{question.inputType !== 'NONE' &&
question.answers.length > 0 &&

{/* If the question has no answers or inputType is "NONE" */}
{!hasResponses || question.inputType === 'NONE' ? null : (
question.answers.map(answer => (
<FeedbackResponseCard
key={answer.id || answer.responder}
responderId={answer.responder}
answer={answer.answer || ''}
answer={isEmptyOrWhitespace(answer.answer) ? ' ⚠️ No response submitted' : String(answer.answer)}
inputType={question.inputType}
sentiment={answer.sentiment}
/>
))}
))
)}
</div>
);
})}
Expand All @@ -321,4 +332,4 @@ const ViewFeedbackResponses = () => {
);
};

export default ViewFeedbackResponses;
export default ViewFeedbackResponses;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import FeedbackAnswerInput from '../../feedback_answer_input/FeedbackAnswerInput

const propTypes = {
responderId: PropTypes.string.isRequired,
answer: PropTypes.string.isRequired,
answer: PropTypes.string, // Allow answer to be null or undefined
inputType: PropTypes.string.isRequired,
sentiment: PropTypes.number
};
Expand All @@ -21,6 +21,19 @@ const FeedbackResponseCard = props => {
const { state } = useContext(AppContext);
const userInfo = selectProfile(state, props.responderId);

const getFormattedAnswer = () => {
if (props.inputType === 'NONE') {
return null; // Return null to display nothing
}

// Return fallback if the answer is null, undefined, or empty
if (props.answer === null || props.answer === undefined || !props.answer.trim()) {
return '⚠️ No response submitted';
}

return props.answer;
};

return (
<Card className="response-card">
<CardContent className="response-card-content">
Expand All @@ -31,16 +44,18 @@ const FeedbackResponseCard = props => {
/>
<Typography className="responder-name">{userInfo?.name}</Typography>
</div>
<FeedbackAnswerInput
inputType={props.inputType}
readOnly
answer={props.answer}
/>
{props.inputType !== 'NONE' && (
<FeedbackAnswerInput
inputType={props.inputType}
readOnly
answer={getFormattedAnswer()} // Ensure the proper message is displayed
/>
)}
</CardContent>
</Card>
);
};

FeedbackResponseCard.propTypes = propTypes;

export default FeedbackResponseCard;
export default FeedbackResponseCard;
1 change: 0 additions & 1 deletion web-ui/src/components/volunteer/Organizations.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ const Organizations = ({ onlyMe = false }) => {
aria-label="Add Organization"
onClick={addOrganization} // Open the dialog to add an organization
>
{console.log("Add Organization button rendered")}
<AddCircleOutline />
</IconButton>
</div>
Expand Down