Skip to content

Commit

Permalink
feat: enhance admin pages
Browse files Browse the repository at this point in the history
  • Loading branch information
exKAZUu committed Oct 11, 2024
1 parent 9a5e125 commit 4ff6108
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 2 deletions.
118 changes: 118 additions & 0 deletions src/app/(withAuth)/admin/problems/[problemId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { NextPage } from 'next';

import { logger } from '../../../../../infrastructures/pino';
import { prisma } from '../../../../../infrastructures/prisma';
import { Box, Heading, Table, Tbody, Td, Th, Thead, Tr, VStack } from '../../../../../infrastructures/useClient/chakra';
import type { CourseId, ProblemId } from '../../../../../problems/problemData';
import { courseIdToLectureIds } from '../../../../../problems/problemData';
import { getEmailFromSession } from '../../../../../utils/session';
import { dayjs } from '../../../../utils/dayjs';

interface UserProblemInfo {
userId: string;
email?: string;
courseId: string;
lectureIndex: number;
problemId: string;
completedAt: Date | null;
elapsedMilliseconds: number;
incorrectSubmissionCount: number;
}

type Props = {
params: { problemId: ProblemId };
};

const StatisticsPage: NextPage<Props> = async (props) => {
const userInfos = await fetchUserProblemInfo(props.params.problemId);

return (
<VStack align="stretch" spacing={6}>
<Heading as="h1">問題: {props.params.problemId}</Heading>
<Box overflowX="auto">
<Table size="sm">
<Thead>
<Tr>
<Th>ユーザーID</Th>
<Th>Email</Th>
<Th>コース</Th>
<Th>講義</Th>
<Th>完了日時</Th>
<Th>所要時間</Th>
<Th>不正解回数</Th>
</Tr>
</Thead>
<Tbody>
{userInfos.map((info) => (
<Tr key={info.userId}>
<Td>{info.userId}</Td>
<Td>{info.email || 'N/A'}</Td>
<Td>{info.courseId}</Td>
<Td>{info.lectureIndex + 1}</Td>
<Td>{info.completedAt ? dayjs(info.completedAt).format('YYYY-MM-DD HH:mm:ss') : '未完了'}</Td>
<Td>{info.elapsedMilliseconds ? dayjs.duration(info.elapsedMilliseconds).humanize() : 'N/A'}</Td>
<Td>{info.incorrectSubmissionCount}</Td>
</Tr>
))}
</Tbody>
</Table>
</Box>
</VStack>
);
};

async function fetchUserProblemInfo(problemId: ProblemId): Promise<UserProblemInfo[]> {
try {
const sessions = await prisma.problemSession.findMany({
where: {
problemId,
// eslint-disable-next-line unicorn/no-null
completedAt: { not: null },
submissions: {
some: { isCorrect: true },
},
},
orderBy: {
completedAt: 'asc',
},
distinct: ['userId'],
include: {
submissions: { where: { isCorrect: false }, select: { id: true } },
},
});
console.log('sessions:', sessions);

return await Promise.all(
sessions.map(async (session) => ({
userId: session.userId,
email: await getEmailFromSession(session.userId),
courseId: session.courseId,
lectureIndex: getLectureIndex(session.courseId, session.lectureId),
problemId: session.problemId,
completedAt: session.completedAt,
elapsedMilliseconds: session.elapsedMilliseconds,
incorrectSubmissionCount: session.submissions.length,
}))
);
} catch (error) {
logger.error(`Failed to fetch user problem info for ${problemId}: %o`, error);
return [];
}
}

function getLectureIndex(courseId: string, lectureId: string): number {
const lectureIds = courseIdToLectureIds[courseId as CourseId];
if (!lectureIds) {
logger.warn(`Course (${courseId}) not found.`);
return -1;
}
for (const [index, lectureId_] of lectureIds.entries()) {
if (lectureId_.includes(lectureId)) {
return index;
}
}
logger.warn(`Lecture (${lectureId}) not found in course (${courseId}).`);
return -1;
}

export default StatisticsPage;
17 changes: 15 additions & 2 deletions src/app/(withAuth)/admin/statistics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@ import type { NextPage } from 'next';

import { logger } from '../../../../infrastructures/pino';
import { prisma } from '../../../../infrastructures/prisma';
import { Box, Heading, Table, Tbody, Td, Th, Thead, Tr, VStack } from '../../../../infrastructures/useClient/chakra';
import {
Box,
Heading,
Link,
Table,
Tbody,
Td,
Th,
Thead,
Tr,
VStack,
} from '../../../../infrastructures/useClient/chakra';
import type { CourseId } from '../../../../problems/problemData';
import { courseIdToLectureIndexToProblemIds } from '../../../../problems/problemData';
import { dayjs } from '../../../utils/dayjs';
Expand Down Expand Up @@ -41,7 +52,9 @@ const StatisticsPage: NextPage = async () => {
<Tr key={`${stat.courseId}-${stat.lectureIndex}-${stat.problemId}`}>
<Td>{stat.courseId}</Td>
<Td>{stat.lectureIndex + 1}</Td>
<Td>{stat.problemId}</Td>
<Td>
<Link href={`/admin/problems/${stat.problemId}`}>{stat.problemId}</Link>
</Td>
<Td>{stat.userCount}</Td>
<Td>{stat.completedUserCount}</Td>
<Td>{dayjs.duration(stat.avgElapsedMilliseconds).humanize()}</Td>
Expand Down

0 comments on commit 4ff6108

Please sign in to comment.