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)}
} OnClick={router.back} type='material' />
- {/* 正しい答えの表示/非表示切り替え */}
+
>
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) => (
+ -
+ {e}
+
+ ))}
+
+ );
+ 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) => (
+ -
+ {e}
+
+ ))}
+
+ );
+ case 'ListSelect':
+ return (
+
+ {props.user_answer.map((e, i) => (
+ -
+ {e}
+
+ ))}
+
+ );
+
+ default:
+ throw Error('invalid Exam type');
+ }
+ })()}
+ >
+ );
+}