Skip to content

Commit

Permalink
update: examtable>履歴の内容をちゃんと表示できるようにした
Browse files Browse the repository at this point in the history
  • Loading branch information
watasuke102 committed Jan 14, 2024
1 parent 7cb666e commit ca95c22
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 64 deletions.
7 changes: 4 additions & 3 deletions src/app/examtable/_components/Table/Table.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
97 changes: 62 additions & 35 deletions src/app/examtable/_components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -62,39 +60,51 @@ 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 (
<>
<Helmet title={`問題一覧 : ${props.data.title} - TAGether`} />

<div className={css.examdata_container}>
<span className={css.title}>{props.data.title}</span>
<span className={css.title}>{props.title}</span>
{props.history && (
<>
<div className={css.filter_selector}>
<SelectButton
type='check'
desc='全問正解'
status={(filter & AnswerState.AllCorrect) !== 0}
onChange={f => UpdateFilter(AnswerState.AllCorrect, f)}
onChange={() => SetFilter(f => f ^ AnswerState.AllCorrect)}
/>
<SelectButton
type='check'
desc='部分正解'
status={(filter & AnswerState.PartialCorrect) !== 0}
onChange={f => UpdateFilter(AnswerState.PartialCorrect, f)}
onChange={() => SetFilter(f => f ^ AnswerState.PartialCorrect)}
/>
<SelectButton
type='check'
desc='不正解'
status={(filter & AnswerState.AllWrong) !== 0}
onChange={f => UpdateFilter(AnswerState.AllWrong, f)}
onChange={() => SetFilter(f => f ^ AnswerState.AllWrong)}
/>
</div>
<span>
{props.history.total_question}問中{props.history.correct_count}問正解、 正答率
{props.history && Math.round((props.history.correct_count / props.history.total_question) * 10000) / 100}%
</span>
{total_questions !== undefined && correct_answers !== undefined && (
<span>
{total_questions}問中{correct_answers}問正解、 正答率
{props.history && Math.round((correct_answers / total_questions) * 10000) / 100}%
</span>
)}
</>
)}
</div>
Expand All @@ -104,15 +114,15 @@ export function Table(props: Props): React.ReactElement {
<tr>
<th>問題</th>
<th>正解</th>
<th>コメント</th>
{props.history && (
<>
<th>自分の解答</th>
<th className={css.result}>結果</th>
</>
)}
<th>コメント</th>
</tr>
{(JSON.parse(props.data.list) as Exam[])
{props.exam
.map((exam, i) => (
<tr key={`tr-${i}`}>
<td>
Expand All @@ -129,11 +139,14 @@ export function Table(props: Props): React.ReactElement {
<td className={show_correct_answer ? '' : css.hide}>
<FmtCorrectAnswer exam={exam} />
</td>
<td className={show_correct_answer ? '' : css.hide}>{exam.comment ?? ''}</td>
{props.history && (
<>
<td>
<FmtCorrectAnswer exam={exam} result={props.history.exam_state[i].result} />
<td className={show_user_answer ? '' : css.hide}>
<FmtUserAnswer
exam={exam}
user_answer={props.history.exam_state[i].user_answer}
result={props.history.exam_state[i].result}
/>
</td>
<td>
{(() => {
Expand All @@ -149,22 +162,36 @@ export function Table(props: Props): React.ReactElement {
</td>
</>
)}
<td className={show_correct_answer ? '' : css.hide}>{exam.comment ?? ''}</td>
</tr>
))
.filter((_, i) => !props.history || (filter & props.history?.exam_state[i].order) !== 0)}
.filter((_, i) => !props.history || (filter & (answer_state?.at(i) ?? 0xff)) !== 0)}
</tbody>
</table>

<div className={css.button_container}>
<div className={css.buttons}>
<Button text='戻る' icon={<ArrowLeftIcon />} OnClick={router.back} type='material' />
{/* 正しい答えの表示/非表示切り替え */}
<Button
OnClick={() => set_show_user_answer(e => !e)}
type='material'
text={show_user_answer ? '自分の解答を非表示' : '自分の解答を表示'}
icon={show_user_answer ? <InvisibleIcon /> : <VisibleIcon />}
/>
<Button
OnClick={() => SetShowCorrectAnswer(!show_correct_answer)}
type='material'
text={show_correct_answer ? '正解を非表示' : '正解を表示'}
icon={show_correct_answer ? <InvisibleIcon /> : <VisibleIcon />}
/>
<Button
OnClick={() =>
router.push(props.history ? `/exam?history_id=${props.history.id}` : `/exam?id=${props.id ?? ''}`)
}
type='filled'
text={'この問題を解く'}
icon={<ArrowRightIcon />}
/>
</div>
</div>
</>
Expand Down
59 changes: 33 additions & 26 deletions src/app/examtable/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExamHistory | undefined>();
const [data, is_loading] = useCategoryData(id ?? -1);
export default function ExamTablePage(props: Props): React.ReactElement {
const [examtable_props, set_examtable_props] = React.useState<ExamTableProps | undefined>();

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 <Loading />;
}

return <Table data={data} history={history} />;
return examtable_props ? <Table {...examtable_props} /> : <Loading />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// TAGether - Share self-made exam for classmates
// CopyRight (c) 2020-2023 watasuke
//
// Email : <[email protected]>
// Twitter: @Watasuke102
// This software is released under the MIT or MIT SUSHI-WARE License.
@import '../../../common/variable';

.wrong {
color: $color-wrong;
}
80 changes: 80 additions & 0 deletions src/components/features/Exam/FmtUserAnswer/FmtUserAnswer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// TAGether - Share self-made exam for classmates
// CopyRight (c) 2020-2024 watasuke
//
// Email : <[email protected]>
// 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 <span className={props.result?.at(0) ?? true ? '' : css.wrong}>{props.user_answer[0]}</span>;
}
return (
<ol>
{props.user_answer.map((e, i) => (
<li key={'result_select_' + i} className={props.result?.at(i) ?? true ? '' : css.wrong}>
{e}
</li>
))}
</ol>
);
case 'Select':
return (
<span className={props.result?.at(0) ?? true ? '' : css.wrong}>
{props.exam.question_choices?.at(Number(props.exam.answer[0]))}
</span>
);
case 'MultiSelect':
return (
<ul>
{props.exam.answer
.filter((_m, i) => props.user_answer[i] === String(i))
.map((e, i) => (
<li key={'result_multiselect_' + i} className={props.result?.at(i) ?? true ? '' : css.wrong}>
{e}
</li>
))}
</ul>
);
case 'Sort':
return (
<ol>
{props.user_answer.map((e, i) => (
<li key={'result_sort_' + i} className={props.result?.at(i) ?? true ? '' : css.wrong}>
{e}
</li>
))}
</ol>
);
case 'ListSelect':
return (
<ol>
{props.user_answer.map((e, i) => (
<li key={'result_listselect_' + i} className={props.result?.at(i) ?? true ? '' : css.wrong}>
{e}
</li>
))}
</ol>
);

default:
throw Error('invalid Exam type');
}
})()}
</>
);
}

0 comments on commit ca95c22

Please sign in to comment.