Skip to content

Commit

Permalink
Merge pull request #1 from USNightOwl/feat/do-exercise
Browse files Browse the repository at this point in the history
Feat - Handle do exercise
  • Loading branch information
nxhawk committed Jun 26, 2024
2 parents ca9c034 + 17f4e53 commit e981a64
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 17 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"axios": "^1.7.2",
"clsx": "^2.1.1",
"pinia": "^2.1.7",
"primeicons": "^7.0.0",
"tailwind-merge": "^2.3.0",
"vue": "^3.4.21",
"vue-awesome-paginate": "^1.1.46",
"vue-router": "4",
Expand Down
6 changes: 5 additions & 1 deletion src/api/examsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import AxiosClient from "./axios";

const resource = "/category";
const resourceExam = "/exam";

export default {
get(classId: string, currentPage: number) {
return AxiosClient.get(`${resource}/${classId}/exams?page=${currentPage}`);
},
getOne(exerciseId: string) {
return AxiosClient.get(`/exam/${exerciseId}`);
return AxiosClient.get(`${resourceExam}/${exerciseId}`);
},
search(searchText: string, page: number) {
return AxiosClient.post('/search', {
keyword: searchText,
page: page || 1,
});
},
submit(formData: any) {
return AxiosClient.post(`${resourceExam}/submit`, formData);
}
}

22 changes: 22 additions & 0 deletions src/components/Button/ButtonUtils.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template lang="">
<button
:class="cn('border rounded px-4 py-2 hover:text-white flex items-center justify-center gap-2 transition ease-linear w-full md:w-fit', props.className)"
@click="props.func"
>
<slot/>
</button>
</template>

<script setup>
import { cn } from "@/utils/cn";
const props = defineProps({
className: {
type: String,
},
func: {
type: Function,
required: true
}
})
</script>
30 changes: 25 additions & 5 deletions src/components/DoExercise/Question.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<template lang="">
<div class="mb-5">
<h1 v-html="props.name" class="mb-2"></h1>
<p v-for="(ans, idx) in props.answers" :key="ans.id" class="text-[1rem] leading-4 mb-4 px-1 flex items-center">

<h1
v-html="props.name"
:class="{'text-red-500' : props.warning}"
></h1>
<p v-for="(ans, idx) in props.answers" :key="ans.id" class="text-[1rem] leading-4 mb-4 px-1 flex items-center mt-2">
<input
:id="'question-'+props.questionId+'-'+idx"
type="radio"
:value="idx"
:value="ans.name"
v-model="checked"
class="w-4 h-4 bg-gray-100 border-gray-300 cursor-pointer focus:outline-none"
/>
Expand All @@ -21,8 +25,9 @@
</template>

