Skip to content

Commit

Permalink
Merge pull request #2 from naoki1510/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
naoki1510 authored Mar 7, 2024
2 parents 2f29849 + d47a798 commit d24e9e4
Show file tree
Hide file tree
Showing 16 changed files with 192 additions and 96 deletions.
18 changes: 13 additions & 5 deletions api/app/controllers/questions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show update destroy start end ]
before_action :set_show_correct, :set_show_answers, only: %i[ index show start end ]
before_action :set_question, only: %i[ show update destroy start end reset ]
before_action :set_show_correct, only: %i[ index show start end reset ]

# GET /questions
def index
Expand Down Expand Up @@ -49,6 +49,7 @@ def destroy
end

def start
reset_question
@question.update!(started_at: Time.current, ended_at: Time.current + 30.seconds)
render :show
end
Expand All @@ -58,6 +59,11 @@ def end
render :show
end

def reset
reset_question
render :show
end

private
# Use callbacks to share common setup or constraints between actions.
def set_question
Expand All @@ -74,8 +80,10 @@ def set_show_correct
@is_show_correct = params[:show_correct].present?
end

# Whether to display answers
def set_show_answers
@is_show_answers = params[:show_answers].present?
def reset_question
@question.update!(started_at: nil, ended_at: nil)
@question.choices.each do |choice|
choice.answers.destroy_all
end
end
end
8 changes: 6 additions & 2 deletions api/app/models/question.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ class Question < ApplicationRecord

scope :ordered, -> { order(display_order: :asc) }
scope :active, -> { where("started_at <= ? AND ? < ended_at", Time.current, Time.current) }
scope :finished, -> { where("ended_at <= ?", Time.current) }
scope :last_finished, -> { finished.order(ended_at: :desc).limit(1) }
scope :finished, -> { where("ended_at <= ?", Time.current).order(ended_at: :desc)}
scope :last_finished, -> { finished.limit(1) }

def correct_answers
answers.where(choice_id: correct_choices.ids)
end

def finished?
ended_at.present? && ended_at <= Time.current
end

end
4 changes: 2 additions & 2 deletions api/app/views/choices/_choice.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
json.extract! choice, :id, :title, :description, :image, :question_id
json.is_correct choice.is_correct if @is_show_correct
json.answer do
json.array! choice.answers, partial: 'answers/answer', as: :answer if @is_show_answers
json.answers do
json.array! choice.answers, partial: 'answers/answer', as: :answer
end
1 change: 1 addition & 0 deletions api/app/views/questions/_question.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
json.extract! question, :id, :title, :image, :question_type, :point
json.is_finished question.finished?
json.choices do
json.array! question.choices.ordered, partial: "choices/choice", as: :choice
end
Expand Down
1 change: 1 addition & 0 deletions api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@

get "questions/:id/start" => "questions#start"
get "questions/:id/end" => "questions#end"
get "questions/:id/reset" => "questions#reset"
end
3 changes: 3 additions & 0 deletions client/src/api/choice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { postHeaders } from "api/headers";
import host from "api/host";
import { Answer } from "./answer";

export type Choice = {
id?: number;
Expand All @@ -8,6 +9,7 @@ export type Choice = {
image: string;
display_order?: number;
is_correct?: boolean;
answers: Answer[];
};

export const createEmptyChoice = (questionId?: number): Choice => {
Expand All @@ -16,6 +18,7 @@ export const createEmptyChoice = (questionId?: number): Choice => {
description: "",
image: "",
is_correct: false,
answers: [],
};
};

Expand Down
14 changes: 14 additions & 0 deletions client/src/api/questions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Question = {
point: number;
choices: Choice[];
until_end?: number;
is_finished: boolean;
};

export const createEmptyQuestion = (): Question => {
Expand All @@ -21,6 +22,7 @@ export const createEmptyQuestion = (): Question => {
question_type: "single",
point: 1,
choices: [createEmptyChoice()],
is_finished: false,
};
};

Expand Down Expand Up @@ -110,6 +112,18 @@ export const endQuestion = (
}).then((res) => res.json() as Promise<Question>);
};

export const resetQuestion = (
id: string | number,
params?: URLSearchParams,
controller?: AbortController
) => {
return fetch(`${host}/questions/${id}/reset?${params}`, {
method: "GET",
headers: getHeaders,
signal: controller?.signal,
}).then((res) => res.json() as Promise<Question>);
};

export const useQuestions = (params?: URLSearchParams) => {
const [questions, setQuestions] = useState<Question[]>();
const [loading, setLoading] = useState<boolean>(false);
Expand Down
74 changes: 38 additions & 36 deletions client/src/components/answers/CreateAnswer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
Box,
Button,
Card,
CardBody,
HStack,
Heading,
IconButton,
Text,
VStack
VStack,
} from "@chakra-ui/react";
import { createAnswer } from "api/answer";
import { Choice } from "api/choice";
Expand All @@ -18,14 +18,17 @@ import locations from "locations";
import { memo, useCallback, useEffect, useState } from "react";
import { IoExit } from "react-icons/io5";
import { useNavigate } from "react-router-dom";
import { useRecoilState } from "recoil";
import userIdState from "recoil/userIdState";
import AnswerForm from "./AnswerForm";

