Skip to content

Commit

Permalink
feat: add global statistics about number of questions
Browse files Browse the repository at this point in the history
feat: add question progress on quiz
feat: add global statistics about the questions
  • Loading branch information
david-vct authored Apr 5, 2024
1 parent 1ab13e7 commit 1347b07
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 8 deletions.
48 changes: 48 additions & 0 deletions src/components/QuestionStatistics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useEffect, useState } from "react"
import { findStatistics } from "../services/statistics-store"
import { AppStatistics, StoreResponse } from "../utils/types"
import { getQuestionTagValue } from "../utils/utils"

export const QuestionStatistics = () => {
const [orderedTags, setOrderedTags] = useState([] as string[])
const [quantities, setQuantities] = useState({} as Record<string, number>)
//const statistics = useMemo(findStatistics, [])

useEffect(() => {
findStatistics().then((response: StoreResponse<AppStatistics>) => {
console.log(response)
if (response.success) {
const data = response.data[0].questions.quantity as Record<string, number>
// Sort tags by value
const keysTags = Object.keys(data)
keysTags.sort((a, b) => data[b] - data[a])
// Remove total key
keysTags.splice(keysTags.indexOf("total"), 1)
// Set up sates
setOrderedTags(keysTags)
setQuantities(data)
}
})
}, [])

// Wait for statistics to be loaded
if (quantities.length === 0) {
return <div className="w-full sm:w-auto px-4 py-8 sm:px-8 rounded-3xl">Waiting</div>
}

return (
<div className="flex flex-row space-x-2">
<span key="total" className="badge badge-lg badge-outline cursor-default">
{quantities["total"] + " questions !"}
</span>
{orderedTags.slice(0, 3).map((tag) => (
<span key={tag} className="badge badge-lg badge-outline cursor-default">
{quantities[tag] + " en " + getQuestionTagValue(tag)}
</span>
))}
<span key="others" className="badge badge-lg badge-outline cursor-default">
...
</span>
</div>
)
}
5 changes: 3 additions & 2 deletions src/components/question/QuestionView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Question, QuestionTag } from "../../utils/types"
import { Question } from "../../utils/types"
import { getQuestionTagValue } from "../../utils/utils"