<script setup>
import console from "console";
import { ref } from "vue";
import { ref, watch } from "vue";
const emit = defineEmits(["update-answer"]);
const props = defineProps({
name: {
type: String,
Expand All @@ -35,8 +40,23 @@ import { ref } from "vue";
questionId: {
type: Number,
required: true,
},
questionIdx: {
type: Number,
required: true,
},
pageIdx: {
type: Number,
required: true,
},
warning: {
type: Boolean,
}
});
const checked = ref();
watch(() => checked.value, ()=>{
emit("update-answer", checked.value, props.pageIdx, props.questionIdx)
})
</script>
20 changes: 20 additions & 0 deletions src/components/DoExercise/Quote.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template lang="">
<i class="block md:mb-8 mb-5 text-base md:text-xl md:px-8 px-2" v-html="quote">
</i>
</template>

<script setup>
import { ref, onMounted } from "vue";
const quote = ref("");
onMounted(async ()=> {
const API_KEY = "SltTB9eBPK6on57H/Vlygg==1GGt23U1uXtMedKH";
const response = await fetch("https://api.api-ninjas.com/v1/quotes?category=learning", {
headers: {
'x-api-key': API_KEY,
}
});
const quoteData = await response.json();
quote.value = `${quoteData[0].quote} - <b>${quoteData[0].author}</b>`;
});
</script>
73 changes: 73 additions & 0 deletions src/components/DoExercise/Results.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template lang="">
<div class="text-center">
<h1 class="text-3xl md:text-4xl my-4 md:my-6 font-bold">
{{ isSubmitHaveNiceResult(props.results.score)?"Xin chúc mừng!":"Bạn có thể thử lại lần nữa."}}
</h1>
<p>Với <span class="text-green-500 font-bold">{{ props.results.correctAnswers }}/{{ props.results.totalQuestion }}</span> câu trả lời đúng. Điểm của bạn là</p>
<h1
:class="'text-4xl font-bold my-6 ' + (isSubmitHaveNiceResult(props.results.score)?'text-green-500':'text-red-400')"
>{{ props.results.score }}</h1>
<div class="flex md:flex-row flex-col gap-3 md:gap-5 flex-wrap md:my-10 my-5 justify-center items-center px-2">
<ButtonUtils
className="bg-[#f0f9eb] border-[#c2e7b0] hover:bg-[#67c23a] text-[#67c23a]"
:func="handleTryAgain"
>
<Icon icon="pi-sync"/>
Thử làm lại
</ButtonUtils>

<ButtonUtils
className="bg-[#fdf6ec] border-[#f5dab1] hover:bg-[#e6a23c] text-[#e6a23c]"
:func="handleShowResults"
>
<Icon icon="pi-file-check"/>
Xem đáp án chi tiết
</ButtonUtils>

<ButtonUtils
className="bg-[#f4f4f5] border-[#d3d4d6] hover:bg-[#909399] text-[#909399]"
:func="handleDoMore"
>
<Icon icon="pi-send"/>
Làm các đề khác
</ButtonUtils>
</div>

<Quote/>
</div>
</template>

<script setup>
import ButtonUtils from "@/components/Button/ButtonUtils.vue";
import Icon from "@/components/Icon.vue";
import Quote from "@/components/DoExercise/Quote.vue";
import router from "@/router";
const props = defineProps({
slug: {
type: String,
required: true,
},
results: {
type: Object,
required: true,
}
});
function isSubmitHaveNiceResult(score) {
return score >= 5;
}
function handleTryAgain() {
window.location.reload();
}
function handleDoMore() {
router.push({ name: 'exercise', params: { classId: props.slug } });
}
function handleShowResults() {
alert("This feature will show the results and implement later");
}
</script>
6 changes: 6 additions & 0 deletions src/utils/cn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
75 changes: 64 additions & 11 deletions src/views/DoExercisePage.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
<template lang="">
<div class="bg-slate-50 m-0 py-2 px-1">
<div class="md:w-9/12 rounded mx-auto box-shadow bg-white text-[#606266] flex flex-col">
<div v-if="!loadingStore.isLoading && examData">
<div v-if="!loadingStore.isLoading && examData && !isShowResults">
<Title :name="examData.value.exam.name"/>
<div class="md:px-4 px-2 text-base mb-8">
<i class="font-medium" v-html="examData.value.exam.pages[0].note">
</i>
<div class="my-3 px-3">
<Question
v-for="q in examData.value.exam.pages[0].questions"
:key="q.id"
:name="q.name"
:answers="q.answers"
:question-id="q.id"
/>
<div
v-for="(page, pageIdx) in examData.value.exam.pages"
>
<Question
v-for="(q, questionIdx) in page.questions"
:key="q.id"
:name="q.name"
:answers="q.answers"
:question-id="q.id"
:page-idx="pageIdx"
:question-idx="questionIdx"
:warning="q.warning==false||!isFirstSubmit?false:true"
@update-answer="handleUpdateAnswer"
/>
</div>
</div>
</div>

Expand All @@ -24,21 +32,32 @@
</button>
</div>
</div>

<div v-else-if="!loadingStore.isLoading && isShowResults">
<Results
:slug="examData.value.category.slug"
:results="resultsData.value"
/>
</div>
</div>
</div>
</template>

<script setup>
import { useRoute } from 'vue-router';
import { watch, reactive } from "vue";
import { watch, reactive, ref } from "vue";
import { RepositoryFactory } from "@/api/RepositoryFactory";
import { useLoadingStore } from "@/store/loading";
import { toast } from 'vue3-toastify';
import Title from "@/components/DoExercise/Title.vue";
import Question from "@/components/DoExercise/Question.vue";
import Results from "@/components/DoExercise/Results.vue";
import Icon from "@/components/Icon.vue";
const examData = reactive([]);
const resultsData = reactive([]);
const isShowResults = ref(false);
const isFirstSubmit = ref(false);
const loadingStore = useLoadingStore();
const ExamRepository = RepositoryFactory.get("exams");
const route = useRoute();
Expand All @@ -59,9 +78,43 @@
});
}, { immediate: true });
function submitExam() {
alert("This feature will implement later");
function handleUpdateAnswer(answer, pageId, questionId) {
examData.value.exam.pages[pageId].questions[questionId].answer = answer;
examData.value.exam.pages[pageId].questions[questionId].warning = false;
}
async function submitExam() {
isFirstSubmit.value = true;
// check all questions have answer from user
for (const page of examData.value.exam.pages) {
for (const q of page.questions){
if (q.warning==null || q.warning==true || !q.answer || q.answer.length <= 0) {
return toast.warning("Vui lòng trả lời hết các câu hỏi (đang bị bôi đỏ) trước khi nộp bài.", {
toastStyle: {
fontSize: '14px',
},
position: toast.POSITION.BOTTOM_RIGHT,
});
}
}
}
// submit answer
loadingStore.changeLoadingState(true);
const { data } = await ExamRepository.submit(examData.value.exam);
loadingStore.changeLoadingState(false);
resultsData.value = data;
isShowResults.value = true;
toast.success("Nộp bài thành công", {
toastStyle: {
fontSize: '14px',
},
position: toast.POSITION.BOTTOM_RIGHT,
});
}
</script>

<style lang="scss">
Expand Down
24 changes: 24 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz"
integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==

"@babel/runtime@^7.24.1":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
dependencies:
regenerator-runtime "^0.14.0"

"@commitlint/cli@^19.3.0":
version "19.3.0"
resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-19.3.0.tgz#44e6da9823a01f0cdcc43054bbefdd2c6c5ddf39"
Expand Down Expand Up @@ -789,6 +796,11 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"

clsx@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==

color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
Expand Down Expand Up @@ -1680,6 +1692,11 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"

regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==

require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
Expand Down Expand Up @@ -1865,6 +1882,13 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==

tailwind-merge@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.3.0.tgz#27d2134fd00a1f77eca22bcaafdd67055917d286"
integrity sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==
dependencies:
"@babel/runtime" "^7.24.1"

tailwindcss@^3.4.4:
version "3.4.4"
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz"
Expand Down

0 comments on commit e981a64

Please sign in to comment.