From 5024438143708c8b4922f737715e918a4e647a73 Mon Sep 17 00:00:00 2001 From: Hristo Oskov Date: Fri, 29 Dec 2023 17:59:33 -0800 Subject: [PATCH 1/5] add missing spanish translations --- src/core/i18n/es/translation.json | 54 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/core/i18n/es/translation.json b/src/core/i18n/es/translation.json index 21ff26c0..e7ad0845 100644 --- a/src/core/i18n/es/translation.json +++ b/src/core/i18n/es/translation.json @@ -83,12 +83,12 @@ "Hermano", "Otro niño" ], - "collaborator": "Who (if anyone) was your child talking to or interacting with during this activity?", + "collaborator": "¿Con quién (si hubo alguien) habló o interactuó con su hijo durante esta actividad?", "collaborator_options": [ - "No one, engaging independently", - "A parent or another caregiver/adult", - "A sibling or another peer/child", - "Both adult(s) and child(ren)" + "Nadie, participando independiente", + "Un padre u otro cuidador/adulto", + "Un hermano u otro compañero/niño", + "Los dos,adultos y niños" ], "bg_tv_day": "¿Había televisión de fondo mientras su hijo estaba {{activity}}? Por ejemplo, tal vez uno de los padres estaba viendo la televisión mientras su hijo hacía otra cosa en la habitación.", "bg_audio_day": "¿Había audio de fondo (p. ej., música, podcast) mientras su hijo estaba {{activity}}? Por ejemplo, tal vez uno de los padres estaba escuchando música mientras su hijo hacía otra cosa en la habitación.", @@ -118,7 +118,7 @@ ], "media_activity_options": [ "Veía contenido de vídeos (TV, películas, YouTube, etc.)", - "Veía contenido de vídeos (TV, películas, YouTube, etc.)", + "Juegos (aplicación, juego de consola, etc.)", "Videollamadas (Facetime, Zoom, etc.)", "Se comunicó con otros de otra manera (habló por teléfono, ayudó a escribir un mensaje de texto, etc.)", "Creó contenido (grabó un vídeo, tomó fotos, etc.)", @@ -129,27 +129,27 @@ "Libro electrónico (eBook)", "Audiolibro" ], - "device_type": "What device or devices was the child using?", - "device_type_options": [ - "TV (including using streaming services viewed on a TV screen)", - "Tablet", - "Phone", - "Computer", - "Gaming device" +"device_type": "¿Qué dispositivo o dispositivos estaba usando el niño?", +"device_type_options": [ + "TV (incluido el uso de servicios de streaming vieron en una pantalla de TV)", + "Tableta", + "Teléfono", + "Computadora", + "Dispositivo de juego" ], - "media_language_non_english": "Was the media entirely in a language other than English?", - "book_language_non_english": "Was/Were the book(s) entirely in a language other than English?", - "language": "What language(s)?", - "language_options": [ - "Spanish", - "Somali", - "Nepali", - "Arabic", - "French", + "media_language_non_english": "¿Estaban los medios completamente en un idioma distinto al inglés?", + "book_language_non_english": "¿Estaban los libros completamente en un idioma distinto al inglés?", + "idioma": "¿Qué idioma(s)?", + "Opciones de lenguaje": [ + "Español", + "Somalí", + "Nepalí", + "Arábica", + "Francés", "Hindi", - "Telgu", - "Chinese", - "German" + "telgu", + "Chino", + "Alemán" ], "no_one": "Ninguno", "bg_tv_night": "¿Estaba la televisión encendidad en la habitación mientras su hijo dormía por la noche?", @@ -199,6 +199,6 @@ "submission_success": "Gracias por completar la encuesta del Diario de Uso del Tiempo. Sus respuestas han sido registradas.", "submission_success_title": "Su encuesta se ha enviado correctamente!", "nighttime_activity_title": "Actividad Nocturna", - "error_invalid_url": "An error occurred while loading the Time Use Diary. Please try again later.", - "scroll_activities": "Please scroll to view more options." + "error_invalid_url": "Se produjo un error al cargar el Diario de uso del tiempo. Vuelva a intentarlo más tarde.", + "scroll_activities": "Desplácese para ver más opciones." } From 457adfce7f69385a5ccb3bf8294137448b1e1324 Mon Sep 17 00:00:00 2001 From: Hristo Oskov Date: Sat, 30 Dec 2023 15:58:12 -0800 Subject: [PATCH 2/5] json keys must be in english --- src/core/i18n/es/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/i18n/es/translation.json b/src/core/i18n/es/translation.json index e7ad0845..17f6ed97 100644 --- a/src/core/i18n/es/translation.json +++ b/src/core/i18n/es/translation.json @@ -139,8 +139,8 @@ ], "media_language_non_english": "¿Estaban los medios completamente en un idioma distinto al inglés?", "book_language_non_english": "¿Estaban los libros completamente en un idioma distinto al inglés?", - "idioma": "¿Qué idioma(s)?", - "Opciones de lenguaje": [ + "language": "¿Qué idioma(s)?", + "language_options": [ "Español", "Somalí", "Nepalí", From e563eb16a6de14a212b4036683701df48f1a0882 Mon Sep 17 00:00:00 2001 From: Hristo Oskov Date: Sat, 10 Feb 2024 17:28:13 -0800 Subject: [PATCH 3/5] update daily survey --- src/containers/survey/DailyAppUsageSurvey.js | 156 ++++++++++++------ src/containers/survey/SurveyContainer.js | 64 ++----- .../survey/components/SurveyForm.js | 89 ++-------- src/containers/survey/constants/index.js | 9 - 4 files changed, 135 insertions(+), 183 deletions(-) diff --git a/src/containers/survey/DailyAppUsageSurvey.js b/src/containers/survey/DailyAppUsageSurvey.js index c3284506..a4f2443e 100644 --- a/src/containers/survey/DailyAppUsageSurvey.js +++ b/src/containers/survey/DailyAppUsageSurvey.js @@ -1,47 +1,92 @@ -// @flow - -import { Map } from 'immutable'; import { AppContainerWrapper, AppContentWrapper, AppHeaderWrapper, Box, + Card, + CardSegment, + DatePicker, Spinner } from 'lattice-ui-kit'; import { DateTime } from 'luxon'; -import { RequestStates } from 'redux-reqseq'; -import type { RequestState } from 'redux-reqseq'; +import { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { OpenLatticeIconSVG } from '../../assets/svg/icons'; +import { + APP_USAGE_SURVEY, + AppUsageFreqTypes +} from '../../common/constants'; +import { + isPending, + isSuccess, + useRequestState +} from '../../common/utils'; +import { selectAppUsageSurveyData } from '../../core/redux/selectors'; +import { GET_APP_USAGE_SURVEY_DATA, SUBMIT_APP_USAGE_SURVEY, getAppUsageSurveyData } from './actions'; import SubmissionSuccessful from './components/SubmissionSuccessful'; import SurveyForm from './components/SurveyForm'; -import { OpenLatticeIconSVG } from '../../assets/svg/icons'; -import { isPending } from '../../common/utils'; +const SELECT_DATE_TEXT = 'Thank you for taking the time to complete this survey! Please select a date' + + ' for which to view app usage records.'; -type Props = { - data :Map; - date :string; - submitSurveyRS :?RequestState; - getAppUsageSurveyDataRS :?RequestState; - participantId :string; - studyId :UUID ; -} +const INSTRUCTIONS_TEXT = 'Below, you will find a list of apps that were used on this device on the' ++ ' selected date. Under each app, there will be a series of buttons you can click (you can click more' ++ ' than one) describing who in your family used that app. Please answer these prompts to the best of' ++ ' your ability. For example, if you and your child both watched YouTube separately, you would click' ++ ' "Parent Alone" and "Child Alone." If you both used this app together, as well as separately, you' ++ ' would click "Parent Alone," "Child Alone," and "Parent and Child Together."'; -const SurveyContainer = (props :Props) => { - const { - data, - date, - studyId, +const NO_APPS_TEXT = 'There is no recorded app usage for the selected date.' + + ' Thank you for your participation, the survey is not needed.'; + +const DailyAppUsageSurvey = ({ + date, + participantId, + studyId, +}) => { + const dispatch = useDispatch(); + const [surveyDate, setSurveyDate] = useState(); + + useEffect(() => { + if (typeof date === 'string') { + const maybeDate = DateTime.fromFormat(date, 'yyyy-MM-dd'); + if (maybeDate.isValid) { + setSurveyDate(maybeDate.toISODate()); + } + } + }, [date]); + + const appUsageSurveyData = useSelector(selectAppUsageSurveyData()); + const getAppUsageSurveyDataRS = useRequestState([APP_USAGE_SURVEY, GET_APP_USAGE_SURVEY_DATA]); + const submitRS = useRequestState([APP_USAGE_SURVEY, SUBMIT_APP_USAGE_SURVEY]); + + useEffect(() => { + if (typeof surveyDate === 'string') { + dispatch( + getAppUsageSurveyData({ + appUsageFreqType: AppUsageFreqTypes.DAILY, + date: surveyDate, + participantId, + studyId, + }) + ); + } + }, [ + dispatch, participantId, - getAppUsageSurveyDataRS, - submitSurveyRS, - } = props; + studyId, + surveyDate, + ]); - if (isPending(getAppUsageSurveyDataRS)) { + if (isSuccess(submitRS)) { return ( - - - + + + + + + ); } @@ -49,34 +94,51 @@ const SurveyContainer = (props :Props) => { - { - getAppUsageSurveyDataRS === RequestStates.SUCCESS && ( - <> - { - submitSurveyRS === RequestStates.SUCCESS - ? - : ( + + Apps Usage Survey + + + + {SELECT_DATE_TEXT} + + setSurveyDate(value)} + value={surveyDate} /> + + + { + typeof surveyDate === 'string' && ( + + { + isPending(getAppUsageSurveyDataRS) && ( + + ) + } + { + isSuccess(getAppUsageSurveyDataRS) && appUsageSurveyData.isEmpty() && ( +
{NO_APPS_TEXT}
+ ) + } + { + isSuccess(getAppUsageSurveyDataRS) && !appUsageSurveyData.isEmpty() && ( <> - - Apps Usage Survey - - - { DateTime.fromISO(date).toLocaleString(DateTime.DATE_FULL) } - + {INSTRUCTIONS_TEXT} + submitSurveyRS={submitRS} /> ) - } - - ) - } + } +
+ ) + } +
); }; -export default SurveyContainer; +export default DailyAppUsageSurvey; diff --git a/src/containers/survey/SurveyContainer.js b/src/containers/survey/SurveyContainer.js index 788d086c..f09446c8 100644 --- a/src/containers/survey/SurveyContainer.js +++ b/src/containers/survey/SurveyContainer.js @@ -1,10 +1,5 @@ -/* - * @flow - */ - import { useEffect } from 'react'; -import qs from 'qs'; import { Alert, AppContainerWrapper, @@ -15,33 +10,28 @@ import { CardSegment, Spinner } from 'lattice-ui-kit'; -import { DateTime } from 'luxon'; +import qs from 'qs'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import type { RequestState } from 'redux-reqseq'; import DailyAppUsageSurvey from './DailyAppUsageSurvey'; import HourlyAppUsageSurvey from './HourlyAppUsageSurvey'; -import { GET_APP_USAGE_SURVEY_DATA, SUBMIT_APP_USAGE_SURVEY, getAppUsageSurveyData } from './actions'; import { OpenLatticeIconSVG } from '../../assets/svg/icons'; import { APP_USAGE_FREQUENCY, - APP_USAGE_SURVEY, AppUsageFreqTypes, DATA_COLLECTION, - STUDIES, + STUDIES } from '../../common/constants'; import { isFailure, isPending, isStandby, - isSuccess, useRequestState, } from '../../common/utils'; -import { selectAppUsageSurveyData, selectStudySettings } from '../../core/redux/selectors'; +import { selectStudySettings } from '../../core/redux/selectors'; import { GET_STUDY_SETTINGS, getStudySettings } from '../study/actions'; -import type { AppUsageFreqType } from '../../common/types'; const SurveyContainer = () => { const dispatch = useDispatch(); @@ -50,42 +40,22 @@ const SurveyContainer = () => { const queryParams = qs.parse(location.search, { ignoreQueryPrefix: true }); const { - date = DateTime.local().toISODate(), participantId, - studyId - // $FlowFixMe - } :{ date :string, participantId :string, studyId :UUID } = queryParams; + studyId, + } = queryParams; - // selectors const studySettings = useSelector(selectStudySettings(studyId)); - const appUsageSurveyData = useSelector(selectAppUsageSurveyData()); - - const getAppUsageSurveyDataRS :?RequestState = useRequestState([APP_USAGE_SURVEY, GET_APP_USAGE_SURVEY_DATA]); - const getStudySettingsRS :?RequestState = useRequestState([STUDIES, GET_STUDY_SETTINGS]); - const submitSurveyRS :?RequestState = useRequestState([APP_USAGE_SURVEY, SUBMIT_APP_USAGE_SURVEY]); + const getStudySettingsRS = useRequestState([STUDIES, GET_STUDY_SETTINGS]); - const appUsageFreqType :AppUsageFreqType = studySettings.getIn( + const appUsageFreqType = studySettings.getIn( [DATA_COLLECTION, APP_USAGE_FREQUENCY] ) || AppUsageFreqTypes.DAILY; useEffect(() => { dispatch(getStudySettings(studyId)); - }, [studyId, dispatch]); - - // get apps - useEffect(() => { - if (isSuccess(getStudySettingsRS)) { - dispatch(getAppUsageSurveyData({ - appUsageFreqType, - date, - participantId, - studyId, - })); - } - - }, [date, participantId, studyId, appUsageFreqType, dispatch, getStudySettingsRS]); + }, [dispatch, studyId]); - if (isPending(getStudySettingsRS) || isStandby(getStudySettingsRS) || isPending(getAppUsageSurveyDataRS)) { + if (isPending(getStudySettingsRS) || isStandby(getStudySettingsRS)) { return ( @@ -93,7 +63,7 @@ const SurveyContainer = () => { ); } - if (isFailure(getStudySettingsRS) || isFailure(getAppUsageSurveyDataRS)) { + if (isFailure(getStudySettingsRS)) { return ( @@ -113,23 +83,17 @@ const SurveyContainer = () => { if (appUsageFreqType === AppUsageFreqTypes.HOURLY) { return ( + studyId={studyId} /> ); } return ( + studyId={studyId} /> ); }; diff --git a/src/containers/survey/components/SurveyForm.js b/src/containers/survey/components/SurveyForm.js index 24fc57f0..534f8279 100644 --- a/src/containers/survey/components/SurveyForm.js +++ b/src/containers/survey/components/SurveyForm.js @@ -1,81 +1,35 @@ -// @flow - import { useEffect, useState } from 'react'; -import styled from 'styled-components'; -import { Map } from 'immutable'; import { Form } from 'lattice-fabricate'; -import { - Card, - CardSegment, - Colors, - StyleUtils, -} from 'lattice-ui-kit'; import { useDispatch } from 'react-redux'; import { RequestStates } from 'redux-reqseq'; -import type { RequestState } from 'redux-reqseq'; import SubmissionFailureModal from './SubmissionFailureModal'; import { resetRequestStates } from '../../../core/redux/actions'; import { SUBMIT_APP_USAGE_SURVEY, submitAppUsageSurvey } from '../actions'; -import { SURVEY_INSTRUCTION_TEXT } from '../constants'; import { createSubmissionData, createSurveyFormSchema } from '../utils'; -const { media } = StyleUtils; -const { NEUTRAL } = Colors; - -const StyledCard = styled(Card)` - ${media.phone` - padding: 10px; - `} -`; - -const StyledCardSegment = styled(CardSegment)` - ${media.phone` - margin: 0 10px 0 10px; - `} -`; - -const NoAppsFound = styled.h4` - font-weight: 400; - font-size: 15px; - text-align: center; -`; - -const InstructionText = styled.span` - color: ${NEUTRAL.N700}; - line-height: 1.8; - font-size: 15px; -`; - -type Props = { - participantId :string; - studyId :UUID; - submitSurveyRS :?RequestState; - userAppsData :Map; -}; - const SurveyForm = ({ + data, participantId, studyId, submitSurveyRS, - userAppsData, -} :Props) => { +}) => { const dispatch = useDispatch(); const [errorModalVisible, setErrorModalVisible] = useState(false); - const { uiSchema, schema } = createSurveyFormSchema(userAppsData); + const { uiSchema, schema } = createSurveyFormSchema(data); useEffect(() => { setErrorModalVisible(submitSurveyRS === RequestStates.FAILURE); }, [errorModalVisible, setErrorModalVisible, submitSurveyRS]); - const handleOnSubmit = ({ formData } :Object) => { + const handleOnSubmit = ({ formData }) => { dispatch(submitAppUsageSurvey({ - data: createSubmissionData(formData, userAppsData), + data: createSubmissionData(formData, data), participantId, studyId, })); @@ -87,35 +41,16 @@ const SurveyForm = ({ }; return ( - - { - userAppsData.isEmpty() - ? ( - - - There is no recorded app usage without a known user today. - Thank you for your participation, the survey is not needed today. - - - ) : ( - <> - - - {SURVEY_INSTRUCTION_TEXT} - -
- - - ) - } + <> + - + ); }; diff --git a/src/containers/survey/constants/index.js b/src/containers/survey/constants/index.js index eaeadaa6..3ba5e334 100644 --- a/src/containers/survey/constants/index.js +++ b/src/containers/survey/constants/index.js @@ -1,13 +1,5 @@ // @flow -const SURVEY_INSTRUCTION_TEXT = 'Thank you for taking the time to complete this survey! Below, you will' -+ ' find a list of apps that were used on this device today. Under each app, there will be a series of' -+ ' buttons you can click (you can click more than one) describing who in your family used that app today.' -+ ' Please answer these prompts to the best of your ability.' -+ ' For example, if you and your child both watched YouTube separately,' -+ ' you would click “Parent Alone” and “Child Alone.” If you both used this app together,' -+ ' as well as separately, you would click “Parent Alone,” “Child Alone,” and “Parent and Child Together.”'; - const SELECT_CHILD_APPS = 'selectChildApps'; const SELECT_SHARED_APPS = 'selectSharedApps'; const RESOLVE_SHARED_APPS = 'resolveSharedApps'; @@ -22,6 +14,5 @@ const SURVEY_STEPS = { SELECT_SHARED_APPS, }; export { - SURVEY_INSTRUCTION_TEXT, SURVEY_STEPS }; From 8143b8c866f2be47ed67e02e76d51f2e4cc394ff Mon Sep 17 00:00:00 2001 From: Hristo Oskov Date: Tue, 13 Feb 2024 18:02:41 -0800 Subject: [PATCH 4/5] add data selection to app usage survey --- src/containers/survey/DailyAppUsageSurvey.js | 24 +- src/containers/survey/HourlyAppUsageSurvey.js | 388 +++++++++--------- .../survey/components/HourlySurvey.js | 52 +-- .../survey/components/HourlySurveyDispatch.js | 5 +- .../components/HourlySurveyInstructions.js | 33 +- .../components/HourlyUsageSurveyAppBar.js | 20 +- .../components/SelectAppUsageTimeSlots.js | 46 +-- .../survey/components/SelectAppsByUser.js | 21 +- 8 files changed, 268 insertions(+), 321 deletions(-) diff --git a/src/containers/survey/DailyAppUsageSurvey.js b/src/containers/survey/DailyAppUsageSurvey.js index a4f2443e..274bff17 100644 --- a/src/containers/survey/DailyAppUsageSurvey.js +++ b/src/containers/survey/DailyAppUsageSurvey.js @@ -13,11 +13,9 @@ import { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { OpenLatticeIconSVG } from '../../assets/svg/icons'; +import { APP_USAGE_SURVEY, AppUsageFreqTypes } from '../../common/constants'; import { - APP_USAGE_SURVEY, - AppUsageFreqTypes -} from '../../common/constants'; -import { + isFailure, isPending, isSuccess, useRequestState @@ -57,7 +55,7 @@ const DailyAppUsageSurvey = ({ } }, [date]); - const appUsageSurveyData = useSelector(selectAppUsageSurveyData()); + const data = useSelector(selectAppUsageSurveyData()); const getAppUsageSurveyDataRS = useRequestState([APP_USAGE_SURVEY, GET_APP_USAGE_SURVEY_DATA]); const submitRS = useRequestState([APP_USAGE_SURVEY, SUBMIT_APP_USAGE_SURVEY]); @@ -95,14 +93,13 @@ const DailyAppUsageSurvey = ({ - Apps Usage Survey + App Usage Survey {SELECT_DATE_TEXT} setSurveyDate(value)} value={surveyDate} /> @@ -116,22 +113,29 @@ const DailyAppUsageSurvey = ({ ) } { - isSuccess(getAppUsageSurveyDataRS) && appUsageSurveyData.isEmpty() && ( + isSuccess(getAppUsageSurveyDataRS) && data.isEmpty() && (
{NO_APPS_TEXT}
) } { - isSuccess(getAppUsageSurveyDataRS) && !appUsageSurveyData.isEmpty() && ( + isSuccess(getAppUsageSurveyDataRS) && !data.isEmpty() && ( <> {INSTRUCTIONS_TEXT} ) } + { + isFailure(getAppUsageSurveyDataRS) && ( + + Sorry, something went wrong. Please try refreshing the page, or contact support. + + ) + }
) } diff --git a/src/containers/survey/HourlyAppUsageSurvey.js b/src/containers/survey/HourlyAppUsageSurvey.js index 5b7f2e03..79ebb8ee 100644 --- a/src/containers/survey/HourlyAppUsageSurvey.js +++ b/src/containers/survey/HourlyAppUsageSurvey.js @@ -1,31 +1,43 @@ -// @flow -import { useEffect, useReducer } from 'react'; - -import { - Map, - Set, -} from 'immutable'; +import { Set, fromJS } from 'immutable'; import { AppContainerWrapper, AppContentWrapper, + Box, Card, CardSegment, + DatePicker, + Spinner, } from 'lattice-ui-kit'; -import { useDispatch } from 'react-redux'; -import type { RequestState } from 'redux-reqseq'; +import { DateTime } from 'luxon'; +import { useEffect, useReducer, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { BasicErrorComponent } from '../../common/components'; +import { APP_USAGE_SURVEY, AppUsageFreqTypes } from '../../common/constants'; +import { + isFailure, + isPending, + isSuccess, + useRequestState +} from '../../common/utils'; +import { selectAppUsageSurveyData } from '../../core/redux/selectors'; +import { + GET_APP_USAGE_SURVEY_DATA, + SUBMIT_APP_USAGE_SURVEY, + getAppUsageSurveyData, + submitAppUsageSurvey, +} from './actions'; import ConfirmSurveySubmissionModal from './components/ConfirmSurveySubmissionModal'; import HourlySurvey from './components/HourlySurvey'; +import HourlySurveyDispatch, { ACTIONS } from './components/HourlySurveyDispatch'; import HourlyUsageSurveyAppBar from './components/HourlyUsageSurveyAppBar'; import InstructionsModal from './components/InstructionsModal'; import SubmissionSuccessful from './components/SubmissionSuccessful'; -import HourlySurveyDispatch, { ACTIONS } from './components/HourlySurveyDispatch'; -import { submitAppUsageSurvey } from './actions'; import { SURVEY_STEPS } from './constants'; import { createHourlySurveySubmissionData } from './utils'; -import { BasicErrorComponent } from '../../common/components'; -import { isFailure, isPending, isSuccess } from '../../common/utils'; +const SELECT_DATE_TEXT = 'Thank you for taking the time to complete this survey! Please select a date' + + ' for which to view app usage records.'; const { SELECT_CHILD_APPS, @@ -35,40 +47,37 @@ const { INTRO } = SURVEY_STEPS; -const initialState = { - childOnlyApps: Set().asMutable(), - initialTimeRangeSelections: Map().asMutable(), +const INITIAL_STATE = fromJS({ + appsCount: 0, + appsData: {}, + childOnlyApps: Set(), + initialTimeRangeSelections: {}, isConfirmModalVisible: false, + isFinalStep: false, isInstructionsModalVisible: false, isSubmissionConfirmed: false, - isFinalStep: false, - otherTimeRangeSelections: Map().asMutable(), - sharedApps: Set().asMutable(), + otherTimeRangeSelections: {}, + sharedApps: Set(), + sharedAppsOptionsCount: 0, step: 0, surveyStep: INTRO, - sharedAppsOptionsCount: 0, -}; +}); const reducer = (state, action) => { - const getNextStep = () => { + const getNextStep = (currentSurveyStep, sharedApps) => { let isFinalStep = false; let nextStep = ''; - const { - surveyStep, - sharedApps, - } = state; - if (surveyStep === INTRO) { + if (currentSurveyStep === INTRO) { nextStep = SELECT_CHILD_APPS; } - if (surveyStep === SELECT_CHILD_APPS) { + if (currentSurveyStep === SELECT_CHILD_APPS) { nextStep = SELECT_SHARED_APPS; isFinalStep = true; // User might choose not to select anything from the shared apps page // in which case they should be able to submit the survey. However, if they select anything // we need to set isFinalStep = false so that they can move on to the next page } - - if (surveyStep === SELECT_SHARED_APPS) { + if (currentSurveyStep === SELECT_SHARED_APPS) { if (sharedApps.isEmpty()) { // No shared apps selected: skip time-resolution steps isFinalStep = true; @@ -77,26 +86,21 @@ const reducer = (state, action) => { nextStep = RESOLVE_SHARED_APPS; } } - if (surveyStep === RESOLVE_SHARED_APPS) { + if (currentSurveyStep === RESOLVE_SHARED_APPS) { nextStep = RESOLVE_OTHER_APPS; isFinalStep = true; } - return { isFinalStep, nextStep }; }; - const getPrevStep = () => { - const { - surveyStep - } = state; - - if (surveyStep === RESOLVE_OTHER_APPS) { + const getPrevStep = (currentSurveyStep) => { + if (currentSurveyStep === RESOLVE_OTHER_APPS) { return RESOLVE_SHARED_APPS; } - if (surveyStep === RESOLVE_SHARED_APPS) { + if (currentSurveyStep === RESOLVE_SHARED_APPS) { return SELECT_SHARED_APPS; } - if (surveyStep === SELECT_SHARED_APPS) { + if (currentSurveyStep === SELECT_SHARED_APPS) { return SELECT_CHILD_APPS; } return INTRO; @@ -105,60 +109,43 @@ const reducer = (state, action) => { switch (action.type) { case ACTIONS.ASSIGN_USER: { const { appName } = action; + const appsCount = state.get('appsCount'); + const childOnlyApps = state.get('childOnlyApps'); + const sharedApps = state.get('sharedApps'); + const surveyStep = state.get('surveyStep'); - const { - childOnlyApps, - sharedApps, - appsCount, - surveyStep - } = state; - - const selected = surveyStep === SELECT_CHILD_APPS ? childOnlyApps : sharedApps; - + let selected = surveyStep === SELECT_CHILD_APPS ? Set(childOnlyApps) : Set(sharedApps); if (selected.has(appName)) { - selected.delete(appName); + selected = selected.delete(appName); } else { - selected.add(appName); + selected = selected.add(appName); } if (surveyStep === SELECT_CHILD_APPS) { // If user select all apps, enable submit - const isFinalStep = selected.size === appsCount; - return { - ...state, - childOnlyApps: selected, - isFinalStep - }; + // const isFinalStep = selected.size === appsCount; + return state + .set('childOnlyApps', selected) + .set('isFinalStep', selected.size === appsCount); } - return { - ...state, - isFinalStep: selected.isEmpty(), - sharedApps: selected - }; + return state + .set('isFinalStep', selected.isEmpty()) + .set('sharedApps', selected); } - case ACTIONS.TOGGLE_INSTRUCTIONS_MODAL: { - const { visible } = action; - return { - ...state, - isInstructionsModalVisible: visible - }; + return state.set('isInstructionsModalVisible', action.visible); } - case ACTIONS.SELECT_TIME_RANGE: { const { appName, timeRange } = action; - const { - initialTimeRangeSelections, - otherTimeRangeSelections, - sharedAppsOptionsCount, - surveyStep, - } = state; + const initialTimeRangeSelections = state.get('initialTimeRangeSelections'); + const otherTimeRangeSelections = state.get('otherTimeRangeSelections'); + const sharedAppsOptionsCount = state.get('sharedAppsOptionsCount'); + const surveyStep = state.get('surveyStep'); + let updatedValue = surveyStep === RESOLVE_SHARED_APPS ? initialTimeRangeSelections : otherTimeRangeSelections; - const updatedValue = surveyStep === RESOLVE_SHARED_APPS ? initialTimeRangeSelections : otherTimeRangeSelections; - - updatedValue.update( + updatedValue = updatedValue.update( appName, Set(), (current) => (current.has(timeRange) ? current.delete(timeRange) : current.add(timeRange)) @@ -167,117 +154,122 @@ const reducer = (state, action) => { if (surveyStep === RESOLVE_SHARED_APPS) { // Here we count the number of selections so far and compare with the total number of // possible selections. If equal, indicate survey as done by setting isFinalStep = true - const currentSelectionsCount = updatedValue.keySeq().toSet().map((key) => updatedValue.get(key).size) + const currentSelectionsCount = updatedValue.keySeq().toSet() + .map((key) => updatedValue.get(key).size) .reduce((prev, next) => prev + next); - - const isFinalStep = currentSelectionsCount === sharedAppsOptionsCount; - return { - ...state, - initialTimeRangeSelections: updatedValue, - isFinalStep - }; + return state + .set('initialTimeRangeSelections', updatedValue) + .set('isFinalStep', currentSelectionsCount === sharedAppsOptionsCount); } - return { - ...state, - remainingTimeRangeOptions: updatedValue - }; + return state.set('otherTimeRangeSelections', updatedValue); } case ACTIONS.NEXT_STEP: { - const { - surveyStep, - sharedAppsOptionsCount, - appsData, - sharedApps - } = state; - let optionsCount = sharedAppsOptionsCount; + const currentSurveyStep = state.get('surveyStep'); + const sharedApps = state.get('sharedApps'); + let sharedAppsOptionsCount = state.get('sharedAppsOptionsCount'); - if (surveyStep === SELECT_SHARED_APPS) { + if (currentSurveyStep === SELECT_SHARED_APPS) { // When navigating away from the shared apps selection page, // we calculate total number of possible selections available for next step(s) of survey - optionsCount = sharedApps.asImmutable() - .map((app) => appsData.getIn([app, 'data']).size) + sharedAppsOptionsCount = sharedApps + .map((app) => state.getIn(['appsData', app, 'data'], Set()).size) .reduce((prev, next) => prev + next); } - const { isFinalStep, nextStep } = getNextStep(); - return { - ...state, - isFinalStep, - step: state.step + 1, - surveyStep: nextStep, - sharedApps: sharedApps.asMutable(), - sharedAppsOptionsCount: optionsCount - }; + const { isFinalStep, nextStep } = getNextStep(currentSurveyStep, sharedApps); + return state + .set('isFinalStep', isFinalStep) + .set('sharedAppsOptionsCount', sharedAppsOptionsCount) + .set('step', state.get('step') + 1) + .set('surveyStep', nextStep); } case ACTIONS.PREV_STEP: { - return { - ...state, - isFinalStep: false, - step: state.step - 1, - surveyStep: getPrevStep(), - }; + const currentSurveyStep = state.get('surveyStep'); + return state + .set('isFinalStep', false) + .set('step', state.get('step') - 1) + .set('surveyStep', getPrevStep(currentSurveyStep)); } case ACTIONS.CONFIRM_SUBMIT: { - return { - ...state, - isSubmissionConfirmed: true, - isConfirmModalVisible: false - }; + return state + .set('isConfirmModalVisible', false) + .set('isSubmissionConfirmed', true); } case ACTIONS.CANCEL_SUBMIT: { - return { - ...state, - isConfirmModalVisible: false - }; + return state.set('isConfirmModalVisible', false); } case ACTIONS.SHOW_CONFIRM_MODAL: { - return { - ...state, - isConfirmModalVisible: true - }; + return state.set('isConfirmModalVisible', true); + } + case ACTIONS.SET_DATA: { + return state + .set('appsCount', action.data.keySeq().size) + .set('appsData', action.data); + } + case ACTIONS.RESET: { + return INITIAL_STATE; } default: return state; } }; -type Props = { - data :Map; - date :string; - submitSurveyRS :?RequestState; - getAppUsageSurveyDataRS :?RequestState; - participantId :string; - studyId :UUID ; -}; - -const HourlyAppUsageSurvey = (props :Props) => { - const { - data, - date, - studyId, - participantId, - submitSurveyRS, - getAppUsageSurveyDataRS, - } = props; +const HourlyAppUsageSurvey = ({ + date, + participantId, + studyId, +}) => { const storeDispatch = useDispatch(); + const [surveyDate, setSurveyDate] = useState(); + + const data = useSelector(selectAppUsageSurveyData()); + const getAppUsageSurveyDataRS = useRequestState([APP_USAGE_SURVEY, GET_APP_USAGE_SURVEY_DATA]); + const submitRS = useRequestState([APP_USAGE_SURVEY, SUBMIT_APP_USAGE_SURVEY]); + + const [state, dispatch] = useReducer(reducer, INITIAL_STATE); + const childOnlyApps = state.get('childOnlyApps'); + const initialTimeRangeSelections = state.get('initialTimeRangeSelections'); + const isConfirmModalVisible = state.get('isConfirmModalVisible'); + const isInstructionsModalVisible = state.get('isInstructionsModalVisible'); + const isSubmissionConfirmed = state.get('isSubmissionConfirmed'); + const otherTimeRangeSelections = state.get('otherTimeRangeSelections'); + const step = state.get('step'); + const surveyStep = state.get('surveyStep'); - const [state, dispatch] = useReducer(reducer, { - ...initialState, - appsData: data, - appsCount: data.keySeq().size - }); + useEffect(() => { + if (typeof date === 'string') { + const maybeDate = DateTime.fromFormat(date, 'yyyy-MM-dd'); + if (maybeDate.isValid) { + setSurveyDate(maybeDate.toISODate()); + } + } + }, [date]); - const { - step, - surveyStep, - childOnlyApps, - isConfirmModalVisible, - initialTimeRangeSelections, - otherTimeRangeSelections, - isSubmissionConfirmed, - isInstructionsModalVisible, - } = state; + useEffect(() => { + if (isSuccess(getAppUsageSurveyDataRS)) { + dispatch({ data, type: ACTIONS.SET_DATA }); + } + }, [data, getAppUsageSurveyDataRS]); + + useEffect(() => { + if (typeof surveyDate === 'string') { + dispatch({ type: ACTIONS.RESET }); + storeDispatch( + getAppUsageSurveyData({ + appUsageFreqType: AppUsageFreqTypes.HOURLY, + date: surveyDate, + participantId, + studyId, + }) + ); + } + }, [ + participantId, + storeDispatch, + studyId, + surveyDate, + ]); useEffect(() => { if (isSubmissionConfirmed) { @@ -302,25 +294,28 @@ const HourlyAppUsageSurvey = (props :Props) => { data, initialTimeRangeSelections, isSubmissionConfirmed, - participantId, otherTimeRangeSelections, + participantId, storeDispatch, studyId, ]); - const hasSubmitted = isSuccess(submitSurveyRS) || isFailure(submitSurveyRS); - - const isSubmitting = isPending(submitSurveyRS); - - const SubmissionCompleted = () => ( - <> - { - isSuccess(submitSurveyRS) - ? - : - } - - ); + if (isSuccess(submitRS) || isFailure(submitRS)) { + return ( + + + + + { + isSuccess(submitRS) + ? + : + } + + + + ); + } return ( @@ -329,18 +324,39 @@ const HourlyAppUsageSurvey = (props :Props) => { - { - hasSubmitted - ? - : ( - - ) - } + {SELECT_DATE_TEXT} + + setSurveyDate(value)} + value={surveyDate} /> + + { + typeof surveyDate === 'string' && ( + + { + isPending(getAppUsageSurveyDataRS) && ( + + ) + } + { + isSuccess(getAppUsageSurveyDataRS) && ( + + ) + } + { + isFailure(getAppUsageSurveyDataRS) && ( + + Sorry, something went wrong. Please try refreshing the page, or contact support. + + ) + } + + ) + } diff --git a/src/containers/survey/components/HourlySurvey.js b/src/containers/survey/components/HourlySurvey.js index fe26201e..f04e28f3 100644 --- a/src/containers/survey/components/HourlySurvey.js +++ b/src/containers/survey/components/HourlySurvey.js @@ -1,10 +1,6 @@ -// @flow - -import { useMemo } from 'react'; - import { Map } from 'immutable'; import { Box } from 'lattice-ui-kit'; -import type { RequestState } from 'redux-reqseq'; +import { useMemo } from 'react'; import HourlySurveyInstructions from './HourlySurveyInstructions'; import SelectAppUsageTimeSlots from './SelectAppUsageTimeSlots'; @@ -22,31 +18,19 @@ const { INTRO } = SURVEY_STEPS; -type Props = { - data :Map; - isSubmitting :boolean; - state :Object; - getAppUsageSurveyDataRS :?RequestState -}; - -const HourlySurvey = (props :Props) => { - - const { - data, - getAppUsageSurveyDataRS, - isSubmitting, - state, - } = props; +const HourlySurvey = ({ + data, + isSubmitting, + state, +}) => { - const { - childOnlyApps, - initialTimeRangeSelections, - isFinalStep, - otherTimeRangeSelections, - sharedApps, - step, - surveyStep - } = state; + const childOnlyApps = state.get('childOnlyApps'); + const initialTimeRangeSelections = state.get('initialTimeRangeSelections'); + const isFinalStep = state.get('isFinalStep'); + const otherTimeRangeSelections = state.get('otherTimeRangeSelections'); + const sharedApps = state.get('sharedApps'); + const step = state.get('step'); + const surveyStep = state.get('surveyStep'); const getInstructionText = () => { switch (surveyStep) { @@ -72,16 +56,6 @@ const HourlySurvey = (props :Props) => { const sharedAppsData = data.filterNot((val, key) => childOnlyApps.has(key)); const timeRangeOptions = data.filter((val, key) => sharedApps.has(key)); - // const childAppsOtherOptions = getChildApppsOtherOptions(); - - if (isFailure(getAppUsageSurveyDataRS)) { - return ( - - Sorry, something went wrong. Please try refreshing the page, or contact support. - - ); - } - if (step === 0) { return ( { - const { - isFinalStep, - isNextButtonDisabled, - isSubmitting, - nextButtonText, - step, - noApps - } = props; - +const HourlySurveyInstructions = ({ + isFinalStep, + isNextButtonDisabled, + isSubmitting, + nextButtonText, + noApps, + step, +}) => { if (noApps) { return ( @@ -35,8 +22,8 @@ const HourlySurveyInstructions = (props :Props) => { Please complete this short survey to let us know which of the apps - used in the last 24 hours were used by the child enrolled in our study. The - survey will refer to this child as "your child". + used on the selected date were used by the child enrolled in our study. + The survey will refer to this child as "your child". For instructions at each step please click on 3 dots to the top-right of the app. diff --git a/src/containers/survey/components/HourlyUsageSurveyAppBar.js b/src/containers/survey/components/HourlyUsageSurveyAppBar.js index e2198f3f..9a07e39a 100644 --- a/src/containers/survey/components/HourlyUsageSurveyAppBar.js +++ b/src/containers/survey/components/HourlyUsageSurveyAppBar.js @@ -1,31 +1,19 @@ -// @flow -import { useContext, useEffect, useState } from 'react'; - import { faEllipsisV } from '@fortawesome/pro-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - // $FlowFixMe AppBar, - // $FlowFixMe Box, Colors, IconButton, - // $FlowFixMe Menu, - // $FlowFixMe MenuItem, - // $FlowFixMe Toolbar, } from 'lattice-ui-kit'; +import { useContext, useEffect, useState } from 'react'; import HourlySurveyDispatch, { ACTIONS } from './HourlySurveyDispatch'; -type Props = { - step :number; - date :string -} - -const HourlyUsageSurveyAppBar = ({ date, step } :Props) => { +const HourlyUsageSurveyAppBar = ({ step }) => { const [title, setTitle] = useState(''); const [anchorEl, setAnchorEl] = useState(null); @@ -33,12 +21,12 @@ const HourlyUsageSurveyAppBar = ({ date, step } :Props) => { useEffect(() => { if (step === 0) { - setTitle(`Survey ${date}`); + setTitle('App Usage Survey'); } else { setTitle(`Step-${step}`); } - }, [step, date]); + }, [step]); const handleOnClick = (event) => { setAnchorEl(event.currentTarget); diff --git a/src/containers/survey/components/SelectAppUsageTimeSlots.js b/src/containers/survey/components/SelectAppUsageTimeSlots.js index 8b4a7811..36ad5502 100644 --- a/src/containers/survey/components/SelectAppUsageTimeSlots.js +++ b/src/containers/survey/components/SelectAppUsageTimeSlots.js @@ -1,7 +1,4 @@ -// @flow -import { useContext } from 'react'; - -import { Map, Set } from 'immutable'; +import { Set } from 'immutable'; import { Box, Card, @@ -10,32 +7,23 @@ import { Checkbox, Grid } from 'lattice-ui-kit'; +import { useContext } from 'react'; import HourlySurveyDispatch, { ACTIONS } from './HourlySurveyDispatch'; -type Props = { - data :Map; - initial :boolean; - initialSelections :Map; - options :Map; - selected :Map; -}; - -const SelectAppUsageTimeSlots = (props :Props) => { - const { - data, - initial, - options, - selected, - initialSelections, - } = props; +const SelectAppUsageTimeSlots = ({ + data, + initial, + initialSelections, + options, + selected, +}) => { const dispatch = useContext(HourlySurveyDispatch); - const isSelected = (timeRange :string, appName :string) => selected.get(appName, Set()).includes(timeRange); - - const handleOnChange = (timeRange :string, appName :string) => { + const isSelected = (timeRange, appName) => selected.get(appName, Set()).includes(timeRange); + const handleOnChange = (timeRange, appName) => { dispatch({ type: ACTIONS.SELECT_TIME_RANGE, appName, @@ -44,7 +32,7 @@ const SelectAppUsageTimeSlots = (props :Props) => { }); }; - const applyFilter = (timeRange :string, appName :String) => ( + const applyFilter = (timeRange, appName) => ( initial ? true : !initialSelections.get(appName, Set()).has(timeRange) ); @@ -59,14 +47,18 @@ const SelectAppUsageTimeSlots = (props :Props) => { - - { data.getIn([appName, 'appLabel'])} + + {data.getIn([appName, 'appLabel'])} { - timeRanges.sort().map((timeRange :string) => ( + timeRanges.sort().map((timeRange) => ( -}; - -const SelectAppsByUser = (props :Props) => { - const { appsData, selected } = props; - +const SelectAppsByUser = ({ appsData, selected }) => { const dispatch = useContext(HourlySurveyDispatch); - const handleOnChange = (appName :string) => { + const handleOnChange = (appName) => { dispatch({ type: ACTIONS.ASSIGN_USER, appName }); }; From fb53473157b1017c59b16383202aa3a9f136cb3a Mon Sep 17 00:00:00 2001 From: Hristo Oskov Date: Wed, 14 Feb 2024 10:55:56 -0800 Subject: [PATCH 5/5] happy linter --- src/containers/survey/components/HourlySurvey.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/containers/survey/components/HourlySurvey.js b/src/containers/survey/components/HourlySurvey.js index f04e28f3..5ab10b0d 100644 --- a/src/containers/survey/components/HourlySurvey.js +++ b/src/containers/survey/components/HourlySurvey.js @@ -2,14 +2,12 @@ import { Map } from 'immutable'; import { Box } from 'lattice-ui-kit'; import { useMemo } from 'react'; +import { SURVEY_STEPS } from '../constants'; import HourlySurveyInstructions from './HourlySurveyInstructions'; import SelectAppUsageTimeSlots from './SelectAppUsageTimeSlots'; import SelectAppsByUser from './SelectAppsByUser'; import SurveyButtons from './SurveyButtons'; -import { isFailure } from '../../../common/utils'; -import { SURVEY_STEPS } from '../constants'; - const { SELECT_CHILD_APPS, SELECT_SHARED_APPS,