From 0d87fa47ecdbf7700d8892f824f8ff4e93505818 Mon Sep 17 00:00:00 2001 From: Ihtisham Arif Date: Wed, 20 Jul 2022 20:34:58 +0500 Subject: [PATCH 1/6] Added decision history --- .../Decision/Modals/DecisionHistoryModal.tsx | 184 ++++++++++++++++++ components/Decision/Modals/TabInfoModal.tsx | 36 ++++ .../Decision/SideCards/QuestionHelperCard.tsx | 44 ++++- components/Decision/common/BaseCard.tsx | 4 +- components/Decision/common/WrapperTabMenu.tsx | 27 ++- features/decision/decisionSlice.ts | 8 + features/interfaces.ts | 1 + pages/index.tsx | 34 +--- queries/getDecisionHistory.ts | 34 ++++ utils/types/firebase.ts | 21 ++ 10 files changed, 352 insertions(+), 41 deletions(-) create mode 100644 components/Decision/Modals/DecisionHistoryModal.tsx create mode 100644 components/Decision/Modals/TabInfoModal.tsx create mode 100644 queries/getDecisionHistory.ts diff --git a/components/Decision/Modals/DecisionHistoryModal.tsx b/components/Decision/Modals/DecisionHistoryModal.tsx new file mode 100644 index 0000000..b942fc5 --- /dev/null +++ b/components/Decision/Modals/DecisionHistoryModal.tsx @@ -0,0 +1,184 @@ +import { UilHistory } from '@iconscout/react-unicons' +import React, { FC, useEffect, useState } from 'react' +import { useFormContext } from 'react-hook-form' + +import { + setClickedConnect, + setCurrentTab, + setDecisionActivityId, + setDecisionEngineOptionTab, + setDecisionHistoryModal, + setDecisionQuestion, + setDecisionRatingUpdate, + setIsDecisionRehydrated, + setIsQuestionSafeForAI, + setPreviousIndex, + setSideCardStep, + setUserIgnoredUnsafeWarning, + updateDecisionFormState, +} from '../../../features/decision/decisionSlice' +import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' +import { getDecisionHistory } from '../../../queries/getDecisionHistory' +import { body, bodyHeavy } from '../../../styles/typography' +import { deepCopy } from '../../../utils/helpers/common' +import { + DecisionFirebase, + FirebaseDecisionActivity, +} from '../../../utils/types/firebase' +import Modal from '../../Utils/Modal' +import { BaseCard } from '../common/BaseCard' + +interface DecisionHistoryModalProps { + deviceIp: string +} +export const DecisionHistoryModal: FC = ({ + deviceIp, +}: DecisionHistoryModalProps) => { + const { decisionHistoryModal, currentTab } = useAppSelector( + state => state.decisionSlice + ) + const { setValue } = useFormContext() + const [completed, setCompleted] = useState([]) + const [inComplete, setInComplete] = useState([]) + + useEffect(() => { + getDecisionHistory(deviceIp).then(({ complete, inComplete }) => { + setCompleted(complete) + setInComplete(inComplete) + }) + }, []) + + const onHandleSelect = (decision: DecisionFirebase) => { + console.log(decision) + // Create copies + const decisionCopy = deepCopy(decision) + // Set rehydration flags + useAppDispatch(setIsDecisionRehydrated(true)) + // remove extra fields + const extraFields = [ + 'timestamp', + 'currentTab', + 'id', + 'userId', + 'isComplete', + 'ipAddress', + ] + for (const field of extraFields) { + delete decisionCopy[field] + } + const formState: FirebaseDecisionActivity = { + id: decision.id, + } + for (const [key, value] of Object.entries( + decisionCopy as FirebaseDecisionActivity + )) { + if (key !== 'clickedConnect') { + setValue(key, deepCopy(value), { + shouldValidate: true, + shouldDirty: true, + }) + } + formState[key] = deepCopy(value) + } + const { + question, + clickedConnect, + userIgnoredUnsafeWarning, + isQuestionSafeForAI, + } = decisionCopy + + // Set form state in redux + useAppDispatch(setDecisionActivityId(decision.id)) + useAppDispatch(setDecisionQuestion(question)) + useAppDispatch(setClickedConnect(clickedConnect)) + useAppDispatch(setUserIgnoredUnsafeWarning(userIgnoredUnsafeWarning)) + useAppDispatch(setIsQuestionSafeForAI(isQuestionSafeForAI)) + useAppDispatch(updateDecisionFormState(formState)) + + if (clickedConnect) { + useAppDispatch(setSideCardStep(2)) + } + // update current tab + if (decision.currentTab && !decision.isComplete) { + useAppDispatch(setCurrentTab(decision.currentTab)) + if (currentTab === 4) useAppDispatch(setDecisionRatingUpdate(true)) + } + if (decision.isComplete) { + useAppDispatch(setPreviousIndex(4)) + useAppDispatch(setCurrentTab(5)) + useAppDispatch(setDecisionEngineOptionTab(0)) + } + useAppDispatch(setDecisionHistoryModal(false)) + } + + return ( + + useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) + } + className="md:w-[60%]" + > +
+ + + Decision History + + + You can view the results of a past decision or return to an + incomplete decision. + +
+ + + Complete + +
+ {completed.map((item, idx) => ( + onHandleSelect(item)} + className="cursor-pointer !rounded-lg p-4" + key={`${item.id}-complete-item${idx}`} + > + + {item.question} + + + ))} +
+
+ + + Incomplete + +
+ {inComplete.map((item, idx) => ( + onHandleSelect(item)} + className="cursor-pointer !rounded-lg p-4" + key={`${item.id}-incomplete-item${idx}`} + > + + {item.question} + + + ))} +
+
+
+
+
+ ) +} diff --git a/components/Decision/Modals/TabInfoModal.tsx b/components/Decision/Modals/TabInfoModal.tsx new file mode 100644 index 0000000..6d1219b --- /dev/null +++ b/components/Decision/Modals/TabInfoModal.tsx @@ -0,0 +1,36 @@ +import { UilQuestionCircle } from '@iconscout/react-unicons' +import React from 'react' + +import { setInfoModal } from '../../../features/decision/decisionSlice' +import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' +import Modal from '../../Utils/Modal' + +export const TabInfoModal = () => { + const { isInfoModal, infoModalDetails, currentTab, matrixStep } = + useAppSelector(state => state.decisionSlice) + return ( + useAppDispatch(setInfoModal(!isInfoModal))} + className="md:w-[40%]" + > +
+ + + + {currentTab === 0 + ? infoModalDetails.title.split('/')[matrixStep] + : infoModalDetails.title} + + + + {infoModalDetails.context} + +
+
+ ) +} diff --git a/components/Decision/SideCards/QuestionHelperCard.tsx b/components/Decision/SideCards/QuestionHelperCard.tsx index b344e22..d151dc6 100644 --- a/components/Decision/SideCards/QuestionHelperCard.tsx +++ b/components/Decision/SideCards/QuestionHelperCard.tsx @@ -1,9 +1,14 @@ -import { UilPlusCircle, UilQuestionCircle } from '@iconscout/react-unicons' +import { + UilHistory, + UilPlusCircle, + UilQuestionCircle, +} from '@iconscout/react-unicons' import React, { FC } from 'react' import { useFormContext } from 'react-hook-form' import { handleResetState, + setDecisionHistoryModal, setInfoModal, setInfoModalDetails, } from '../../../features/decision/decisionSlice' @@ -22,7 +27,9 @@ export const QuestionHelperCard: FC = ({ }: QuestionHelperCardProps) => { const { reset } = useFormContext() const isMobile = useMediaQuery('(max-width: 965px)') - const { currentTab } = useAppSelector(state => state.decisionSlice) + const { currentTab, decisionHistoryModal } = useAppSelector( + state => state.decisionSlice + ) const handleInfoClick = () => { useAppDispatch( @@ -38,6 +45,10 @@ export const QuestionHelperCard: FC = ({ reset() useAppDispatch(handleResetState()) } + + const handleHistory = () => { + useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) + } return ( = ({
- + - New Decision + Decision History
- + - Explain + New Decision
+ {currentTab !== 5 && ( +
+ + + Explain + +
+ )}
) } diff --git a/components/Decision/common/BaseCard.tsx b/components/Decision/common/BaseCard.tsx index 26c731c..e5238c5 100644 --- a/components/Decision/common/BaseCard.tsx +++ b/components/Decision/common/BaseCard.tsx @@ -3,10 +3,12 @@ import React from 'react' interface BaseCardProps { children: JSX.Element | JSX.Element[] | any className?: string + onClick?: () => void } -export const BaseCard = ({ children, className }: BaseCardProps) => { +export const BaseCard = ({ children, className, onClick }: BaseCardProps) => { return (
= ({ title, }: WrapperTabMenuProps) => { const { reset } = useFormContext() - const { currentTab } = useAppSelector(state => state.decisionSlice) + const { currentTab, decisionHistoryModal } = useAppSelector( + state => state.decisionSlice + ) const handleReset = () => { reset() @@ -40,6 +44,10 @@ export const WrapperTabMenu: FC = ({ ) useAppDispatch(setInfoModal(true)) } + const handleHistory = () => { + useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) + } + return ( = ({ 'custom-box-shadow dark:custom-box-shadow-dark absolute top-9 z-10 w-48 space-y-1 rounded-2xl bg-white p-2 dark:bg-neutralDark-300' } > + handleHistory()} + as="section" + className={`flex cursor-pointer items-center rounded-lg py-2 pr-1 hover:bg-neutral-50 dark:hover:bg-primaryDark/80`} + > + + + Decision History + + + handleReset()} as="section" diff --git a/features/decision/decisionSlice.ts b/features/decision/decisionSlice.ts index 99501ae..f616467 100644 --- a/features/decision/decisionSlice.ts +++ b/features/decision/decisionSlice.ts @@ -45,12 +45,19 @@ const initialState: DecisionSliceStates = { isQuestionSafeForAI: true, userIgnoredUnsafeWarning: false, decisionMatrixHasResults: true, + decisionHistoryModal: false, } export const decisionSlice = createSlice({ name: 'decision', initialState, reducers: { + setDecisionHistoryModal: ( + state, + { payload }: PayloadAction + ) => { + state.decisionHistoryModal = payload + }, handleResetState: state => { state.currentTab = 0 state.matrixStep = 0 @@ -323,6 +330,7 @@ export const { setIsQuestionSafeForAI, setUserIgnoredUnsafeWarning, setDecisionMatrixHasResults, + setDecisionHistoryModal, } = decisionSlice.actions export default decisionSlice.reducer diff --git a/features/interfaces.ts b/features/interfaces.ts index bd8b5f6..308d71e 100644 --- a/features/interfaces.ts +++ b/features/interfaces.ts @@ -67,4 +67,5 @@ export interface DecisionSliceStates { isQuestionSafeForAI: boolean userIgnoredUnsafeWarning: boolean decisionMatrixHasResults: boolean + decisionHistoryModal: boolean } diff --git a/pages/index.tsx b/pages/index.tsx index 835c3db..0103bc8 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,4 @@ import { useUser } from '@auth0/nextjs-auth0' -import { UilQuestionCircle } from '@iconscout/react-unicons' import Cookies from 'js-cookie' import Head from 'next/head' import { useRouter } from 'next/router' @@ -12,6 +11,8 @@ import { DecisionBarHandler } from '../components/Decision/layout/DecisionBarHan import { DecisionSideBar } from '../components/Decision/layout/DecisionSideBar' import { DecisionTabWrapper } from '../components/Decision/layout/DecisionTabWrapper' import OptionRatingTabWrapper from '../components/Decision/layout/OptionRatingTabWrapper' +import { DecisionHistoryModal } from '../components/Decision/Modals/DecisionHistoryModal' +import { TabInfoModal } from '../components/Decision/Modals/TabInfoModal' import { CriteriaInfo } from '../components/Decision/SideCards/CriteriaInfo' import { CriteriaSuggestions } from '../components/Decision/SideCards/CriteriaSuggestions' import { DecisionHelperCard } from '../components/Decision/SideCards/DecisionHelperCard' @@ -27,11 +28,9 @@ import { OptionTab } from '../components/Decision/Tabs/OptionTab' import { RatingTab } from '../components/Decision/Tabs/RatingTab' import { ResultTab } from '../components/Decision/Tabs/ResultTab' import FeedDisclaimer from '../components/Feed/Sidebar/FeedDisclaimer' -import Modal from '../components/Utils/Modal' -import { setInfoModal } from '../features/decision/decisionSlice' import useInstantiateDecisionForm from '../hooks/useInstantiateDecisionForm' import useMediaQuery from '../hooks/useMediaQuery' -import { useAppDispatch, useAppSelector } from '../hooks/useRedux' +import { useAppSelector } from '../hooks/useRedux' import useSaveDecisionFormState from '../hooks/useSaveDecisionFormState' import { bigContainer, decisionContainer } from '../styles/decision' import { @@ -47,8 +46,6 @@ const DecisionEngine: FC = () => { userExceedsMaxDecisions, decisionEngineOptionTab, userIgnoredUnsafeWarning, - isInfoModal, - infoModalDetails, currentTab, matrixStep, } = useAppSelector(state => state.decisionSlice) @@ -287,30 +284,9 @@ const DecisionEngine: FC = () => { {isMobile && currentTab === 0 && ( )} + - useAppDispatch(setInfoModal(!isInfoModal))} - className="md:w-[40%]" - > -
- - - - {currentTab === 0 - ? infoModalDetails.title.split('/')[matrixStep] - : infoModalDetails.title} - - - - {infoModalDetails.context} - -
-
+
) } diff --git a/queries/getDecisionHistory.ts b/queries/getDecisionHistory.ts new file mode 100644 index 0000000..1582cbb --- /dev/null +++ b/queries/getDecisionHistory.ts @@ -0,0 +1,34 @@ +import { collection, getDocs, query, where } from 'firebase/firestore' + +import { db } from '../firebase' +import { DecisionFirebase } from '../utils/types/firebase' + +export const getDecisionHistory = async (deviceIp: string) => { + const decisionActivityRef = query( + collection(db, 'decision-activity'), + where('ipAddress', '==', deviceIp) + ) + const decisionActivity = await getDocs(decisionActivityRef) + const complete: DecisionFirebase[] = [] + const inComplete: DecisionFirebase[] = [] + + decisionActivity.forEach(doc => { + const decisionActivityItem = doc.data() as DecisionFirebase + if ( + decisionActivityItem.isComplete && + complete.length < 5 && + decisionActivityItem?.question + ) { + complete.push(decisionActivityItem) + } + if ( + !decisionActivityItem.isComplete && + inComplete.length < 5 && + decisionActivityItem?.question + ) { + inComplete.push(decisionActivityItem) + } + }) + + return { complete, inComplete } +} diff --git a/utils/types/firebase.ts b/utils/types/firebase.ts index 4f2feb0..82d7a65 100644 --- a/utils/types/firebase.ts +++ b/utils/types/firebase.ts @@ -205,6 +205,27 @@ export interface FirebaseUnauthenticatedDecision { createdAt?: FieldValue updatedAt?: FieldValue } +export interface Timestamp { + seconds: number + nanoseconds: number +} + +export interface DecisionFirebase { + isComplete: boolean + id: string + question: string + criteria: Criteria[] + context: string + clickedConnect: boolean + options: Options[] + suggestedCriteria: Criteria[] + suggestedOptions: Options[] + ipAddress: string + ratings: Rating[] + timestamp: Timestamp + userId: string + currentTab: number +} /** * Type guards From 828c3661f8191a9dd9f45af648f72d57658ba9d8 Mon Sep 17 00:00:00 2001 From: Ihtisham Arif Date: Thu, 21 Jul 2022 23:13:44 +0500 Subject: [PATCH 2/6] PR fixes --- .../Decision/Modals/DecisionHistoryModal.tsx | 92 ++++++++++++------- .../Decision/SideCards/DecisionHelperCard.tsx | 2 +- .../Decision/SideCards/QuestionHelperCard.tsx | 33 ++++--- components/Decision/Tabs/ResultTab.tsx | 2 +- features/decision/decisionSlice.ts | 26 +++++- pages/index.tsx | 2 +- queries/getDecisionHistory.ts | 4 +- 7 files changed, 108 insertions(+), 53 deletions(-) diff --git a/components/Decision/Modals/DecisionHistoryModal.tsx b/components/Decision/Modals/DecisionHistoryModal.tsx index b942fc5..ac4b7e2 100644 --- a/components/Decision/Modals/DecisionHistoryModal.tsx +++ b/components/Decision/Modals/DecisionHistoryModal.tsx @@ -1,5 +1,5 @@ import { UilHistory } from '@iconscout/react-unicons' -import React, { FC, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import { useFormContext } from 'react-hook-form' import { @@ -16,6 +16,7 @@ import { setSideCardStep, setUserIgnoredUnsafeWarning, updateDecisionFormState, + updateFormCopy, } from '../../../features/decision/decisionSlice' import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' import { getDecisionHistory } from '../../../queries/getDecisionHistory' @@ -28,28 +29,25 @@ import { import Modal from '../../Utils/Modal' import { BaseCard } from '../common/BaseCard' -interface DecisionHistoryModalProps { - deviceIp: string -} -export const DecisionHistoryModal: FC = ({ - deviceIp, -}: DecisionHistoryModalProps) => { +export const DecisionHistoryModal = () => { const { decisionHistoryModal, currentTab } = useAppSelector( state => state.decisionSlice ) - const { setValue } = useFormContext() + const { + user: { uid }, + } = useAppSelector(state => state.userSlice) + const { setValue, getValues } = useFormContext() const [completed, setCompleted] = useState([]) const [inComplete, setInComplete] = useState([]) useEffect(() => { - getDecisionHistory(deviceIp).then(({ complete, inComplete }) => { + getDecisionHistory(uid).then(({ complete, inComplete }) => { setCompleted(complete) setInComplete(inComplete) }) }, []) const onHandleSelect = (decision: DecisionFirebase) => { - console.log(decision) // Create copies const decisionCopy = deepCopy(decision) // Set rehydration flags @@ -100,6 +98,9 @@ export const DecisionHistoryModal: FC = ({ } // update current tab if (decision.currentTab && !decision.isComplete) { + if (!decision.currentTab) { + setCurrentTab(1) + } useAppDispatch(setCurrentTab(decision.currentTab)) if (currentTab === 4) useAppDispatch(setDecisionRatingUpdate(true)) } @@ -108,6 +109,17 @@ export const DecisionHistoryModal: FC = ({ useAppDispatch(setCurrentTab(5)) useAppDispatch(setDecisionEngineOptionTab(0)) } + + useAppDispatch( + updateFormCopy( + deepCopy({ + question: getValues('question'), + context: getValues('context'), + options: getValues('options'), + criteria: getValues('criteria'), + }) + ) + ) useAppDispatch(setDecisionHistoryModal(false)) } @@ -140,19 +152,25 @@ export const DecisionHistoryModal: FC = ({ Complete
- {completed.map((item, idx) => ( - onHandleSelect(item)} - className="cursor-pointer !rounded-lg p-4" - key={`${item.id}-complete-item${idx}`} - > - ( + onHandleSelect(item)} + className="cursor-pointer !rounded-lg p-4" + key={`${item.id}-complete-item${idx}`} > - {item.question} - - - ))} + + {item.question} + + + )) + ) : ( + + No Complete decisions + + )}
@@ -162,19 +180,25 @@ export const DecisionHistoryModal: FC = ({ Incomplete
- {inComplete.map((item, idx) => ( - onHandleSelect(item)} - className="cursor-pointer !rounded-lg p-4" - key={`${item.id}-incomplete-item${idx}`} - > - ( + onHandleSelect(item)} + className="cursor-pointer !rounded-lg p-4" + key={`${item.id}-incomplete-item${idx}`} > - {item.question} - - - ))} + + {item.question} + + + )) + ) : ( + + No Incomplete decisions + + )}
diff --git a/components/Decision/SideCards/DecisionHelperCard.tsx b/components/Decision/SideCards/DecisionHelperCard.tsx index b29879f..95767e7 100644 --- a/components/Decision/SideCards/DecisionHelperCard.tsx +++ b/components/Decision/SideCards/DecisionHelperCard.tsx @@ -34,7 +34,7 @@ export const DecisionHelperCard: FC = ({ return (
diff --git a/components/Decision/SideCards/QuestionHelperCard.tsx b/components/Decision/SideCards/QuestionHelperCard.tsx index d151dc6..fcc0aa0 100644 --- a/components/Decision/SideCards/QuestionHelperCard.tsx +++ b/components/Decision/SideCards/QuestionHelperCard.tsx @@ -1,3 +1,4 @@ +import { useUser } from '@auth0/nextjs-auth0' import { UilHistory, UilPlusCircle, @@ -30,6 +31,7 @@ export const QuestionHelperCard: FC = ({ const { currentTab, decisionHistoryModal } = useAppSelector( state => state.decisionSlice ) + const { user } = useUser() const handleInfoClick = () => { useAppDispatch( @@ -49,26 +51,33 @@ export const QuestionHelperCard: FC = ({ const handleHistory = () => { useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) } + return ( -
- - - Decision History - -
+ + + Decision History + +
+ )}
diff --git a/components/Decision/Tabs/ResultTab.tsx b/components/Decision/Tabs/ResultTab.tsx index dbfbc4f..2bad798 100644 --- a/components/Decision/Tabs/ResultTab.tsx +++ b/components/Decision/Tabs/ResultTab.tsx @@ -211,7 +211,7 @@ export const ResultTab: FC = ({ deviceIp }: ResultTabProps) => {
{isMobile ? : ''} - + diff --git a/features/decision/decisionSlice.ts b/features/decision/decisionSlice.ts index f616467..d86bda9 100644 --- a/features/decision/decisionSlice.ts +++ b/features/decision/decisionSlice.ts @@ -61,14 +61,36 @@ export const decisionSlice = createSlice({ handleResetState: state => { state.currentTab = 0 state.matrixStep = 0 - state.decisionQuestion = undefined + state.decisionEngineOptionTab = 0 + state.decisionEngineBestOption = undefined + state.decisionRatingUpdate = true + state.loadingAiSuggestions = false + state.isSuggestionsEmpty = false + state.previousIndex = 1 + state.ratingTabChecker = [] + state.suggestions = { + optionsList: [], + criteriaList: [], + copyOptionsList: [], + copyCriteriaList: [], + } + state.formCopy = { + question: '', + context: '', + options: [], + criteria: [], + } + state.decisionCriteriaQueryKey = undefined state.decisionActivityId = undefined + state.decisionQuestion = undefined + state.criteriaMobileIndex = 0 state.sideCardStep = 1 - state.clickedConnect = false state.decisionFormState = {} state.isDecisionRehydrated = false + state.isThereATie = false state.isQuestionSafeForAI = true state.userIgnoredUnsafeWarning = false + state.decisionMatrixHasResults = true }, setInfoModalDetails: ( state, diff --git a/pages/index.tsx b/pages/index.tsx index 0103bc8..2b90306 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -284,7 +284,7 @@ const DecisionEngine: FC = () => { {isMobile && currentTab === 0 && ( )} - + {user && } diff --git a/queries/getDecisionHistory.ts b/queries/getDecisionHistory.ts index 1582cbb..3ed561a 100644 --- a/queries/getDecisionHistory.ts +++ b/queries/getDecisionHistory.ts @@ -3,10 +3,10 @@ import { collection, getDocs, query, where } from 'firebase/firestore' import { db } from '../firebase' import { DecisionFirebase } from '../utils/types/firebase' -export const getDecisionHistory = async (deviceIp: string) => { +export const getDecisionHistory = async (uid: string) => { const decisionActivityRef = query( collection(db, 'decision-activity'), - where('ipAddress', '==', deviceIp) + where('userId', '==', uid) ) const decisionActivity = await getDocs(decisionActivityRef) const complete: DecisionFirebase[] = [] From b9cba7c16e29f139b3348cc1703e1afd06afd2c4 Mon Sep 17 00:00:00 2001 From: Ihtisham Arif Date: Thu, 21 Jul 2022 23:33:26 +0500 Subject: [PATCH 3/6] PR fixes --- .../Decision/Modals/DecisionHistoryModal.tsx | 35 +++++++++++-------- components/Decision/common/WrapperTabMenu.tsx | 2 +- queries/getDecisionHistory.ts | 7 +--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/components/Decision/Modals/DecisionHistoryModal.tsx b/components/Decision/Modals/DecisionHistoryModal.tsx index ac4b7e2..fbb4540 100644 --- a/components/Decision/Modals/DecisionHistoryModal.tsx +++ b/components/Decision/Modals/DecisionHistoryModal.tsx @@ -20,7 +20,6 @@ import { } from '../../../features/decision/decisionSlice' import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' import { getDecisionHistory } from '../../../queries/getDecisionHistory' -import { body, bodyHeavy } from '../../../styles/typography' import { deepCopy } from '../../../utils/helpers/common' import { DecisionFirebase, @@ -129,9 +128,9 @@ export const DecisionHistoryModal = () => { onClose={() => useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) } - className="md:w-[60%]" + className="!p-3 md:w-[60%] md:!p-5" > -
+
@@ -139,28 +138,30 @@ export const DecisionHistoryModal = () => { Decision History You can view the results of a past decision or return to an incomplete decision. -
- +
+ Complete -
+
{completed.length ? ( completed.map((item, idx) => ( onHandleSelect(item)} - className="cursor-pointer !rounded-lg p-4" + className="cursor-pointer !rounded-lg p-2 md:p-4" key={`${item.id}-complete-item${idx}`} > {item.question} @@ -173,22 +174,26 @@ export const DecisionHistoryModal = () => { )}
- + Incomplete -
+
{inComplete.length ? ( inComplete.map((item, idx) => ( onHandleSelect(item)} - className="cursor-pointer !rounded-lg p-4" + className="cursor-pointer !rounded-lg p-2 md:p-4" key={`${item.id}-incomplete-item${idx}`} > {item.question} diff --git a/components/Decision/common/WrapperTabMenu.tsx b/components/Decision/common/WrapperTabMenu.tsx index 67a3c31..2ffc85e 100644 --- a/components/Decision/common/WrapperTabMenu.tsx +++ b/components/Decision/common/WrapperTabMenu.tsx @@ -78,7 +78,7 @@ export const WrapperTabMenu: FC = ({ > { decisionActivity.forEach(doc => { const decisionActivityItem = doc.data() as DecisionFirebase - if ( - decisionActivityItem.isComplete && - complete.length < 5 && - decisionActivityItem?.question - ) { + if (decisionActivityItem.isComplete && decisionActivityItem?.question) { complete.push(decisionActivityItem) } if ( !decisionActivityItem.isComplete && - inComplete.length < 5 && decisionActivityItem?.question ) { inComplete.push(decisionActivityItem) From bc3be2fec44f8c8ceb50298fdb0fa18845814c01 Mon Sep 17 00:00:00 2001 From: Ihtisham Arif Date: Sat, 23 Jul 2022 19:53:21 +0500 Subject: [PATCH 4/6] PR fixes --- .../Decision/Modals/DecisionHistoryModal.tsx | 6 +- .../Decision/SideCards/QuestionHelperCard.tsx | 12 ++-- components/Decision/SideCards/ScoreCard.tsx | 2 +- components/Decision/common/ResultTable.tsx | 4 +- components/Decision/common/WrapperTabMenu.tsx | 37 ++++++------ .../Decision/layout/DecisionTabWrapper.tsx | 60 +++++++++---------- utils/constants/global.ts | 2 +- 7 files changed, 62 insertions(+), 61 deletions(-) diff --git a/components/Decision/Modals/DecisionHistoryModal.tsx b/components/Decision/Modals/DecisionHistoryModal.tsx index fbb4540..41af7cf 100644 --- a/components/Decision/Modals/DecisionHistoryModal.tsx +++ b/components/Decision/Modals/DecisionHistoryModal.tsx @@ -97,12 +97,12 @@ export const DecisionHistoryModal = () => { } // update current tab if (decision.currentTab && !decision.isComplete) { - if (!decision.currentTab) { - setCurrentTab(1) - } useAppDispatch(setCurrentTab(decision.currentTab)) if (currentTab === 4) useAppDispatch(setDecisionRatingUpdate(true)) + } else if (!decision.currentTab && !decision.isComplete) { + setCurrentTab(1) } + if (decision.isComplete) { useAppDispatch(setPreviousIndex(4)) useAppDispatch(setCurrentTab(5)) diff --git a/components/Decision/SideCards/QuestionHelperCard.tsx b/components/Decision/SideCards/QuestionHelperCard.tsx index fcc0aa0..dfa082d 100644 --- a/components/Decision/SideCards/QuestionHelperCard.tsx +++ b/components/Decision/SideCards/QuestionHelperCard.tsx @@ -28,7 +28,7 @@ export const QuestionHelperCard: FC = ({ }: QuestionHelperCardProps) => { const { reset } = useFormContext() const isMobile = useMediaQuery('(max-width: 965px)') - const { currentTab, decisionHistoryModal } = useAppSelector( + const { currentTab, decisionHistoryModal, matrixStep } = useAppSelector( state => state.decisionSlice ) const { user } = useUser() @@ -58,10 +58,10 @@ export const QuestionHelperCard: FC = ({ isMobile ? 'w-[99%]' : 'w-[98%]' }`} > - {user && ( +
+ {user && matrixStep < 2 && currentTab < 1 && (
= ({
)}
diff --git a/components/Decision/SideCards/ScoreCard.tsx b/components/Decision/SideCards/ScoreCard.tsx index 8a152bf..db2dd38 100644 --- a/components/Decision/SideCards/ScoreCard.tsx +++ b/components/Decision/SideCards/ScoreCard.tsx @@ -18,7 +18,7 @@ export const ScoreCard = () => { const isMobile = useMediaQuery('(max-width: 965px)') return ( - +
setOpen(!isOpen)} diff --git a/components/Decision/common/ResultTable.tsx b/components/Decision/common/ResultTable.tsx index 3c2f326..def9c5c 100644 --- a/components/Decision/common/ResultTable.tsx +++ b/components/Decision/common/ResultTable.tsx @@ -97,7 +97,7 @@ export const ResultTable: FC = ({ '!rounded bg-primary dark:bg-primaryDark text-white border-none shadow-none left-[16%]' } classForParent={'mb-8'} - classForBottomArrow="bg-primary dark:bg-primaryDark border-none relative left-[-20%]" + classForBottomArrow="hidden" > = ({ '!rounded bg-primary dark:bg-primaryDark text-white border-none shadow-none' } classForParent={'mb-8'} - classForBottomArrow="bg-primary dark:bg-primaryDark border-none" + classForBottomArrow="hidden" > {item.name} diff --git a/components/Decision/common/WrapperTabMenu.tsx b/components/Decision/common/WrapperTabMenu.tsx index 2ffc85e..343b540 100644 --- a/components/Decision/common/WrapperTabMenu.tsx +++ b/components/Decision/common/WrapperTabMenu.tsx @@ -1,3 +1,4 @@ +import { useUser } from '@auth0/nextjs-auth0' import { Menu, Transition } from '@headlessui/react' import { UilEllipsisH, @@ -26,7 +27,8 @@ export const WrapperTabMenu: FC = ({ title, }: WrapperTabMenuProps) => { const { reset } = useFormContext() - const { currentTab, decisionHistoryModal } = useAppSelector( + const { user } = useUser() + const { currentTab, decisionHistoryModal, matrixStep } = useAppSelector( state => state.decisionSlice ) @@ -81,23 +83,24 @@ export const WrapperTabMenu: FC = ({ 'custom-box-shadow dark:custom-box-shadow-dark absolute top-9 z-50 w-48 space-y-1 rounded-2xl bg-white p-2 dark:bg-neutralDark-300' } > - handleHistory()} - as="section" - className={`flex cursor-pointer items-center rounded-lg py-2 pr-1 hover:bg-neutral-50 dark:hover:bg-primaryDark/80`} - > - - handleHistory()} + as="section" + className={`flex cursor-pointer items-center rounded-lg py-2 pr-1 hover:bg-neutral-50 dark:hover:bg-primaryDark/80`} > - Decision History - - - + + + Decision History + + + )} handleReset()} as="section" diff --git a/components/Decision/layout/DecisionTabWrapper.tsx b/components/Decision/layout/DecisionTabWrapper.tsx index 72139a1..76236b7 100644 --- a/components/Decision/layout/DecisionTabWrapper.tsx +++ b/components/Decision/layout/DecisionTabWrapper.tsx @@ -161,36 +161,36 @@ export const DecisionTabWrapper: FC = ({ {!isMobile && currentTab !== 4 && ( )} - {currentTab !== 5 ? ( -

- {currentTab === 0 ? title.split('/')[matrixStep] : title} - {isMobile && } - {!isMobile && - decisionMatrixHasResults && - currentTab === 0 && - matrixStep === 1 && ( - - )} -

- ) : null} + {/* {currentTab !== 5 ? ( */} +

+ {currentTab === 0 ? title.split('/')[matrixStep] : title} + {isMobile && } + {!isMobile && + decisionMatrixHasResults && + currentTab === 0 && + matrixStep === 1 && ( + + )} +

+ {/* ) : null} */} {children}
) diff --git a/utils/constants/global.ts b/utils/constants/global.ts index 51a8928..f16b6da 100644 --- a/utils/constants/global.ts +++ b/utils/constants/global.ts @@ -35,7 +35,7 @@ export const decisionTitle = [ 'Your criteria', 'Your options', 'Your ratings', - '', + 'Your result', ] export const decisionInfo = [ From be5d659606098b9a19a26bbdbdefe6ec370d69e9 Mon Sep 17 00:00:00 2001 From: Ihtisham Arif Date: Wed, 27 Jul 2022 01:15:46 +0500 Subject: [PATCH 5/6] Added tooltip to table header --- components/Decision/common/ResultTable.tsx | 30 +++++++++++++--------- components/Utils/Tooltip.tsx | 6 ++++- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/components/Decision/common/ResultTable.tsx b/components/Decision/common/ResultTable.tsx index def9c5c..9f7f1be 100644 --- a/components/Decision/common/ResultTable.tsx +++ b/components/Decision/common/ResultTable.tsx @@ -90,7 +90,7 @@ export const ResultTable: FC = ({ className={`${bodySmall} mr-4 flex w-1/3 flex-col items-start rounded-lg bg-primary/20 py-1.5 px-2 text-primary dark:text-primaryDark`} > - {item.name.split('').length > 14 ? ( + {item.name.split('').length > 12 ? ( = ({ }} > - + = ({ {rating.map((item: Ratings, index: number) => ( - - {item.option} - + <> + + {item.option.split('').length > 14 && ( + + {item.option} + + )} + {item.option} + + ))} @@ -187,7 +194,7 @@ export const ResultTable: FC = ({ - {item.name.split('').length > 16 ? ( + {item.name.split('').length > 14 ? ( = ({ {item.name} )} - {weightToString(item.weight)} diff --git a/components/Utils/Tooltip.tsx b/components/Utils/Tooltip.tsx index fd08abd..b31666b 100644 --- a/components/Utils/Tooltip.tsx +++ b/components/Utils/Tooltip.tsx @@ -7,6 +7,7 @@ interface TooltipProps { classForParent?: string classForToolTipBox?: string classForBottomArrow?: string + removeFlexFromParent?: boolean } export const Tooltip: FC< @@ -18,10 +19,13 @@ export const Tooltip: FC< classForParent = '', classForToolTipBox = '', classForBottomArrow = '', + removeFlexFromParent = false, }: TooltipProps) => { return (
{children}
Date: Fri, 29 Jul 2022 13:58:08 -0400 Subject: [PATCH 6/6] convert decision history query to react hooks; fix bug in trigger logic for query; make decision history modal lazy load; remove unnecessary code --- .../Decision/Modals/DecisionHistoryModal.tsx | 187 +++++++++++------- components/Decision/common/WrapperTabMenu.tsx | 2 +- hooks/useInstantiateDecisionForm.tsx | 130 +----------- pages/index.tsx | 5 +- queries/getDecisionHistory.ts | 29 --- utils/constants/global.ts | 1 - 6 files changed, 123 insertions(+), 231 deletions(-) delete mode 100644 queries/getDecisionHistory.ts diff --git a/components/Decision/Modals/DecisionHistoryModal.tsx b/components/Decision/Modals/DecisionHistoryModal.tsx index 41af7cf..2bbbfde 100644 --- a/components/Decision/Modals/DecisionHistoryModal.tsx +++ b/components/Decision/Modals/DecisionHistoryModal.tsx @@ -1,5 +1,5 @@ import { UilHistory } from '@iconscout/react-unicons' -import React, { useEffect, useState } from 'react' +import React, { Fragment, useRef, useState } from 'react' import { useFormContext } from 'react-hook-form' import { @@ -18,13 +18,15 @@ import { updateDecisionFormState, updateFormCopy, } from '../../../features/decision/decisionSlice' +import useIntersectionObserver from '../../../hooks/useIntersectionObserver' import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' -import { getDecisionHistory } from '../../../queries/getDecisionHistory' +import { useInfiniteDecisionsQuery } from '../../../queries/decisionActivity' import { deepCopy } from '../../../utils/helpers/common' +import { FirebaseDecisionActivity } from '../../../utils/types/firebase' import { - DecisionFirebase, - FirebaseDecisionActivity, -} from '../../../utils/types/firebase' + GenerateNotificationLoaders, + NotificationLoader, +} from '../../Header/Notifications/NotificationLoaders' import Modal from '../../Utils/Modal' import { BaseCard } from '../common/BaseCard' @@ -36,19 +38,32 @@ export const DecisionHistoryModal = () => { user: { uid }, } = useAppSelector(state => state.userSlice) const { setValue, getValues } = useFormContext() - const [completed, setCompleted] = useState([]) - const [inComplete, setInComplete] = useState([]) + const [showComplete, setShowComplete] = useState(false) - useEffect(() => { - getDecisionHistory(uid).then(({ complete, inComplete }) => { - setCompleted(complete) - setInComplete(inComplete) - }) - }, []) + const { status, data, isFetchingNextPage, fetchNextPage, hasNextPage } = + useInfiniteDecisionsQuery(uid, showComplete, decisionHistoryModal) - const onHandleSelect = (decision: DecisionFirebase) => { + // Instantiate intersection observer + const loadMoreRef = useRef(null) + useIntersectionObserver({ + threshold: 0.3, + target: loadMoreRef, + onIntersect: fetchNextPage, + enabled: !!hasNextPage, + }) + + // Handler functions + const showCompleteDecisions = () => { + setShowComplete(true) + } + const showIncompleteDecisions = () => { + setShowComplete(false) + } + + const onHandleSelect = (decision: FirebaseDecisionActivity) => { // Create copies const decisionCopy = deepCopy(decision) + // Set rehydration flags useAppDispatch(setIsDecisionRehydrated(true)) // remove extra fields @@ -97,10 +112,14 @@ export const DecisionHistoryModal = () => { } // update current tab if (decision.currentTab && !decision.isComplete) { - useAppDispatch(setCurrentTab(decision.currentTab)) + // Needed for forward compatibility of decisions made prior to us + // switching the order of entering options and criteria. + if (!('criteria' in decision)) { + useAppDispatch(setCurrentTab(2)) + } else { + useAppDispatch(setCurrentTab(decision.currentTab)) + } if (currentTab === 4) useAppDispatch(setDecisionRatingUpdate(true)) - } else if (!decision.currentTab && !decision.isComplete) { - setCurrentTab(1) } if (decision.isComplete) { @@ -144,65 +163,93 @@ export const DecisionHistoryModal = () => { incomplete decision.
- - - Complete - -
- {completed.length ? ( - completed.map((item, idx) => ( - onHandleSelect(item)} - className="cursor-pointer !rounded-lg p-2 md:p-4" - key={`${item.id}-complete-item${idx}`} - > - - {item.question} - - - )) - ) : ( - - No Complete decisions - - )} + +
+ +
-
- - - Incomplete - -
- {inComplete.length ? ( - inComplete.map((item, idx) => ( - onHandleSelect(item)} - className="cursor-pointer !rounded-lg p-2 md:p-4" - key={`${item.id}-incomplete-item${idx}`} - > - + ) : status === 'error' ? ( +
Error loading decisions
+ ) : ( + + {/* Infinite scroller / lazy loader */} + {data?.pages.map(page => ( + - {item.question} + {/* If notifications exist */} + {page.decisions?.length > 0 && + page.decisions.map( + ( + decision: FirebaseDecisionActivity + ) => ( + + onHandleSelect( + decision + ) + } + className="cursor-pointer !rounded-lg p-2 md:p-4" + key={decision.id} + > + + { + decision.question + } + + + ) + )} + + ))} + + {/* Lazy loaader sentinel and end of decisions */} + {isFetchingNextPage || hasNextPage ? ( + + ) : data?.pages[0].decisions && + data?.pages[0].decisions.length === 0 ? ( + + {`No ${ + showComplete + ? 'Complete' + : 'Incomplete' + } decisions`} -
- )) - ) : ( - - No Incomplete decisions - + ) : ( + <> + )} + )}
diff --git a/components/Decision/common/WrapperTabMenu.tsx b/components/Decision/common/WrapperTabMenu.tsx index 343b540..c6ea551 100644 --- a/components/Decision/common/WrapperTabMenu.tsx +++ b/components/Decision/common/WrapperTabMenu.tsx @@ -85,7 +85,7 @@ export const WrapperTabMenu: FC = ({ > {user && matrixStep < 2 && currentTab < 1 && ( handleHistory()} + onClick={handleHistory} as="section" className={`flex cursor-pointer items-center rounded-lg py-2 pr-1 hover:bg-neutral-50 dark:hover:bg-primaryDark/80`} > diff --git a/hooks/useInstantiateDecisionForm.tsx b/hooks/useInstantiateDecisionForm.tsx index 9568f7d..b9bcd5b 100644 --- a/hooks/useInstantiateDecisionForm.tsx +++ b/hooks/useInstantiateDecisionForm.tsx @@ -1,35 +1,11 @@ import { useEffect } from 'react' import { useForm } from 'react-hook-form' -import { - setClickedConnect, - setCurrentTab, - setDecisionActivityId, - setDecisionFormState, - setDecisionQuestion, - setDecisionRatingUpdate, - setIsDecisionRehydrated, - setIsQuestionSafeForAI, - setSideCardStep, - setUserIgnoredUnsafeWarning, - updateDecisionFormState, -} from '../features/decision/decisionSlice' -import { useInfiniteDecisionsQuery } from '../queries/decisionActivity' -import { deepCopy } from '../utils/helpers/common' -import { FirebaseDecisionActivity } from '../utils/types/firebase' +import { handleResetState } from '../features/decision/decisionSlice' import { DecisionForm } from '../utils/types/global' -import { useAppDispatch, useAppSelector } from './useRedux' - -interface UseInstantiateDecisionFormProps { - rehydrate: boolean -} - -const useInstantiateDecisionForm = ({ - rehydrate, -}: UseInstantiateDecisionFormProps) => { - const currentTab = useAppSelector(state => state.decisionSlice.currentTab) - const userProfile = useAppSelector(state => state.userSlice.user) +import { useAppDispatch } from './useRedux' +const useInstantiateDecisionForm = () => { // Rehydrate form state from stored values const methods = useForm({ defaultValues: { @@ -47,109 +23,11 @@ const useInstantiateDecisionForm = ({ }, }) - if (rehydrate) { - const { setValue } = methods - const { isFetched, isSuccess } = useInfiniteDecisionsQuery( - userProfile.uid, - undefined, - userProfile.uid !== '', // only enable the call if the userProfile.uid is defined - retrievedData => { - if (!retrievedData.pages[0].decisions[0].isComplete) { - // Set rehydration flags - useAppDispatch(setIsDecisionRehydrated(true)) - - // Create copies - const incompleteDecision = deepCopy( - retrievedData.pages[0].decisions[0] - ) - // remove extra fields - const extraFields = [ - 'timestamp', - 'currentTab', - 'id', - 'userId', - 'isComplete', - 'ipAddress', - ] - for (const field of extraFields) { - delete incompleteDecision[field] - } - - // set values - const formState: FirebaseDecisionActivity = { - id: retrievedData.pages[0].decisions[0].id, - } - for (const [key, value] of Object.entries( - incompleteDecision as FirebaseDecisionActivity - )) { - if (key !== 'clickedConnect') { - setValue(key, deepCopy(value), { - shouldValidate: true, - shouldDirty: true, - }) - } - formState[key] = deepCopy(value) - } - // Set form state in redux - useAppDispatch( - setDecisionActivityId( - retrievedData.pages[0].decisions[0].id - ) - ) - useAppDispatch( - setDecisionQuestion(incompleteDecision.question) - ) - useAppDispatch( - setClickedConnect(incompleteDecision.clickedConnect) - ) - useAppDispatch( - setUserIgnoredUnsafeWarning( - incompleteDecision.userIgnoredUnsafeWarning - ) - ) - useAppDispatch( - setIsQuestionSafeForAI( - incompleteDecision.isQuestionSafeForAI - ) - ) - if (incompleteDecision.clickedConnect) - useAppDispatch(setSideCardStep(2)) - useAppDispatch(updateDecisionFormState(formState)) - - // update current tab - if (retrievedData.pages[0].decisions[0].currentTab) { - useAppDispatch( - setCurrentTab( - retrievedData.pages[0].decisions[0].currentTab - ) - ) - if (currentTab === 4) - useAppDispatch(setDecisionRatingUpdate(true)) - } - } - } - ) - - // Update ratings when user navigates back to decision engine tab - // and form is rehydrated - useEffect(() => { - if (isFetched && isSuccess) - useAppDispatch(setDecisionRatingUpdate(true)) - }, []) - } - // Clear redux state on unmount useEffect(() => { return () => { // Wipe previous decision question and id - useAppDispatch(setDecisionQuestion(undefined)) - useAppDispatch(setDecisionActivityId(undefined)) - useAppDispatch(setSideCardStep(1)) - useAppDispatch(setClickedConnect(false)) - useAppDispatch(setDecisionFormState({})) - useAppDispatch(setIsDecisionRehydrated(false)) - useAppDispatch(setIsQuestionSafeForAI(true)) - useAppDispatch(setUserIgnoredUnsafeWarning(false)) + useAppDispatch(handleResetState()) } }, []) diff --git a/pages/index.tsx b/pages/index.tsx index 2b90306..a17fe9f 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -36,7 +36,6 @@ import { bigContainer, decisionContainer } from '../styles/decision' import { decisionSideBarOptions, decisionTitle, - rehydrateDecisionForm, } from '../utils/constants/global' import { insertAtArray } from '../utils/helpers/common' @@ -56,9 +55,7 @@ const DecisionEngine: FC = () => { const router = useRouter() // Instantiate form - const methods = useInstantiateDecisionForm({ - rehydrate: rehydrateDecisionForm, - }) + const methods = useInstantiateDecisionForm() const { control, getValues, setValue } = methods const watchQuestion = useWatch({ name: 'question', control }) const optionList = getValues('options') diff --git a/queries/getDecisionHistory.ts b/queries/getDecisionHistory.ts deleted file mode 100644 index bcb696b..0000000 --- a/queries/getDecisionHistory.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { collection, getDocs, query, where } from 'firebase/firestore' - -import { db } from '../firebase' -import { DecisionFirebase } from '../utils/types/firebase' - -export const getDecisionHistory = async (uid: string) => { - const decisionActivityRef = query( - collection(db, 'decision-activity'), - where('userId', '==', uid) - ) - const decisionActivity = await getDocs(decisionActivityRef) - const complete: DecisionFirebase[] = [] - const inComplete: DecisionFirebase[] = [] - - decisionActivity.forEach(doc => { - const decisionActivityItem = doc.data() as DecisionFirebase - if (decisionActivityItem.isComplete && decisionActivityItem?.question) { - complete.push(decisionActivityItem) - } - if ( - !decisionActivityItem.isComplete && - decisionActivityItem?.question - ) { - inComplete.push(decisionActivityItem) - } - }) - - return { complete, inComplete } -} diff --git a/utils/constants/global.ts b/utils/constants/global.ts index f16b6da..cdf4a80 100644 --- a/utils/constants/global.ts +++ b/utils/constants/global.ts @@ -58,7 +58,6 @@ export const decisionInfoParagraph = [ export const criteriaInfoCacheDays = 21 export const maxAllowedUnauthenticatedDecisions = 2 -export const rehydrateDecisionForm = false /** * Decision Page