273 lines
8.2 KiB
TypeScript

import { QuestionTypes } from "@/api/types";
import {
useAnswerMutation,
useCompleteTestMutation,
useUserTest,
} from "@/api/userTests";
import Question from "@/components/question";
import {
AlertDialog,
AlertDialogBackdrop,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
} from "@/components/ui/alert-dialog";
import Answer from "@/components/ui/answer";
import { Button, ButtonText } from "@/components/ui/button";
import Content from "@/components/ui/content";
import CustomButton from "@/components/ui/custom-button";
import { Divider } from "@/components/ui/divider";
import Panel from "@/components/ui/panel";
import { Textarea, TextareaInput } from "@/components/ui/textarea";
import { ThemedText } from "@/components/ui/themed-text";
import getErrorAxiosMessage from "@/utils/get-error-axios-message";
import getUserTestTitle from "@/utils/get-user-test-title";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { ScrollView, StyleSheet, View } from "react-native";
import { useToast } from "react-native-toast-notifications";
const DoingUserTestScreen = () => {
const { id: idParam } = useLocalSearchParams<{ id: string }>();
const [answer, setAnswer] = useState<(string | number)[]>([]);
const [page, setPage] = useState(0);
const [openCompleteDialog, setOpenCompleteDialog] = useState(false);
const id = +idParam;
const { data: userTest, refetch: refetchUserTest } = useUserTest(id);
const { mutateAsync: mutateAsyncAnswer, isPending: answerPending } =
useAnswerMutation();
const { mutate: mutateCompleteTest, isPending: completePending } = useCompleteTestMutation();
const toast = useToast();
useEffect(() => {
if (!userTest) return;
setAnswer(userTest.answers[page].answer || []);
}, [userTest, page]);
if (!userTest)
return (
<>
<Stack.Screen options={{ title: `Test #${id}` }} />
<Content>
<Question />
</Content>
</>
);
const currentAnswer = userTest.answers[page];
const currentQuestion = currentAnswer.question;
const pages = Array.from({ length: userTest.answers.length }).map(
(_, i) => i
);
const countAnsweredQuestion = userTest.answers.filter(
(answer) => answer.answer
).length;
const isAllAnswered = countAnsweredQuestion === userTest.answers.length;
const isLastPage = page === pages.length - 1;
const userTextAnswer = answer ? (answer[0] as string) : "";
const alertDialogMessage = isAllAnswered
? "Finish the test to see your results."
: "Warning: You haven't answered all questions. Are you sure you want to finish?";
const handleSetAnswer = (value: number | string) => {
if (currentQuestion.type === QuestionTypes.Text) {
setAnswer([value]);
}
if (currentQuestion.type === QuestionTypes.Single) {
setAnswer([value as number]);
}
if (currentQuestion.type === QuestionTypes.Multiple) {
if ((answer as number[]).includes(value as number)) {
setAnswer((prev) => prev.filter((v) => v !== value));
} else {
setAnswer((prev) => {
const set = new Set(prev as number[]);
set.add(value as number);
return Array.from(set);
});
}
}
};
const handleSaveAnswer = async () => {
let ans = answer;
if (currentQuestion.type === QuestionTypes.Text && answer) {
ans = [(answer[0] as string).trim()];
}
try {
await mutateAsyncAnswer(
{
answerId: currentAnswer.id,
answer: ans,
},
{
onSuccess: () => {
toast.show("Your answer was saved", { type: "success" });
if(!isLastPage)
goPage(page + 1);
},
onError: (error) => {
toast.show(getErrorAxiosMessage(error), { type: "danger" });
},
}
);
} catch {}
await refetchUserTest();
};
const handleCompleteTest = () => {
mutateCompleteTest({
testId: userTest.id
}, {
onSuccess: () => {
router.replace(`/user-tests/${userTest.id}`);
},
onError: (error) => {
toast.show(getErrorAxiosMessage(error), { type: "danger" });
}
})
};
const goPage = (page: number) => {
setAnswer([]);
setPage(page);
};
return (
<>
<Stack.Screen options={{ title: getUserTestTitle(userTest) }} />
<View style={styles.container}>
<Content style={styles.content}>
<View style={styles.top}>
<ScrollView horizontal contentContainerStyle={styles.topScrollView}>
{pages.map((p) => (
<Button
key={p}
variant={p === page ? "solid" : "outline"}
action={
p === page || userTest.answers[p].answer
? "primary"
: "secondary"
}
onPress={() => goPage(p)}
>
<ButtonText>{p + 1}</ButtonText>
</Button>
))}
<Button
variant="outline"
action="negative"
onPress={() => setOpenCompleteDialog(true)}
>
<ButtonText>Complete test</ButtonText>
</Button>
</ScrollView>
</View>
<Question question={currentQuestion} />
<Divider className="mt-5 mb-5" />
<View className="gap-4 mb-4">
{currentQuestion.variants.map((variant) => (
<Answer
key={variant.id}
answer={variant}
isTextType={false}
userAnswers={answer}
onPress={() => handleSetAnswer(variant.id)}
displayUserAnswer={true}
/>
))}
{currentQuestion.type === QuestionTypes.Text && (
<Panel>
<ThemedText className="mb-1 text-sm" type="defaultSemiBold">
Your answer:
</ThemedText>
<Textarea>
<TextareaInput
onChangeText={(text) => handleSetAnswer(text)}
value={userTextAnswer}
/>
</Textarea>
</Panel>
)}
</View>
<CustomButton isLoading={answerPending} onPress={handleSaveAnswer}>
<ButtonText>Save answer</ButtonText>
</CustomButton>
<Divider className="mt-2 mb-3" />
{(isAllAnswered || isLastPage) && (
<Button
variant={isAllAnswered ? "outline" : "solid"}
action="negative"
onPress={() => setOpenCompleteDialog(true)}
>
<ButtonText>Complete test</ButtonText>
</Button>
)}
</Content>
</View>
<AlertDialog
isOpen={openCompleteDialog}
onClose={() => setOpenCompleteDialog(false)}
>
<AlertDialogBackdrop />
<AlertDialogContent>
<AlertDialogHeader className="mb-2">
<ThemedText type="defaultSemiBold">Complete test</ThemedText>
</AlertDialogHeader>
<AlertDialogBody className="mb-3">
<ThemedText>{alertDialogMessage}</ThemedText>
</AlertDialogBody>
<AlertDialogFooter>
<Button
variant="outline"
action="secondary"
onPress={() => setOpenCompleteDialog(false)}
size="sm"
>
<ButtonText>Cancel</ButtonText>
</Button>
<CustomButton
variant={isAllAnswered ? "outline" : "solid"}
action="negative"
size="sm"
isLoading={completePending}
onPress={handleCompleteTest}
>
<ButtonText>Complete</ButtonText>
</CustomButton>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
width: "100%"
},
top: {
width: "100%",
marginBottom: 15,
},
topScrollView: {
flexDirection: "row",
alignItems: "flex-start",
gap: 10,
},
content: {
flex: 1,
width: "100%",
},
});
export default DoingUserTestScreen;