11import React , { useContext , useEffect , useState } from 'react' ;
22import { styled } from '@mui/material/styles' ;
3- import { Autocomplete , Avatar , Button , Checkbox , Chip , TextField , Typography } from '@mui/material' ;
3+ import { Autocomplete , Avatar , Button , Checkbox , Chip , TextField , Typography , Box } from '@mui/material' ;
44import FeedbackResponseCard from './feedback_response_card/FeedbackResponseCard' ;
55import { getQuestionsAndAnswers } from '../../api/feedbackanswer' ;
66import { getFeedbackRequestById } from '../../api/feedback' ;
@@ -20,7 +20,6 @@ import { getAvatarURL } from '../../api/api';
2020import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank' ;
2121import CheckBoxIcon from '@mui/icons-material/CheckBox' ;
2222import SkeletonLoader from '../skeleton_loader/SkeletonLoader' ;
23-
2423import './ViewFeedbackResponses.css' ;
2524
2625const PREFIX = 'MuiCardContent' ;
@@ -50,15 +49,13 @@ const Root = styled('div')({
5049 marginRight : '3em' ,
5150 width : '350px' ,
5251 [ '@media (max-width: 800px)' ] : {
53- // eslint-disable-line no-useless-computed-key
5452 marginRight : 0 ,
5553 width : '100%'
5654 }
5755 } ,
5856 [ `& .${ classes . responderField } ` ] : {
5957 minWidth : '500px' ,
6058 [ '@media (max-width: 800px)' ] : {
61- // eslint-disable-line no-useless-computed-key
6259 minWidth : 0 ,
6360 width : '100%'
6461 }
@@ -75,8 +72,7 @@ const ViewFeedbackResponses = () => {
7572 const [ searchText , setSearchText ] = useState ( '' ) ;
7673 const [ responderOptions , setResponderOptions ] = useState ( [ ] ) ;
7774 const [ selectedResponders , setSelectedResponders ] = useState ( [ ] ) ;
78- const [ filteredQuestionsAndAnswers , setFilteredQuestionsAndAnswers ] =
79- useState ( [ ] ) ;
75+ const [ filteredQuestionsAndAnswers , setFilteredQuestionsAndAnswers ] = useState ( [ ] ) ;
8076 const [ isLoading , setIsLoading ] = useState ( true ) ;
8177
8278 useEffect ( ( ) => {
@@ -90,7 +86,29 @@ const ViewFeedbackResponses = () => {
9086 ? requests
9187 : [ requests ]
9288 : [ ] ;
93- return await getQuestionsAndAnswers ( requests , cookie ) ;
89+ const res = await getQuestionsAndAnswers ( requests , cookie ) ;
90+
91+ if ( res ) {
92+ const sanitizedResponses = res . map ( question => ( {
93+ ...question ,
94+ answers : question . answers . map ( answer => ( {
95+ ...answer ,
96+ answer : isEmptyOrWhitespace ( answer . answer ) ? ' ⚠️ No response submitted' : String ( answer . answer ) ,
97+ } ) )
98+ } ) ) ;
99+
100+ sanitizedResponses . sort ( ( a , b ) => a . questionNumber - b . questionNumber ) ;
101+ setQuestionsAndAnswers ( sanitizedResponses ) ;
102+
103+ } else {
104+ window . snackDispatch ( {
105+ type : UPDATE_TOAST ,
106+ payload : {
107+ severity : 'error' ,
108+ toast : 'Failed to retrieve questions and answers'
109+ }
110+ } ) ;
111+ }
94112 }
95113
96114 if ( ! csrf || ! query . request ) {
@@ -120,59 +138,47 @@ const ViewFeedbackResponses = () => {
120138 } ) ;
121139 }
122140 } ) ;
123- retrieveQuestionsAndAnswers ( query . request , csrf ) . then ( res => {
124- if ( res ) {
125- res . sort ( ( a , b ) => a . questionNumber - b . questionNumber ) ;
126- setQuestionsAndAnswers ( res ) ;
127- } else {
128- window . snackDispatch ( {
129- type : UPDATE_TOAST ,
130- payload : {
131- severity : 'error' ,
132- toast : 'Failed to retrieve questions and answers'
133- }
134- } ) ;
135- }
136- } ) ;
141+ retrieveQuestionsAndAnswers ( query . request , csrf ) ;
137142 } , [ csrf , query . request ] ) ;
138143
139- // Sets the options for filtering by responders
140144 useEffect ( ( ) => {
141145 let allResponders = [ ] ;
142146 questionsAndAnswers . forEach ( ( { answers } ) => {
143147 const responders = answers . map ( answer => answer . responder ) ;
144148 allResponders . push ( ...responders ) ;
145149 } ) ;
146- allResponders = [ ...new Set ( allResponders ) ] ; // Remove duplicate responders
150+ allResponders = [ ...new Set ( allResponders ) ] ;
147151 setResponderOptions ( allResponders ) ;
148152 } , [ state , questionsAndAnswers ] ) ;
149153
150- // Populate all responders as selected by default
151154 useEffect ( ( ) => {
152155 setSelectedResponders ( responderOptions ) ;
153156 } , [ responderOptions ] ) ;
154157
155158 useEffect ( ( ) => {
156159 let responsesToDisplay = [ ...questionsAndAnswers ] ;
157-
158160 responsesToDisplay = responsesToDisplay . map ( response => {
159- // Filter based on selected responders
160161 let filteredAnswers = response . answers . filter ( answer =>
161162 selectedResponders . includes ( answer . responder )
162163 ) ;
164+
165+ if ( filteredAnswers . length === 0 ) {
166+ filteredAnswers = [ { answer : 'No input due to recipient filter' , responder : null } ] ;
167+ }
168+
163169 if ( searchText . trim ( ) ) {
164- // Filter based on search text
165170 filteredAnswers = filteredAnswers . filter (
166171 ( { answer } ) =>
167172 answer &&
168173 answer . toLowerCase ( ) . includes ( searchText . trim ( ) . toLowerCase ( ) )
169174 ) ;
170175 }
176+
171177 return { ...response , answers : filteredAnswers } ;
172178 } ) ;
173179
174180 setFilteredQuestionsAndAnswers ( responsesToDisplay ) ;
175- } , [ searchText , selectedResponders ] ) ; // eslint-disable-line react-hooks/exhaustive-deps
181+ } , [ searchText , selectedResponders ] ) ;
176182
177183 useEffect ( ( ) => {
178184 if ( isLoading && filteredQuestionsAndAnswers . length > 0 ) {
@@ -184,7 +190,14 @@ const ViewFeedbackResponses = () => {
184190 setSelectedResponders ( responderOptions ) ;
185191 } ;
186192
193+
194+ const isEmptyOrWhitespace = ( text ) => {
195+ return typeof text !== 'string' || ! text . trim ( ) ;
196+ } ;
197+
198+
187199 return selectCanViewFeedbackAnswerPermission ( state ) ? (
200+
188201 < Root className = "view-feedback-responses-page" >
189202 < Typography
190203 variant = "h4"
@@ -283,6 +296,9 @@ const ViewFeedbackResponses = () => {
283296 ) ) }
284297 { ! isLoading &&
285298 filteredQuestionsAndAnswers ?. map ( question => {
299+ const questionText = question . question ;
300+ const hasResponses = question . answers . length > 0 ;
301+
286302 return (
287303 < div
288304 className = "question-responses-container"
@@ -292,26 +308,21 @@ const ViewFeedbackResponses = () => {
292308 className = "question-text"
293309 style = { { marginBottom : '0.5em' , fontWeight : 'bold' } }
294310 >
295- Q { question . questionNumber } : { question . question }
311+ { questionText }
296312 </ Typography >
297- { question . answers . length === 0 && (
298- < div className = "no-responses-found" >
299- < Typography variant = "body1" style = { { color : 'gray' } } >
300- No matching responses found
301- </ Typography >
302- </ div >
303- ) }
304- { question . inputType !== 'NONE' &&
305- question . answers . length > 0 &&
313+
314+ { /* If the question has no answers or inputType is "NONE" */ }
315+ { ! hasResponses || question . inputType === 'NONE' ? null : (
306316 question . answers . map ( answer => (
307317 < FeedbackResponseCard
308318 key = { answer . id || answer . responder }
309319 responderId = { answer . responder }
310- answer = { answer . answer || '' }
320+ answer = { isEmptyOrWhitespace ( answer . answer ) ? ' ⚠️ No response submitted' : String ( answer . answer ) }
311321 inputType = { question . inputType }
312322 sentiment = { answer . sentiment }
313323 />
314- ) ) }
324+ ) )
325+ ) }
315326 </ div >
316327 ) ;
317328 } ) }
@@ -321,4 +332,4 @@ const ViewFeedbackResponses = () => {
321332 ) ;
322333} ;
323334
324- export default ViewFeedbackResponses ;
335+ export default ViewFeedbackResponses ;
0 commit comments