diff --git a/app/post/[id]/page.tsx b/app/post/[id]/page.tsx new file mode 100644 index 0000000..404143d --- /dev/null +++ b/app/post/[id]/page.tsx @@ -0,0 +1,142 @@ +/** + * This code was generated by v0 by Vercel. + * @see https://v0.dev/t/EYJ4s50uN2T + * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app + */ + +/** Add fonts into your Next.js project: + +import { Libre_Baskerville } from 'next/font/google' + +libre_baskerville({ + subsets: ['latin'], + display: 'swap', +}) + +To read more about using these font, please visit the Next.js documentation: +- App Directory: https://nextjs.org/docs/app/building-your-application/optimizing/fonts +- Pages Directory: https://nextjs.org/docs/pages/building-your-application/optimizing/fonts +**/ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; + +export default function post() { + return ( +
+
+
+

+ 스터디 입장전 필독 +

+
+
+ + + JD + + John Doe +
+ | + August 13, 2024 +
+
+ +

+ 스터디 구성 최소 조건 요약 스터디 주기 및 인원 최소 4-6주 이상 지속 + 매주 또는 격주 진행 2시간 이내

+

중간 쉬는시간 갖기 인원 4-8명 참여자 스터디 리더 스터디 방향 + 길잡이 매회차 스터디 사전 질문 준비 스터디원 공부 반드시 하고 참여하기 +

본인이 이해한 부분 설명, 못한 부분 질문 기록 남기기 모든 + 참여자 - 발표내용에 대한 질문, 코멘트, 새로 배운 것, 중요한 부분 등 + 최소 3개 이상 공유 지원자 - 공유 내용 취합 및 정리 후 공개 장소에 기록 +

규칙 공부 내용과 관련된 모든 질문 환영 + 비난/비방/욕/뒷담화/무시 등 절대 금지 정신

+

1. 지식의 소화전 자신의 직업 또는 관심 분야에서 뛰어난 도서, + 아티클, 강연을 찾아서 그것들을 깊이있게 공부하기 공부재료 찾는 방법 - + 분야를 잘 아는 사람에게 좋아하는 책/작가, 영향을 받은 것들에 대해 + 물어보기 재료를 찾았으면 스터디 그룹 참여하기 공부 순서 정하기 공부 + 목록 구성하기

2. 통찰력 웅덩이 혼자 공부한 내용을 스터디에서 + 나누기 공부하면서 헷갈렸던 부분 잘못 이해한 부분 새로 알게된 사실 + 몰랐던 부분이 어디인지 확실히 알게 됨 내가 모르는 것은 질문하고 아는 + 것은 설명하기 가장 좋은 스터디 그룹은 다른 사람과 같이 배우는 것을 + 편안하게 느낄 수 있는 곳 분위기는 대화하는데 중요한 역할을 함 질 높은 + 스터디를 위한 스터디 리더와 스터디원이 필요

3. 안전한 공간 안 + 좋은 예시 자랑, 따돌림, 무시하는 말투, 무례하거나 싸우자 식의 태도 + 지나치게 한명만 많이 말하는 상황 개인에 대한 비난/비방 개인적 의견 + 충돌이 있을 때는 스터디가 끝난 후 따로 대화시간 갖기 스터디 내에서 + 따르는 규칙 정하기 어떤 질문이나 실수도 용인되는 환경 만들기

{' '} + 4. 지속력 빈도 - 매주 또는 격주 시간 - 2시간 이내, 일찍 끝나면 그대로 + 스터디 종료 중간에 쉬는시간 갖기 참여자들이 실제로 관심 있어하는 + 주제에 대해서 다루기 이상적인 스터디 공간과 환경 갖추기 서브그룹과 + 스터디 주기 적절히 활용하기

5. 동료 찾기 사내 스터디 - + 성공확률 반반 컨퍼런스, 강연 등 큰 집단은 커넥션이 약하고 비효율적 + 작은 스터디를 지속하면서 본인이 실제로 관심있는 주제를 같이 공부하는 + 것이 가장 효율적 의미있는 네트워킹(관계형성)은 안전한 공간에서 제일 잘 + 이루어짐 네트워킹에 가장 좋은 시간대는 스터디 전/후 +

+
+
+

Comments

+
+
+ + + JD + +
+
+
Jane Doe
+
+ August 13, 2024 +
+
+

+ This is a great article! I really enjoyed learning about the + future of web development and the importance of automation and + collaboration. Can't wait to see what the future holds. +

+
+
+
+ + + JD + +
+
+
John Smith
+
+ August 14, 2024 +
+
+

+ Excellent insights! I'm really excited about the potential of AI + and machine learning in web development. Can't wait to see how + these technologies will shape the future. +

+
+
+
+ + + JD + +
+
+
Emily Johnson
+
+ August 15, 2024 +
+
+

+ This article really resonates with me. I've been seeing the + benefits of automation and collaboration in my own work, and I'm + excited to see how the industry continues to evolve. Thanks for + the great insights! +

+
+
+
+
+
+ ); +} diff --git a/app/study/[id]/board/page.tsx b/app/study/[id]/board/page.tsx new file mode 100644 index 0000000..7fcd1c0 --- /dev/null +++ b/app/study/[id]/board/page.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from '@/components/ui/card/card'; +import { CalendarIcon } from '@/components/ui/icon/icon'; + +export default function Signup() { + return ( +
+
+ + + + 스터디 입장 전 필독 + + + 오프라인 주 1회 + + + + +

8월 17일

+
+
+ + + + 스터디 입장 전 필독 + + + 오프라인 주 1회 + + + + +

8월 17일

+
+
+ + + + 스터디 입장 전 필독 + + + 오프라인 주 1회 + + + + +

8월 17일

+
+
+ + + + + 스터디 입장 전 필독 + + + 오프라인 주 1회 + + + + +

8월 17일

+
+
+
+
+ ); +} diff --git a/app/study/[id]/page.tsx b/app/study/[id]/page.tsx index 9847905..d2987b2 100644 --- a/app/study/[id]/page.tsx +++ b/app/study/[id]/page.tsx @@ -11,11 +11,7 @@ import Spinner from '@/components/ui/spinner/spinner'; import getStudyDetails from '@/lib/api/study/get-details'; import startStudy from '@/lib/api/study/start'; import { userState } from '@/recoil/userAtom'; -import { - AlgorithmRound, - StudyDetails, - StudyStatus -} from '@/types/study/study-detail'; +import { Round, StudyDetails, StudyStatus } from '@/types/study/study-detail'; import { toast } from 'react-toastify'; import { useRecoilState } from 'recoil'; @@ -25,7 +21,7 @@ export default function StudyPage() { const studyId = Number(params.id); const [details, setDetails] = useState(); - const [round, setRound] = useState(); + const [round, setRound] = useState(); const [isParticipant, setIsParticipant] = useState(false); const [canStart, setCanStart] = useState(false); const [myData, setMyData] = useRecoilState(userState); diff --git a/components/study/dashboard/about.tsx b/components/study/dashboard/about.tsx index 586ef09..2abe639 100644 --- a/components/study/dashboard/about.tsx +++ b/components/study/dashboard/about.tsx @@ -3,6 +3,8 @@ import { AvatarFallback, AvatarImage } from '@/components/ui/avatar/avatar'; +import { BellIcon, BoxIcon, FilePenIcon } from '@/components/ui/icon/icon'; +import { StudyType } from '@/constants/study/study'; import { StudyDetails, StudyMemberInfo } from '@/types/study/study-detail'; export default function StudyAbout({ @@ -21,8 +23,17 @@ export default function StudyAbout({

소개

+

{details.introduce}

+
+
{}} + className="group w-fit hover:text-gray-900 text-blue-600 flex items-center space-x-2" + > + + 공지사항 +
@@ -57,6 +68,32 @@ export default function StudyAbout({ {details.reliabilityLimit} 이상
+ {StudyType[details.studyType as keyof typeof StudyType] === + StudyType.BOOK && ( + <> +
+ + {/* TODO details.book.title로 변경 */} + 자바의 정석 +
+ +
{}} + className="group w-fit hover:text-gray-900 text-blue-600 flex items-center space-x-2" + > + + 과제 선택지 수정 +
+ +
{}} + className="group w-fit hover:text-gray-900 text-blue-600 flex items-center space-x-2" + > + + 과제 투표 +
+ + )}

diff --git a/components/study/dashboard/book-row.tsx b/components/study/dashboard/book-row.tsx new file mode 100644 index 0000000..96b5187 --- /dev/null +++ b/components/study/dashboard/book-row.tsx @@ -0,0 +1,81 @@ +import { PlayIcon, PuzzleIcon, XIcon } from '@/components/ui/icon/icon'; +import { TableCell, TableRow } from '@/components/ui/table/table'; +import { + AlgorithmProblemInfo, + BookMemberInfo, + StudyAssignmentInfo +} from '@/types/study/study-detail'; + +import { Button } from '@/components/ui/button/button'; +import { userState } from '@/recoil/userAtom'; +import { useState } from 'react'; +import { useRecoilState } from 'recoil'; + +export function BookRow({ + studyId, + roundIdx, + userId, + assignment, + user +}: { + studyId: number; + roundIdx: number; + userId: number; + assignment: StudyAssignmentInfo; + user: BookMemberInfo; +}) { + const [isLoading, setIsLoading] = useState(false); + const [my, _] = useRecoilState(userState); + return ( + + +

{user.username}

+
+ + +

{assignment.content}

+
+ +
+ {user.video ? ( + + ) : ( + + )} +
+
+ + +
+ {user.quiz ? ( + + ) : ( + + )} +
+
+
+ ); +} + +const createRefIdToProblemIdMap = (problems: { + [problemId: number]: AlgorithmProblemInfo; +}) => { + const map: { [refId: number]: number } = {}; + Object.entries(problems).forEach(([key, problem]) => { + map[problem.refId] = Number(key); + }); + return map; +}; + +const getSolvedProblemRefIds = (res: any) => { + const solvedProblemRefIds: number[] = []; + res.data.items.forEach((item: any) => { + solvedProblemRefIds.push(item.problemId); + }); + return solvedProblemRefIds; +}; diff --git a/components/study/dashboard/dashboard.tsx b/components/study/dashboard/dashboard.tsx index 789bd83..f0d18d2 100644 --- a/components/study/dashboard/dashboard.tsx +++ b/components/study/dashboard/dashboard.tsx @@ -13,16 +13,20 @@ import { TableHeader, TableRow } from '@/components/ui/table/table'; +import { StudyType } from '@/constants/study/study'; import getRound from '@/lib/api/study/get-round'; import { userState } from '@/recoil/userAtom'; import { AlgorithmProblemInfo, AlgorithmRound, + BookRound, + Round, StudyDetails } from '@/types/study/study-detail'; import { useParams } from 'next/navigation'; import { useRecoilState } from 'recoil'; import FeedbackDialog from '../feedback-dialog'; +import { BookRow } from './book-row'; export default function StudyDashBoard({ details, @@ -32,18 +36,31 @@ export default function StudyDashBoard({ }: { details: StudyDetails; studyId: number; - round: AlgorithmRound; - setRound: (round: AlgorithmRound) => void; + round: Round; + setRound: (round: Round) => void; }) { - return ( -
- - -
- ); + const studyType = StudyType[details.studyType as keyof typeof StudyType]; + if (studyType === StudyType.ALGORITHM) { + return ( +
+ + +
+ ); + } else if (studyType === StudyType.BOOK) { + return ( +
+ + +
+ ); + } } -function DashBoardBody({ +function AlgorithmDashBoardBody({ round, studyId }: { @@ -91,14 +108,50 @@ function DashBoardBody({ ); } +function BookDashBoardBody({ + round, + studyId +}: { + round: BookRound; + studyId: number; +}) { + const [my, _] = useRecoilState(userState); + return ( +
+ + + + 스터디원 + 과제 내용 + 해설 영상 + 확인 문제 + + + + {Object.entries(round.users).map(([key, value]) => ( + + ))} + +
+
+ ); +} + function DashBoardHeader({ round, setRound, details }: { details: StudyDetails; - round: AlgorithmRound; - setRound: (round: AlgorithmRound) => void; + round: Round; + setRound: (round: Round) => void; }) { return (
@@ -112,8 +165,8 @@ function SelectRound({ round, setRound }: { - round: AlgorithmRound; - setRound: (round: AlgorithmRound) => void; + round: Round; + setRound: (round: Round) => void; }) { const params = useParams(); const studyId = Number(params.id); diff --git a/components/study/dashboard/row.tsx b/components/study/dashboard/row.tsx index a0e4c24..45ada92 100644 --- a/components/study/dashboard/row.tsx +++ b/components/study/dashboard/row.tsx @@ -2,15 +2,15 @@ import { Button } from '@/components/ui/button/button'; import { CheckIcon, RefreshIcon, XIcon } from '@/components/ui/icon/icon'; import { TableCell, TableRow } from '@/components/ui/table/table'; import { - AlgorithmProblemInfo, - StudyMemberInfo + AlgorithmMemberInfo, + AlgorithmProblemInfo } from '@/types/study/study-detail'; +import updateTaskStatus from '@/lib/api/algo/update-task-status'; import { userState } from '@/recoil/userAtom'; import { useState } from 'react'; -import { useRecoilState } from 'recoil'; import { toast } from 'react-toastify'; -import updateTaskStatus from '@/lib/api/algo/update-task-status'; +import { useRecoilState } from 'recoil'; export function Row({ studyId, @@ -23,7 +23,7 @@ export function Row({ roundIdx: number; userId: number; problems: { [problemId: number]: AlgorithmProblemInfo }; - user: StudyMemberInfo; + user: AlgorithmMemberInfo; }) { const [isLoading, setIsLoading] = useState(false); const refresh = async () => { @@ -60,15 +60,13 @@ export function Row({ {Object.entries(problems).map(([key, problem]) => ( - {user.tasks[Number(key)] ? ( -
+
+ {user.tasks[Number(key)] ? ( -
- ) : ( -
+ ) : ( -
- )} + )} +
))} diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/card/card.tsx b/components/ui/card/card.tsx new file mode 100644 index 0000000..afa13ec --- /dev/null +++ b/components/ui/card/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/icon/icon.tsx b/components/ui/icon/icon.tsx index 1c3d521..225019f 100644 --- a/components/ui/icon/icon.tsx +++ b/components/ui/icon/icon.tsx @@ -358,3 +358,82 @@ export function LectureIcon(props: Record) { ); } + +export function PlayIcon(props: Record) { + return ( + + + + ); +} +export function FilePenIcon(props: Record) { + return ( + + + + + + ); +} + +export function BellIcon(props: Record) { + return ( + + + + + ); +} +export function BoxIcon(props: Record) { + return ( + + + + + + ); +} diff --git a/lib/api/study/get-details.ts b/lib/api/study/get-details.ts index b2b4f0d..2a2db07 100644 --- a/lib/api/study/get-details.ts +++ b/lib/api/study/get-details.ts @@ -1,4 +1,4 @@ -import { AlgorithmStudyDetailsAndRound } from '@/types/study/study-detail'; +import { StudyDetailsAndRound } from '@/types/study/study-detail'; import axios from 'axios'; /** @@ -6,7 +6,7 @@ import axios from 'axios'; */ export default async function getStudyDetails( id: number -): Promise { +): Promise { return axios .get(`${process.env.NEXT_PUBLIC_API_SERVER_URL}/api/v1/studies/` + id) .then((response) => response.data); diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg new file mode 100644 index 0000000..6fa7543 Binary files /dev/null and b/public/placeholder-user.jpg differ diff --git a/types/study/study-detail.ts b/types/study/study-detail.ts index d76614a..f8f1ff5 100644 --- a/types/study/study-detail.ts +++ b/types/study/study-detail.ts @@ -1,12 +1,8 @@ import { BookResult } from '../book/book-result'; -export interface AlgorithmStudyDetailsAndRound { +export interface StudyDetailsAndRound { details: StudyDetails; - round: AlgorithmRound; -} -export interface BookStudyDetailsAndRound { - details: BookStudyDetails; - round: BookRound; + round: Round; } export interface StudyDetails { @@ -27,15 +23,12 @@ export interface BookStudyDetails extends StudyDetails { book: BookResult; } -export interface BookRound { - idx: number; - startDate: Date; - endDate: Date; +export interface BookRound extends Round { assignments: { [assignmentId: number]: StudyAssignmentInfo; }; users: { - [userId: number]: StudyMemberInfo; + [userId: number]: BookMemberInfo; }; } @@ -44,16 +37,21 @@ export enum StudyStatus { RUNNING = 'RUNNING', END = 'END' } - -export interface AlgorithmRound { +export interface Round { idx: number; startDate: Date; endDate: Date; + users: { + [userId: number]: StudyMemberInfo; + }; +} + +export interface AlgorithmRound extends Round { problems: { [problemId: number]: AlgorithmProblemInfo; }; users: { - [userId: number]: StudyMemberInfo; + [userId: number]: AlgorithmMemberInfo; }; } @@ -67,16 +65,30 @@ export interface AlgorithmProblemInfo { } export interface StudyAssignmentInfo { - assignmentId: number; - title: string; content: string; } export interface StudyMemberInfo { username: string; +} + +export interface AlgorithmMemberInfo extends StudyMemberInfo { baekjoonId: string; isUpdating: boolean; tasks: { [problemId: number]: boolean; }; } + +export interface BookMemberInfo extends StudyMemberInfo { + assignmentId: number; + video: VideoInfo; + quiz: QuizInfo; +} + +export interface VideoInfo { + link: string; +} +export interface QuizInfo { + link: string; +}