let previousQuestionIds: number[] = [];

export default memo(function CreateAnswer() {
const navigate = useNavigate();
const [userId, setUserID] = useRecoilState(userIdState);
useEffect(() => {
if (!localStorage.getItem("userId") === null) {
if (userId === undefined) {
navigate(locations.createUser);
}
}, [navigate]);
Expand All @@ -37,24 +40,24 @@ export default memo(function CreateAnswer() {
);
const { questions: lastQuestions, fetchQuestions: fetchLastQuestions } =
useQuestions(new URLSearchParams({ last: "true", show_correct: "true" }));
const { user, fetchUser } = useUser(localStorage.getItem("userId") || "");
const { user } = useUser(userId || "");

useEffect(() => {
if (!questions) return;

previousQuestionIds.forEach((id) => {
previousQuestionIds.map(async (id) => {
if (questions.every((question) => question.id !== id)) {
Promise.all(
await Promise.all(
selectedChoices
.filter((choice) => choice.question_id === id)
.map((choice) => {
if (choice.id && localStorage.getItem("userId"))
if (choice.id && userId)
return createAnswer({
choice_id: choice.id,
user_id: Number(localStorage.getItem("userId")),
user_id: userId,
});
})
).then(() => fetchUser());
);
fetchLastQuestions();
}
});
Expand All @@ -74,7 +77,7 @@ export default memo(function CreateAnswer() {
}, [fetchQuestions, questions]);

const handleLogout = useCallback(() => {
localStorage.removeItem("userId");
setUserID(undefined);
navigate(locations.createUser);
}, [navigate]);

Expand All @@ -91,36 +94,35 @@ export default memo(function CreateAnswer() {
</Text>
</Heading>
</Box>
<IconButton
aria-label="logout"
icon={<IoExit />}
<Button
leftIcon={<IoExit />}
onClick={handleLogout}
/>
colorScheme="red"
>
ログアウト
</Button>
</HStack>
</CardBody>
</Card>
{questions ? (
questions.length ? (
questions.map((question) => (
<AnswerForm
question={question}
key={question.id}
selectedChoices={selectedChoices}
setSelectedChoices={setSelectedChoices}
/>
))
) : lastQuestions?.length ? (
<>
<Text>回答済みの問題</Text>
{lastQuestions.map((question) => (
<VStack key={question.id} alignItems={"stretch"}>
<QuestionCard question={question} showChoices />
</VStack>
))}
</>
) : (
<Text>現在回答を受け付けている問題はありません。</Text>
)
{questions?.length ? (
questions.map((question) => (
<AnswerForm
question={question}
key={question.id}
selectedChoices={selectedChoices}
setSelectedChoices={setSelectedChoices}
/>
))
) : lastQuestions?.length ? (
<>
{lastQuestions.map((question) => (
<VStack key={question.id} alignItems={"stretch"}>
<QuestionCard question={question} showChoices />
</VStack>
))}
</>
) : questions && lastQuestions ? (
<Text>クイズが開始されるまでお待ちください。</Text>
) : (
<Loading />
)}
Expand Down
22 changes: 17 additions & 5 deletions client/src/components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,28 @@ import { useAuthorize } from "./Authorize";
type MenuData = {
label: string;
href: string;
admin?: boolean;
};

const menus: MenuData[] = [
{
label: "Play",
href: locations.createUser,
},
{
label: "Admin",
href: locations.listQuestions,
admin: false,
},
{
label: "Questions",
href: locations.listQuestions,
admin: true,
},
{
label: "Users",
href: locations.listQuestions,
admin: true,
},
];

Expand All @@ -42,11 +50,15 @@ export default memo(function Header() {
Quiz
</Heading>
<HStack divider={<Box w={"1px"} />} alignItems={"stretch"}>
{menus.map((menu) => (
<Button variant={"link"} key={menu.label} as={Link} to={menu.href}>
{menu.label}
</Button>
))}
{menus
.filter(
(menu) => menu.admin === undefined || menu.admin === authorized
)
.map((menu) => (
<Button variant={"link"} key={menu.label} as={Link} to={menu.href}>
{menu.label}
</Button>
))}
{authorized && (
<Button variant={"link"} onClick={handleLogout}>
Exit
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/questions/ListQuestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ export default memo(function ListQuestions() {
href={locations.showQuestion.replace(":id", String(question.id))}
key={question.id}
showChoices
showStartButton
showActions
/>
))
) : (
<Text>問題は存在しません</Text>
<Text>問題がありません</Text>
)
) : (
<Loading />
Expand Down
Loading

0 comments on commit d24e9e4

Please sign in to comment.