type QuestionViewProps = {
question: Question
Expand All @@ -12,7 +13,7 @@ export const QuestionView = ({ question, isAnswerVisible = true }: QuestionViewP
<div className="space-x-2 pb-4">
{question.tags.map((tag, index) => (
<span key={"tag-" + index} className="badge badge-outline cursor-default">
{Object.values(QuestionTag)[Object.keys(QuestionTag).indexOf(tag)]}
{getQuestionTagValue(tag)}
</span>
))}
</div>
Expand Down
13 changes: 10 additions & 3 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { JoinGame } from "../components/JoinGame"
import { QuestionStatistics } from "../components/QuestionStatistics"

export const Home = () => {
return (
<div className="w-full min-h-screen flex flex-col items-center justify-center pt-8">
<div className="flex flex-col items-center w-full sm:w-auto px-4 py-8 sm:px-8 rounded-3xl">
<h1 className="text-9xl font-bold">Rculture</h1>
<p className="font-bold pb-16">Un jeu de se culturer avec des quiz</p>

<div className="flex flex-col items-center pb-16">
<h1 className="text-7xl sm:text-9xl font-bold">Rculture</h1>
<p className="text-lg sm:text-xl font-medium pb-4">
Le jeu de se <span className="line-through decoration-primary decoration-2">culturé</span> culturer avec des{" "}
<span className="underline decoration-wavy decoration-primary">quiz</span> et des{" "}
<span className="underline decoration-wavy decoration-primary">potes</span>
</p>
<QuestionStatistics />
</div>
<JoinGame />
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions src/pages/QuestionCreator/SimpleQuestionCreator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const SimpleQuestionCreator = () => {
if (!response.success) {
toast.error("La question n'a pas pu être créée")
} else {
resetForm()
toast.success("La question a été créée")
}
})
Expand All @@ -38,6 +39,13 @@ export const SimpleQuestionCreator = () => {
setTags(tags)
}

const resetForm = () => {
setTitle("")
setBody([])
setAnswers("")
setTags([])
}

return (
<div className="flex flex-col space-y-4">
<div className="form-control">
Expand All @@ -47,6 +55,7 @@ export const SimpleQuestionCreator = () => {
<input
className="input input-bordered rounded-full"
placeholder="Question"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
Expand All @@ -63,6 +72,7 @@ export const SimpleQuestionCreator = () => {
<input
className="input input-bordered rounded-full"
placeholder="Réponses"
value={answers}
onChange={(e) => setAnswers(e.target.value)}
/>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/pages/game/GameQuiz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export const GameQuiz = (props: GameQuizProps) => {

return (
<div className="flex flex-col w-full sm:w-3/4 max-w-3xl px-4 py-8 sm:px-8 rounded-box space-y-4">
<div className="flex flex-col items-center pb-16">
<div>{questionIndex + 1 + " / " + questions.length}</div>
<progress className="progress progress-primary" value={questionIndex + 1} max={questions.length}></progress>
</div>
<QuestionView question={questions[questionIndex]} isAnswerVisible={false} />
<div className="form-control">
<label className="label">
Expand Down
9 changes: 8 additions & 1 deletion src/pages/game/GameReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ export const GameReview = (props: GameReviewProps) => {

return (
<div className="flex flex-col w-full sm:w-3/4 max-w-3xl px-4 py-8 sm:px-8 space-y-4 rounded-box">
<h2>Question Review</h2>
<div className="flex flex-col items-center pb-16">
<div>{questionIndex + 1 + " / " + props.game.questions.length}</div>
<progress
className="progress progress-primary"
value={questionIndex + 1}
max={props.game.questions.length}
></progress>
</div>
<div className="felx flex-col space-y-4 pb-4">
<QuestionView question={question} />
<div className="divider"></div>
Expand Down
17 changes: 16 additions & 1 deletion src/services/questions-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isValideQuestion } from "./validation"
import { findDataByQuery } from "./store"
import { createQuestionRandomIndex, getErrorStoreResponse, getSuccessStoreResponse } from "../utils/utils"
import { shuffle } from "lodash"
import { updateStatisticsByQuestion } from "./statistics-store"

// References to the collections
const questionsRef = collection(db, "questions")
Expand All @@ -31,12 +32,26 @@ export async function createQuestion(question: QuestionData) {
return getErrorStoreResponse("Not valide question")
}

const data = await addDoc(questionsRef, question)
let data
try {
data = await addDoc(questionsRef, question)
// Update global app statistics
await updateStatisticsByQuestion(question)
} catch (e) {
return getErrorStoreResponse(e)
}

return getSuccessStoreResponse([data.id])
}

/* Domain methods */

/**
* Finds questions filtering by tags
* @param tags - Array of tags
* @param nbQuestions - Number max of questions to return
* @returns
*/
export async function findQuestionByTags(tags: string[], nbQuestions: number = 10) {
const q = query(questionsRef, where("tags", "array-contains-any", tags), limit(nbQuestions))

Expand Down
55 changes: 55 additions & 0 deletions src/services/statistics-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FieldValue, collection, doc, documentId, increment, query, updateDoc, where } from "firebase/firestore"
import { db } from "../config/firebase"
import { findDataByQuery } from "./store"
import { AppStatisticsSchema, QuestionData } from "../utils/types"
import { getErrorStoreResponse, getSuccessStoreResponse } from "../utils/utils"

const appRef = collection(db, "app")

/* Basic CRUD methods */

/**
* Finds the statistics in app collection
* @returns
*/
export async function findStatistics() {
const q = query(appRef, where(documentId(), "==", "statistics"))
const response = await findDataByQuery(q, AppStatisticsSchema)
return response
}

/**
* Updates statistics data.
* @param data - Format as {"a.b":c}
* @returns
*/
export async function updateStatistics(data: object) {
try {
const statisticsRef = doc(db, "app/statistics")
await updateDoc(statisticsRef, data)
} catch (e) {
return getErrorStoreResponse(e)
}

return getSuccessStoreResponse([data])
}

/* Domain methods */

/**
* Increments the number of questions by 1 for each tag.
* Updates db statistics.
* @param tags
* @returns
*/
export async function updateStatisticsByQuestion(question: QuestionData) {
const data = { "questions.quantity.total": increment(1) } as Record<string, FieldValue>

// Increment questions quantity for each tag
for (const tag of question.tags) {
data[`questions.quantity.${tag}`] = increment(1)
}

const response = await updateStatistics(data)
return response
}
26 changes: 26 additions & 0 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export enum QuestionTag {
SPORT = "sport",
ART = "art",
MUSIC = "musique",
LITERATURE = "littérature",
ENIGME = "énigme",
DILEMMA = "dilemme",
}
Expand Down Expand Up @@ -100,3 +101,28 @@ export const StoreResponseSchema = z.discriminatedUnion("success", [
])

export type StoreResponse<DataType> = { success: true; data: DataType[] } | { success: false; error: unknown }

/* Statistics types */

export const AppStatisticsSchema = z.object({
questions: z.object({
quantity: z.object({
total: z.number(),
CULTUREG: z.number(),
CULTUREP: z.number(),
HISTORY: z.number(),
GEOGRAPHY: z.number(),
SCIENCE: z.number(),
PHYSICS: z.number(),
BIOLOGY: z.number(),
MATHS: z.number(),
SPORT: z.number(),
ART: z.number(),
MUSIC: z.number(),
ENIGME: z.number(),
DILEMMA: z.number(),
}),
}),
})

export type AppStatistics = z.infer<typeof AppStatisticsSchema>
30 changes: 29 additions & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { random } from "lodash"
import { GameData, GameUser, StoreResponse, UserInfo } from "./types"
import { GameData, GameUser, QuestionTag, StoreResponse, UserInfo } from "./types"

/**
* Get an anonymous userInfo
Expand Down Expand Up @@ -41,6 +41,30 @@ export function initializeGameUser(name: string): GameUser {
}
}

export function initializeEmptyAppStatistics() {
return {
questions: {
quantity: {
total: 0,
CULTUREG: 0,
CULTUREP: 0,
HISTORY: 0,
GEOGRAPHY: 0,
SCIENCE: 0,
PHYSICS: 0,
BIOLOGY: 0,
MATHS: 0,
SPORT: 0,
ART: 0,
MUSIC: 0,
LITTERATURE: 0,
ENIGME: 0,
DILEMMA: 0,
},
},
}
}

export function initializeEmptyQuestionData() {}

export function getSuccessStoreResponse<DataType>(data: DataType[]): StoreResponse<DataType> {
Expand Down Expand Up @@ -72,3 +96,7 @@ export function createQuestionRandomIndex() {
const randomIndex = random(0, RANDOM_INDEX_MAX, false)
return randomIndex
}

export function getQuestionTagValue(tag: string) {
return Object.values(QuestionTag)[Object.keys(QuestionTag).indexOf(tag)]
}

0 comments on commit 1347b07

Please sign in to comment.