Add functionality to go next and back on questions

pull/5774/head
Kamran Ahmed 6 months ago
parent a14d8b5f90
commit 4c615f85e5
  1. 36
      src/components/Questions/QuestionFinished.tsx
  2. 99
      src/components/Questions/QuestionsList.tsx
  3. 40
      src/components/Questions/QuestionsProgress.tsx

@ -13,28 +13,19 @@ type ProgressStatButtonProps = {
icon: ReactNode; icon: ReactNode;
label: string; label: string;
count: number; count: number;
onClick: () => void;
}; };
function ProgressStatButton(props: ProgressStatButtonProps) { function ProgressStatLabel(props: ProgressStatButtonProps) {
const { icon, label, count, onClick, isDisabled = false } = props; const { icon, label, count } = props;
return ( return (
<button <span className="group relative flex flex-1 items-center overflow-hidden rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors disabled:opacity-50 sm:rounded-xl sm:px-4 sm:py-3 sm:text-base">
disabled={isDisabled}
onClick={onClick}
className="group relative flex flex-1 items-center overflow-hidden rounded-md border border-gray-300 bg-white px-2 py-2 text-sm text-black transition-colors hover:border-black disabled:pointer-events-none disabled:opacity-50 sm:rounded-xl sm:px-4 sm:py-3 sm:text-base"
>
{icon} {icon}
<span className="flex flex-grow justify-between"> <span className="flex flex-grow justify-between">
<span>{label}</span> <span>{label}</span>
<span>{count}</span> <span>{count}</span>
</span> </span>
</span>
<span className="absolute left-0 right-0 top-full flex h-full items-center justify-center border border-black bg-black text-white transition-all duration-200 group-hover:top-0">
Restart Asking
</span>
</button>
); );
} }
@ -43,12 +34,11 @@ type QuestionFinishedProps = {
didNotKnowCount: number; didNotKnowCount: number;
skippedCount: number; skippedCount: number;
totalCount: number; totalCount: number;
onReset: (type: QuestionProgressType | 'reset') => void; onReset: () => void;
}; };
export function QuestionFinished(props: QuestionFinishedProps) { export function QuestionFinished(props: QuestionFinishedProps) {
const { knowCount, didNotKnowCount, skippedCount, totalCount, onReset } = const { knowCount, didNotKnowCount, skippedCount, onReset } = props;
props;
return ( return (
<div className="relative flex flex-grow flex-col items-center justify-center px-4 sm:px-0"> <div className="relative flex flex-grow flex-col items-center justify-center px-4 sm:px-0">
@ -63,31 +53,25 @@ export function QuestionFinished(props: QuestionFinishedProps) {
</p> </p>
<div className="mb-5 mt-5 flex w-full flex-col gap-1.5 px-2 sm:flex-row sm:gap-3 sm:px-16"> <div className="mb-5 mt-5 flex w-full flex-col gap-1.5 px-2 sm:flex-row sm:gap-3 sm:px-16">
<ProgressStatButton <ProgressStatLabel
icon={<ThumbsUp className="mr-1 h-4" />} icon={<ThumbsUp className="mr-1 h-4" />}
label="Knew" label="Knew"
count={knowCount} count={knowCount}
isDisabled={knowCount === 0}
onClick={() => onReset('know')}
/> />
<ProgressStatButton <ProgressStatLabel
icon={<Sparkles className="mr-1 h-4" />} icon={<Sparkles className="mr-1 h-4" />}
label="Learned" label="Learned"
count={didNotKnowCount} count={didNotKnowCount}
isDisabled={didNotKnowCount === 0}
onClick={() => onReset('dontKnow')}
/> />
<ProgressStatButton <ProgressStatLabel
icon={<SkipForward className="mr-1 h-4" />} icon={<SkipForward className="mr-1 h-4" />}
label="Skipped" label="Skipped"
count={skippedCount} count={skippedCount}
isDisabled={skippedCount === 0}
onClick={() => onReset('skip')}
/> />
</div> </div>
<div className="mb-4 mt-2 text-sm sm:mb-0"> <div className="mb-4 mt-2 text-sm sm:mb-0">
<button <button
onClick={() => onReset('reset')} onClick={() => onReset()}
className="flex items-center gap-0.5 text-sm text-red-700 hover:text-black sm:text-base" className="flex items-center gap-0.5 text-sm text-red-700 hover:text-black sm:text-base"
> >
<RefreshCcw className="mr-1 h-4" /> <RefreshCcw className="mr-1 h-4" />

@ -24,14 +24,14 @@ type QuestionsListProps = {
}; };
export function QuestionsList(props: QuestionsListProps) { export function QuestionsList(props: QuestionsListProps) {
const { questions: unshuffledQuestions, groupId } = props; const { questions: defaultQuestions, groupId } = props;
const toast = useToast(); const toast = useToast();
const [questions, setQuestions] = useState(defaultQuestions);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [showConfetti, setShowConfetti] = useState(false); const [showConfetti, setShowConfetti] = useState(false);
const [questions, setQuestions] = useState<QuestionType[]>(); const [currQuestionIndex, setCurrQuestionIndex] = useState(0);
const [pendingQuestions, setPendingQuestions] = useState<QuestionType[]>([]);
const [userProgress, setUserProgress] = useState<UserQuestionProgress>(); const [userProgress, setUserProgress] = useState<UserQuestionProgress>();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -57,7 +57,7 @@ export function QuestionsList(props: QuestionsListProps) {
return response; return response;
} }
async function loadQuestions() { async function prepareProgress() {
const userProgress = await fetchUserProgress(); const userProgress = await fetchUserProgress();
setUserProgress(userProgress); setUserProgress(userProgress);
@ -65,7 +65,7 @@ export function QuestionsList(props: QuestionsListProps) {
const didNotKnowQuestions = userProgress?.dontKnow || []; const didNotKnowQuestions = userProgress?.dontKnow || [];
const skipQuestions = userProgress?.skip || []; const skipQuestions = userProgress?.skip || [];
const pendingQuestions = unshuffledQuestions.filter((question) => { const pendingQuestionIndex = questions.findIndex((question) => {
return ( return (
!knownQuestions.includes(question.id) && !knownQuestions.includes(question.id) &&
!didNotKnowQuestions.includes(question.id) && !didNotKnowQuestions.includes(question.id) &&
@ -73,31 +73,21 @@ export function QuestionsList(props: QuestionsListProps) {
); );
}); });
// Shuffle and set pending questions setCurrQuestionIndex(pendingQuestionIndex);
// setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
setPendingQuestions(pendingQuestions);
setQuestions(unshuffledQuestions);
setIsLoading(false); setIsLoading(false);
} }
async function resetProgress(type: QuestionProgressType | 'reset' = 'reset') { async function resetProgress() {
let knownQuestions = userProgress?.know || []; let knownQuestions = userProgress?.know || [];
let didNotKnowQuestions = userProgress?.dontKnow || []; let didNotKnowQuestions = userProgress?.dontKnow || [];
let skipQuestions = userProgress?.skip || []; let skipQuestions = userProgress?.skip || [];
if (!isLoggedIn()) { if (!isLoggedIn()) {
if (type === 'know') { setQuestions(defaultQuestions);
knownQuestions = [];
} else if (type === 'dontKnow') { knownQuestions = [];
didNotKnowQuestions = []; didNotKnowQuestions = [];
} else if (type === 'skip') { skipQuestions = [];
skipQuestions = [];
} else if (type === 'reset') {
knownQuestions = [];
didNotKnowQuestions = [];
skipQuestions = [];
}
} else { } else {
setIsLoading(true); setIsLoading(true);
@ -106,7 +96,7 @@ export function QuestionsList(props: QuestionsListProps) {
import.meta.env.PUBLIC_API_URL import.meta.env.PUBLIC_API_URL
}/v1-reset-question-progress/${groupId}`, }/v1-reset-question-progress/${groupId}`,
{ {
status: type, status: 'reset',
}, },
); );
@ -120,21 +110,13 @@ export function QuestionsList(props: QuestionsListProps) {
skipQuestions = response?.skip || []; skipQuestions = response?.skip || [];
} }
const pendingQuestions = unshuffledQuestions.filter((question) => { setCurrQuestionIndex(0);
return (
!knownQuestions.includes(question.id) &&
!didNotKnowQuestions.includes(question.id) &&
!skipQuestions.includes(question.id)
);
});
setUserProgress({ setUserProgress({
know: knownQuestions, know: knownQuestions,
dontKnow: didNotKnowQuestions, dontKnow: didNotKnowQuestions,
skip: skipQuestions, skip: skipQuestions,
}); });
setPendingQuestions(pendingQuestions.sort(() => Math.random() - 0.5));
setIsLoading(false); setIsLoading(false);
} }
@ -173,30 +155,29 @@ export function QuestionsList(props: QuestionsListProps) {
newProgress = response; newProgress = response;
} }
const updatedQuestionList = pendingQuestions.filter( const nextQuestionIndex = currQuestionIndex + 1;
(q) => q.id !== questionId,
);
setUserProgress(newProgress); setUserProgress(newProgress);
setPendingQuestions(updatedQuestionList);
setIsLoading(false); setIsLoading(false);
if (updatedQuestionList.length === 0) { if (!nextQuestionIndex || !questions[nextQuestionIndex]) {
setShowConfetti(true); setShowConfetti(true);
} }
setCurrQuestionIndex(nextQuestionIndex);
} }
useEffect(() => { useEffect(() => {
loadQuestions().then(() => null); prepareProgress().then(() => null);
}, [unshuffledQuestions]); }, [questions]);
const knowCount = userProgress?.know.length || 0; const knowCount = userProgress?.know.length || 0;
const dontKnowCount = userProgress?.dontKnow.length || 0; const dontKnowCount = userProgress?.dontKnow.length || 0;
const skipCount = userProgress?.skip.length || 0; const skipCount = userProgress?.skip.length || 0;
const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0; const hasProgress = knowCount > 0 || dontKnowCount > 0 || skipCount > 0;
const currQuestion = pendingQuestions[0]; const currQuestion = questions[currQuestionIndex];
const hasFinished = !isLoading && hasProgress && !currQuestion; const hasFinished = !isLoading && hasProgress && currQuestionIndex === -1;
return ( return (
<div className="mb-0 gap-3 text-center sm:mb-40"> <div className="mb-0 gap-3 text-center sm:mb-40">
@ -204,11 +185,37 @@ export function QuestionsList(props: QuestionsListProps) {
knowCount={knowCount} knowCount={knowCount}
didNotKnowCount={dontKnowCount} didNotKnowCount={dontKnowCount}
skippedCount={skipCount} skippedCount={skipCount}
totalCount={unshuffledQuestions?.length || questions?.length} totalCount={questions?.length}
isLoading={isLoading} isLoading={isLoading}
showLoginAlert={!isLoggedIn() && hasProgress} showLoginAlert={!isLoggedIn() && hasProgress}
onResetClick={() => { onResetClick={() => {
resetProgress('reset').finally(() => null); resetProgress().finally(() => null);
}}
onNextClick={() => {
if (
currQuestionIndex !== -1 &&
currQuestionIndex < questions.length - 1
) {
updateQuestionStatus('skip', currQuestion.id).finally(() => null);
}
}}
onPrevClick={() => {
if (currQuestionIndex > 0) {
const prevQuestion = questions[currQuestionIndex - 1];
// remove last question from the progress of the user
const tempUserProgress = {
know:
userProgress?.know.filter((id) => id !== prevQuestion.id) || [],
dontKnow:
userProgress?.dontKnow.filter((id) => id !== prevQuestion.id) ||
[],
skip:
userProgress?.skip.filter((id) => id !== prevQuestion.id) || [],
};
setUserProgress(tempUserProgress);
setCurrQuestionIndex(currQuestionIndex - 1);
}
}} }}
/> />
@ -228,12 +235,12 @@ export function QuestionsList(props: QuestionsListProps) {
> >
{hasFinished && ( {hasFinished && (
<QuestionFinished <QuestionFinished
totalCount={unshuffledQuestions?.length || questions?.length || 0} totalCount={questions?.length || 0}
knowCount={knowCount} knowCount={knowCount}
didNotKnowCount={dontKnowCount} didNotKnowCount={dontKnowCount}
skippedCount={skipCount} skippedCount={skipCount}
onReset={(type: QuestionProgressType | 'reset') => { onReset={() => {
resetProgress(type).finally(() => null); resetProgress().finally(() => null);
}} }}
/> />
)} )}

@ -1,4 +1,11 @@
import { CheckCircle, RotateCcw, SkipForward, Sparkles } from 'lucide-react'; import {
CheckCircle,
ChevronLeft,
ChevronRight,
RotateCcw,
SkipForward,
Sparkles,
} from 'lucide-react';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
type QuestionsProgressProps = { type QuestionsProgressProps = {
@ -9,6 +16,8 @@ type QuestionsProgressProps = {
totalCount?: number; totalCount?: number;
skippedCount?: number; skippedCount?: number;
onResetClick?: () => void; onResetClick?: () => void;
onPrevClick?: () => void;
onNextClick?: () => void;
}; };
export function QuestionsProgress(props: QuestionsProgressProps) { export function QuestionsProgress(props: QuestionsProgressProps) {
@ -20,6 +29,8 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
totalCount = 0, totalCount = 0,
skippedCount = 0, skippedCount = 0,
onResetClick = () => null, onResetClick = () => null,
onPrevClick = () => null,
onNextClick = () => null,
} = props; } = props;
const totalSolved = knowCount + didNotKnowCount + skippedCount; const totalSolved = knowCount + didNotKnowCount + skippedCount;
@ -36,8 +47,22 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
}} }}
/> />
</div> </div>
<span className="ml-3 text-sm"> <span className="ml-3 flex items-center text-sm">
{totalSolved} / {totalCount} <button
onClick={onPrevClick}
className="text-zinc-400 hover:text-black"
>
<ChevronLeft className="h-4" strokeWidth={3} />
</button>
<span className="block min-w-[41px] text-center">
<span className="tabular-nums">{totalSolved}</span> / {totalCount}
</span>
<button
onClick={onNextClick}
className="text-zinc-400 hover:text-black"
>
<ChevronRight className="h-4" strokeWidth={3} />
</button>
</span> </span>
</div> </div>
@ -46,8 +71,7 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
<CheckCircle className="mr-1 h-4" /> <CheckCircle className="mr-1 h-4" />
<span>Knew</span> <span>Knew</span>
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black"> <span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
<span className="tabular-nums">{knowCount}</span>{' '} <span className="tabular-nums">{knowCount}</span> Items
Items
</span> </span>
</span> </span>
@ -55,8 +79,7 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
<Sparkles className="mr-1 h-4" /> <Sparkles className="mr-1 h-4" />
<span>Learnt</span> <span>Learnt</span>
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black"> <span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
<span className="tabular-nums">{didNotKnowCount}</span>{' '} <span className="tabular-nums">{didNotKnowCount}</span> Items
Items
</span> </span>
</span> </span>
@ -64,8 +87,7 @@ export function QuestionsProgress(props: QuestionsProgressProps) {
<SkipForward className="mr-1 h-4" /> <SkipForward className="mr-1 h-4" />
<span>Skipped</span> <span>Skipped</span>
<span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black"> <span className="ml-2 rounded-md bg-gray-200/80 px-1.5 font-medium text-black">
<span className="tabular-nums">{skippedCount}</span>{' '} <span className="tabular-nums">{skippedCount}</span> Items
Items
</span> </span>
</span> </span>

Loading…
Cancel
Save