Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: progress on problems #36

Merged
merged 14 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- CreateTable
CREATE TABLE "UserSolvedProblem" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"userId" TEXT NOT NULL,
"courseId" TEXT NOT NULL,
"programId" TEXT NOT NULL,
"languageId" TEXT NOT NULL,
CONSTRAINT "UserSolvedProblem_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
17 changes: 15 additions & 2 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ datasource db {
// -----------------------------------------------------------------------------

model User {
id String @id
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

displayName String @unique
displayName String @unique
userSolvedAnswers UserSolvedProblem[]
}

model UserSolvedProblem {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

user User @relation(fields: [userId], references: [id])
userId String
courseId String
programId String
languageId String
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

programIdやcourseIdはハードコーディングしてるから、問題IDやコースIDの変更は基本できない(やる場合はUserSolvedProblemのレコードも更新する)のは気をつけないといけないですね・・・💡

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ですね! どこかでDBを使用するように改善する余地もあるかもしれません!

}
Binary file added public/crown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
135 changes: 135 additions & 0 deletions src/app/(withAuth)/courses/[courseId]/Course.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use client';

import {
Box,
Heading,
VStack,
Accordion,
AccordionItem,
AccordionButton,
AccordionIcon,
AccordionPanel,
Select,
Table,
TableContainer,
Thead,
Tbody,
Tr,
Td,
Th,
Flex,
} from '@chakra-ui/react';
import Image from 'next/image';
import NextLink from 'next/link';
import React, { useEffect, useState } from 'react';

import {
courseIdToProgramIdLists,
languageIdToName,
languageIds,
programIdToName,
} from '../../../../problems/problemData';
import { getLanguageIdFromSessionStorage, setLanguageIdToSessionStorage } from '../../../lib/SessionStorage';

export const Course: React.FC<{
courseId: string;
userSolvedProblems: { programId: string; languageId: string }[];
}> = ({ courseId, userSolvedProblems }) => {
const [selectedLanguageId, setSelectedLanguageId] = useState('');

const SPECIFIED_COMPLETION_COUNT = 2;

useEffect(() => {
setSelectedLanguageId(getLanguageIdFromSessionStorage());
}, []);

const handleSelectLanguage = (event: React.ChangeEvent<HTMLSelectElement>): void => {
const inputValue = event.target.value;
setLanguageIdToSessionStorage(inputValue);
setSelectedLanguageId(inputValue);
};

const countUserSolvedProblems = (programId: string, languageId: string): number => {
return userSolvedProblems.filter(
(userSolvedProblem) => userSolvedProblem.programId === programId && userSolvedProblem.languageId === languageId
).length;
};

return (
<main>
<Heading as="h1" marginBottom="4">
Lessons
</Heading>
<Select
marginBottom="4"
maxW="300"
placeholder="Select language"
value={selectedLanguageId}
onChange={(e) => handleSelectLanguage(e)}
>
{languageIds.map((languageId) => (
<option key={languageId} value={languageId}>
{languageIdToName[languageId]}
</option>
))}
</Select>
<VStack align="stretch">
{courseIdToProgramIdLists[courseId].map((programIds, iLesson) => (
<Box key={iLesson}>
<Accordion allowToggle>
<AccordionItem>
<AccordionButton>
<Box flex="1" textAlign="left">
第{iLesson + 1}回
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th textAlign="left" width="50%">
プログラム
</Th>
<Th align="left" width="50%">
進捗
</Th>
</Tr>
</Thead>
<Tbody>
{programIds.map((programId) => (
<Tr key={programId}>
<Td>
<NextLink passHref href={`${courseId}/programs/${programId}`}>
{programIdToName[programId]}
</NextLink>
</Td>
<Td>
<Flex>
<p>
{countUserSolvedProblems(programId, selectedLanguageId)} /{' '}
{SPECIFIED_COMPLETION_COUNT}
</p>
{countUserSolvedProblems(programId, selectedLanguageId) >=
SPECIFIED_COMPLETION_COUNT && (
<Box h={4} ml={2} position={'relative'} w={4}>
<Image fill alt="完了の王冠" src="/crown.png" />
</Box>
)}
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</AccordionPanel>
</AccordionItem>
</Accordion>
</Box>
))}
</VStack>
</main>
);
};
102 changes: 21 additions & 81 deletions src/app/(withAuth)/courses/[courseId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,30 @@
'use client';

import {
Box,
Heading,
OrderedList,
ListItem,
VStack,
Accordion,
AccordionItem,
AccordionButton,
AccordionIcon,
AccordionPanel,
Select,
} from '@chakra-ui/react';
import type { NextPage } from 'next';
import NextLink from 'next/link';
import React, { useEffect, useState } from 'react';
import { redirect } from 'next/navigation';

import { prisma } from '../../../../infrastructures/prisma';
import { getNullableSessionOnServer } from '../../../../utils/session';
import { fetchUserSolvedProblems } from '../../../lib/actions';

import {
courseIdToProgramIdLists,
languageIdToName,
languageIds,
programIdToName,
} from '../../../../problems/problemData';
import { getLanguageIdFromSessionStorage, setLanguageIdToSessionStorage } from '../../../lib/SessionStorage';
import { Course } from './Course';

const CoursePage: NextPage<{ params: { courseId: string } }> = ({ params }) => {
const [selectedLanguageId, setSelectedLanguageId] = useState('');
const CoursePage: NextPage<{ params: { courseId: string } }> = async ({ params }) => {
const { session } = await getNullableSessionOnServer();
const user =
session &&
(await prisma.user.findUnique({
where: {
id: session.getUserId(),
},
}));

useEffect(() => {
setSelectedLanguageId(getLanguageIdFromSessionStorage());
}, []);
if (!user) {
return redirect('/auth');
}

const handleSelectLanguage = (event: React.ChangeEvent<HTMLSelectElement>): void => {
const inputValue = event.target.value;
setLanguageIdToSessionStorage(inputValue);
setSelectedLanguageId(inputValue);
};
const courseId = params.courseId;
const userSolvedProblems = await fetchUserSolvedProblems(user.id, courseId);

return (
<main>
<Heading as="h1" marginBottom="4">
Lessons
</Heading>
<Select
marginBottom="4"
maxW="300"
placeholder="Select language"
value={selectedLanguageId}
onChange={(e) => handleSelectLanguage(e)}
>
{languageIds.map((languageId) => (
<option key={languageId} value={languageId}>
{languageIdToName[languageId]}
</option>
))}
</Select>
<VStack align="stretch">
{courseIdToProgramIdLists[params.courseId].map((programIds, iLesson) => (
<Box key={iLesson}>
<Accordion allowToggle>
<AccordionItem>
<AccordionButton>
<Box flex="1" textAlign="left">
第{iLesson + 1}回
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4}>
<OrderedList>
{programIds.map((programId) => (
<ListItem key={programId}>
<NextLink passHref href={`/programs/${programId}`}>
{programIdToName[programId]}
</NextLink>
</ListItem>
))}
</OrderedList>
</AccordionPanel>
</AccordionItem>
</Accordion>
</Box>
))}
</VStack>
</main>
);
return <Course courseId={params.courseId} userSolvedProblems={userSolvedProblems} />;
};

export default CoursePage;
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import { Box, Button, Flex, HStack, VStack } from '@chakra-ui/react';
import { useRef } from 'react';

import { SyntaxHighlighter } from '../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../components/organisms/TurtleGraphics';
import type { ProblemType } from '../../../../types';
import { SyntaxHighlighter } from '../../../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../../../components/organisms/TurtleGraphics';
import type { ProblemType } from '../../../../../../types';

interface CheckpointProblemProps {
problemProgram: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
import { Box, Button, Flex, HStack, VStack } from '@chakra-ui/react';
import { useRef } from 'react';

import { SyntaxHighlighter } from '../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../components/organisms/TurtleGraphics';
import type { ProblemType } from '../../../../types';
import { SyntaxHighlighter } from '../../../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../../../components/organisms/TurtleGraphics';
import type { ProblemType } from '../../../../../../types';

interface ExecutionResultProblemProps {
problemProgram: string;
selectedLanguageId: string;
setStep: (step: ProblemType) => void;
handleComplete: () => void;
}

export const ExecutionResultProblem: React.FC<ExecutionResultProblemProps> = ({
handleComplete,
problemProgram,
selectedLanguageId,
setStep,
Expand All @@ -31,6 +33,7 @@ export const ExecutionResultProblem: React.FC<ExecutionResultProblemProps> = ({
// TODO: 一旦アラートで表示
if (isCorrect) {
alert('正解です。この問題は終了です');
handleComplete();
} else {
alert('不正解です。チェックポイントごとに回答してください');
setStep('checkpoint');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import { Box, Button, Flex, HStack, VStack } from '@chakra-ui/react';
import { useRef } from 'react';

import { SyntaxHighlighter } from '../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../components/organisms/TurtleGraphics';
import { SyntaxHighlighter } from '../../../../../../components/organisms/SyntaxHighlighter';
import type { TurtleGraphicsHandle } from '../../../../../../components/organisms/TurtleGraphics';
import { TurtleGraphics } from '../../../../../../components/organisms/TurtleGraphics';

interface StepProblemProps {
beforeCheckPointLine: number;
Expand All @@ -14,11 +14,13 @@ interface StepProblemProps {
selectedLanguageId: string;
setBeforeCheckPointLine: (line: number) => void;
setCurrentCheckPointLine: (line: number) => void;
handleComplete: () => void;
}

export const StepProblem: React.FC<StepProblemProps> = ({
beforeCheckPointLine,
currentCheckPointLine,
handleComplete,
problemProgram,
selectedLanguageId,
setBeforeCheckPointLine,
Expand All @@ -39,6 +41,7 @@ export const StepProblem: React.FC<StepProblemProps> = ({

if (currentCheckPointLine === problemProgramLines) {
alert('正解です。この問題は終了です');
handleComplete();
} else {
alert('正解です。次の行に進みます');
setBeforeCheckPointLine(currentCheckPointLine);
Expand Down
Loading