diff --git a/src/app/examtable/_components/Table/Table.module.scss b/src/app/examtable/_components/Table/Table.module.scss index 5ea4d7fb..aeb4e72d 100644 --- a/src/app/examtable/_components/Table/Table.module.scss +++ b/src/app/examtable/_components/Table/Table.module.scss @@ -81,7 +81,8 @@ background-color: rgba($color: $color-1, $alpha: 0.9); } .buttons { - margin: 20px 25px; - display: flex; - justify-content: space-between; + margin: 12px 12px; + display: grid; + gap: 32px; + grid-template-columns: auto 1fr 1fr auto; } diff --git a/src/app/examtable/_components/Table/Table.tsx b/src/app/examtable/_components/Table/Table.tsx index eb580383..99efb3a4 100644 --- a/src/app/examtable/_components/Table/Table.tsx +++ b/src/app/examtable/_components/Table/Table.tsx @@ -7,43 +7,41 @@ 'use client'; import css from './Table.module.scss'; import React from 'react'; -import Helmet from 'react-helmet'; import {useRouter} from 'next/navigation'; import Button from '@/common/Button/Button'; import {SelectButton} from '@/common/SelectBox'; import {FmtCorrectAnswer} from '@/features/Exam/FmtCorrectAnswer/FmtCorrectAnswer'; import AnswerState from '@mytypes/AnswerState'; -import {CategoryDataType} from '@mytypes/Categoly'; import Exam from '@mytypes/Exam'; -import ExamHistory from '@mytypes/ExamHistory'; +import {History} from '@mytypes/ExamHistory'; import ArrowLeftIcon from '@assets/arrow-left.svg'; import VisibleIcon from '@assets/visible.svg'; import InvisibleIcon from '@assets/invisible.svg'; +import ArrowRightIcon from '@assets/arrow-right.svg'; import {Shuffle} from '@utils/ArrayUtil'; +import {FmtUserAnswer} from '@/features/Exam/FmtUserAnswer/FmtUserAnswer'; -interface Props { - data: CategoryDataType; - history?: ExamHistory; -} +export type ExamTableProps = { + title: string; + exam: Exam[]; + id?: string; + history?: History; +}; -export function Table(props: Props): React.ReactElement { +export function Table(props: ExamTableProps): React.ReactElement { const [show_correct_answer, SetShowCorrectAnswer] = React.useState(false); + const [show_user_answer, set_show_user_answer] = React.useState(false); const [filter, SetFilter] = React.useState(0x07); const router = useRouter(); - const UpdateFilter = (type: AnswerState, state: boolean) => { - SetFilter(filter => { - if (state) { - return filter | type; - } else { - return filter & (~type & 0x07); - } - }); - }; + const [correct_answers, total_questions] = props.history?.exam_state.reduce( + (acc, cur) => [acc[0] + cur.correct_count, acc[1] + cur.total_question], + [0, 0], + ) ?? [undefined, undefined]; const choices: string[] = React.useMemo( () => - (JSON.parse(props.data.list) as Exam[]).map(exam => { + props.exam.map(exam => { let array: string[]; switch (exam.type) { case 'Select': @@ -62,13 +60,23 @@ export function Table(props: Props): React.ReactElement { }), [], ); + const answer_state = React.useMemo( + () => + props.history?.exam_state.map(e => { + if (e.correct_count === e.total_question) { + return AnswerState.AllCorrect; + } else if (e.correct_count === 0) { + return AnswerState.AllWrong; + } + return AnswerState.PartialCorrect; + }), + [], + ); return ( <> - -
- {props.data.title} + {props.title} {props.history && ( <>
@@ -76,25 +84,27 @@ export function Table(props: Props): React.ReactElement { type='check' desc='全問正解' status={(filter & AnswerState.AllCorrect) !== 0} - onChange={f => UpdateFilter(AnswerState.AllCorrect, f)} + onChange={() => SetFilter(f => f ^ AnswerState.AllCorrect)} /> UpdateFilter(AnswerState.PartialCorrect, f)} + onChange={() => SetFilter(f => f ^ AnswerState.PartialCorrect)} /> UpdateFilter(AnswerState.AllWrong, f)} + onChange={() => SetFilter(f => f ^ AnswerState.AllWrong)} />
- - {props.history.total_question}問中{props.history.correct_count}問正解、 正答率 - {props.history && Math.round((props.history.correct_count / props.history.total_question) * 10000) / 100}% - + {total_questions !== undefined && correct_answers !== undefined && ( + + {total_questions}問中{correct_answers}問正解、 正答率 + {props.history && Math.round((correct_answers / total_questions) * 10000) / 100}% + + )} )}
@@ -104,15 +114,15 @@ export function Table(props: Props): React.ReactElement { 問題 正解 - コメント {props.history && ( <> 自分の解答 結果 )} + コメント - {(JSON.parse(props.data.list) as Exam[]) + {props.exam .map((exam, i) => ( @@ -129,11 +139,14 @@ export function Table(props: Props): React.ReactElement { - {exam.comment ?? ''} {props.history && ( <> - - + + {(() => { @@ -149,22 +162,36 @@ export function Table(props: Props): React.ReactElement { )} + {exam.comment ?? ''} )) - .filter((_, i) => !props.history || (filter & props.history?.exam_state[i].order) !== 0)} + .filter((_, i) => !props.history || (filter & (answer_state?.at(i) ?? 0xff)) !== 0)}
diff --git a/src/app/examtable/page.tsx b/src/app/examtable/page.tsx index 91335ee7..42578093 100644 --- a/src/app/examtable/page.tsx +++ b/src/app/examtable/page.tsx @@ -5,39 +5,46 @@ // Twitter: @Watasuke102 // This software is released under the MIT or MIT SUSHI-WARE License. 'use client'; -import {useSearchParams} from 'next/navigation'; import React from 'react'; -import {Table} from './_components/Table/Table'; +import {ExamTableProps, Table} from './_components/Table/Table'; import Loading from '@/common/Loading/Loading'; -import ExamHistory from '@mytypes/ExamHistory'; -import {useCategoryData} from '@utils/api/swr_hooks'; -import {GetSpecifiedExamHistory} from '@utils/ManageDB'; +import {fetch_category_data} from '@utils/api/category'; +import {fetch_history} from '@utils/api/history'; +import {redirect} from 'next/navigation'; -export default function ExamTablePage(): React.ReactElement { - const search_params = useSearchParams(); - const id = search_params.get('id'); - const history_id = search_params.get('history_id'); +type Props = { + searchParams: { + id?: string; + history_id?: string; + }; +}; - const [history, SetHistory] = React.useState(); - const [data, is_loading] = useCategoryData(id ?? -1); +export default function ExamTablePage(props: Props): React.ReactElement { + const [examtable_props, set_examtable_props] = React.useState(); React.useEffect(() => { - if (id) { - return; - } - const history_id_str = Array.isArray(history_id) ? history_id[0] : history_id ?? ''; - GetSpecifiedExamHistory(history_id_str).then(result => { - if (result) { - SetHistory(result); + (async () => { + let prop: ExamTableProps; + if (props.searchParams.id) { + const category = await fetch_category_data(props.searchParams.id); + prop = { + exam: JSON.parse(category.list), + title: category.title, + id: props.searchParams.id, + }; + } else if (props.searchParams.history_id) { + const history = await fetch_history(props.searchParams.history_id); + prop = { + exam: history.exam, + title: history.title, + history: history, + }; } else { - throw new Error('[FATAL] cannot get ExamHistory'); + redirect('/list'); } - }); - }, [search_params, is_loading]); + set_examtable_props(prop); + })(); + }, []); - if ((id && is_loading) || (history_id && !history)) { - return ; - } - - return ; + return examtable_props ?
: ; } diff --git a/src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.module.scss b/src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.module.scss new file mode 100644 index 00000000..57e72ff1 --- /dev/null +++ b/src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.module.scss @@ -0,0 +1,11 @@ +// TAGether - Share self-made exam for classmates +// CopyRight (c) 2020-2023 watasuke +// +// Email : +// Twitter: @Watasuke102 +// This software is released under the MIT or MIT SUSHI-WARE License. +@import '../../../common/variable'; + +.wrong { + color: $color-wrong; +} diff --git a/src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.tsx b/src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.tsx new file mode 100644 index 00000000..de2554a4 --- /dev/null +++ b/src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.tsx @@ -0,0 +1,80 @@ +// TAGether - Share self-made exam for classmates +// CopyRight (c) 2020-2024 watasuke +// +// Email : +// Twitter: @Watasuke102 +// This software is released under the MIT or MIT SUSHI-WARE License. +import css from './FmtUserAnswer.module.scss'; +import React from 'react'; +import Exam from '@mytypes/Exam'; + +type Props = { + exam: Exam; + user_answer: string[]; + result: boolean[]; +}; + +export function FmtUserAnswer(props: Props): JSX.Element { + return ( + <> + {(() => { + switch (props.exam.type ?? 'Text') { + case 'Text': + if (props.exam.answer.length === 1) { + return {props.user_answer[0]}; + } + return ( +
    + {props.user_answer.map((e, i) => ( +
  1. + {e} +
  2. + ))} +
+ ); + case 'Select': + return ( + + {props.exam.question_choices?.at(Number(props.exam.answer[0]))} + + ); + case 'MultiSelect': + return ( +
    + {props.exam.answer + .filter((_m, i) => props.user_answer[i] === String(i)) + .map((e, i) => ( +
  • + {e} +
  • + ))} +
+ ); + case 'Sort': + return ( +
    + {props.user_answer.map((e, i) => ( +
  1. + {e} +
  2. + ))} +
+ ); + case 'ListSelect': + return ( +
    + {props.user_answer.map((e, i) => ( +
  1. + {e} +
  2. + ))} +
+ ); + + default: + throw Error('invalid Exam type'); + } + })()} + + ); +}