Skip to content

Commit

Permalink
[BBB-114] Feat: 피드백 모달 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
msjang4 committed Jul 26, 2024
1 parent 6dc78f4 commit 81410a9
Show file tree
Hide file tree
Showing 12 changed files with 663 additions and 102 deletions.
7 changes: 6 additions & 1 deletion app/study/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ export default function StudyPage() {
}
return (
<div className="flex space-x-4 justify-center">
<StudyDashBoard details={details} round={round} setRound={setRound} />
<StudyDashBoard
details={details}
studyId={Number(studyId)}
round={round}
setRound={setRound}
/>
<StudyAbout details={details} users={round.users} />
</div>
);
Expand Down
42 changes: 34 additions & 8 deletions components/study/dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,64 @@ import {
TableRow
} from '@/components/ui/table/table';
import getRound from '@/lib/api/study/get-round';
import { AlgorithmRound, StudyDetails } from '@/types/study/study-detail';
import { userState } from '@/recoil/userAtom';
import {
AlgorithmProblemInfo,
AlgorithmRound,
StudyDetails
} from '@/types/study/study-detail';
import { useParams } from 'next/navigation';
import { useRecoilState } from 'recoil';
import FeedbackDialog from '../feedback-dialog';

export default function StudyDashBoard({
details,
studyId,
round,
setRound
}: {
details: StudyDetails;
studyId: number;
round: AlgorithmRound;
setRound: (round: AlgorithmRound) => void;
}) {
return (
<div className="mt-5 bg-background rounded-lg border p-6 w-full max-w-4xl h-full">
<DashBoardHeader round={round} setRound={setRound} details={details} />
<DashBoardBody round={round} />
<DashBoardBody round={round} studyId={studyId} />
</div>
);
}

function DashBoardBody({ round }: { round: AlgorithmRound }) {
function DashBoardBody({
round,
studyId
}: {
round: AlgorithmRound;
studyId: number;
}) {
const [my, _] = useRecoilState(userState);
const myTasks = round.users[my!.id].tasks;
return (
<div className="overflow-auto rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">스터디원</TableHead>
{Object.values(round.problems).map((problem, index) => (
<TableHead key={index} className="text-center">
{problem.title}
</TableHead>
))}
{Object.entries(round.problems).map(
([problemId, problem]: [string, AlgorithmProblemInfo], index) => (
<TableHead key={index} className="text-center">
<p>{problem.title}</p>

{myTasks[Number(problemId)] && (
<FeedbackDialog
problem={{ ...problem, problemId: Number(problemId) }}
studyId={studyId}
></FeedbackDialog>
)}
</TableHead>
)
)}
</TableRow>
</TableHeader>
<TableBody>
Expand Down
7 changes: 5 additions & 2 deletions components/study/dashboard/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
StudyMemberInfo
} from '@/types/study/study-detail';

import { userState } from '@/recoil/userAtom';
import { useState } from 'react';
import { useRecoilState } from 'recoil';

export function Row({
userId,
Expand All @@ -23,6 +25,7 @@ export function Row({
// await refreshSolveTF(userId);
setIsLoading(false);
};
const [my, _] = useRecoilState(userState);

return (
<TableRow>
Expand All @@ -32,11 +35,11 @@ export function Row({
<RefreshIcon className={isLoading ? 'animate-spin' : ''} />
</Button>
</TableCell>
{Object.entries(problems).map(([key, value]) => (
{Object.entries(problems).map(([key, problem]) => (
<TableCell key={key} className="text-center">
{user.tasks[Number(key)] ? (
<div className="flex items-center justify-center">
<CheckIcon className="w-5 h-5 text-primary" />
<CheckIcon className="w-5 h-5" />
</div>
) : (
<div className="flex items-center justify-center">
Expand Down
196 changes: 196 additions & 0 deletions components/study/feedback-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
'use client';
import { Button } from '@/components/ui/button/button';
import { Checkbox } from '@/components/ui/checkbox/checkbox';
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogTrigger
} from '@/components/ui/dialog/dialog';
import { Label } from '@/components/ui/label/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio/radio-group';
import giveDifficultyFeedback from '@/lib/api/algo/give-feedback';
import { cn } from '@/lib/utils';
import { FeedbackAlgorithmProblemReq } from '@/types/algo/feedback';
import { AlgorithmProblemInfo } from '@/types/study/study-detail';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { FlagIcon, FrownIcon, SmileIcon } from '../ui/icon/icon';

export default function FeedbackDialog({
problem,
studyId
}: {
problem: AlgorithmProblemInfo;
studyId: number;
}) {
const [difficulty, setDifficulty] = useState('3');
const [again, setAgain] = useState(false);

const handleSubmit = async () => {
const feedback: FeedbackAlgorithmProblemReq = {
problemId: problem.problemId,
studyId,
difficulty: Number(difficulty),
again
};
try {
const response = await giveDifficultyFeedback(feedback);
toast.success('피드백이 반영되었습니다.');
} catch (error: any) {
toast.error(error.response.data.error);
console.error(error);
}
};
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" className="px-1 py-0 h-min">
난이도 피드백
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<div className="flex flex-col items-center justify-center gap-6">
<div className="space-y-2 text-center">
<h3 className="text-2xl font-bold">{problem.title}</h3>

<p className="text-muted-foreground">문제 난이도는 어떠셨나요?</p>
</div>
<RadioGroup
aria-label="Difficulty Level"
className="flex items-center gap-4 pb-4"
value={difficulty.toString()}
onValueChange={(value) => {
setDifficulty(value);
}}
>
<RadioGroupItem value="1" id="very-easy" className="peer sr-only" />
<Label
htmlFor="very-easy"
className="flex flex-col items-center gap-2 cursor-pointer"
>
<div
className={cn(
'group border hover:bg-gray-600 aspect-square flex items-center justify-center w-12 rounded-full bg-primary text-primary-foreground peer-aria-checked:bg-primary peer-aria-checked:text-primary-foreground',
difficulty == '1' ? 'bg-gray-900' : ''
)}
>
<SmileIcon
className={cn(
'w-6 h-6 group-hover:stroke-white rotate-180',
difficulty == '1' ? 'stroke-white' : ''
)}
/>
</div>
<span className="text-sm font-medium">매우 쉬움</span>
</Label>
<RadioGroupItem value="2" id="easy" className="peer sr-only" />
<Label
htmlFor="easy"
className="flex flex-col items-center gap-2 cursor-pointer"
>
<div
className={cn(
'group border hover:bg-gray-600 aspect-square flex items-center justify-center w-12 rounded-full bg-primary text-primary-foreground peer-aria-checked:bg-primary peer-aria-checked:text-primary-foreground',
difficulty == '2' ? 'bg-gray-900' : ''
)}
>
<SmileIcon
className={cn(
'w-6 h-6 group-hover:stroke-white',
difficulty == '2' ? 'stroke-white' : ''
)}
/>
</div>
<span className="text-sm font-medium">쉬움</span>
</Label>
<RadioGroupItem value="3" id="normal" className="peer sr-only" />
<Label
htmlFor="normal"
className="flex flex-col items-center gap-2 cursor-pointer"
>
<div
className={cn(
'group border hover:bg-gray-600 aspect-square flex items-center justify-center w-12 rounded-full bg-primary text-primary-foreground peer-aria-checked:bg-primary peer-aria-checked:text-primary-foreground',
difficulty == '3' ? 'bg-gray-900' : ''
)}
>
<FlagIcon
className={cn(
'w-6 h-6 group-hover:stroke-white',
difficulty == '3' ? 'stroke-white' : ''
)}
/>
</div>
<span className="text-sm font-medium">보통</span>
</Label>
<RadioGroupItem value="4" id="difficult" className="peer sr-only" />
<Label
htmlFor="difficult"
className="flex flex-col items-center gap-2 cursor-pointer"
>
<div
className={cn(
'group border hover:bg-gray-600 aspect-square flex items-center justify-center w-12 rounded-full bg-primary text-primary-foreground peer-aria-checked:bg-primary peer-aria-checked:text-primary-foreground',
difficulty == '4' ? 'bg-gray-900' : ''
)}
>
<FrownIcon
className={cn(
'w-6 h-6 group-hover:stroke-white',
difficulty == '4' ? 'stroke-white' : ''
)}
/>
</div>
<span className="text-sm font-medium">어려움</span>
</Label>
<RadioGroupItem
value="5"
id="very-difficult"
className="peer sr-only"
/>
<Label
htmlFor="very-difficult"
className="flex flex-col items-center gap-2 cursor-pointer"
>
<div
className={cn(
'group border hover:bg-gray-600 aspect-square flex items-center justify-center w-12 rounded-full bg-primary text-primary-foreground peer-aria-checked:bg-primary peer-aria-checked:text-primary-foreground',
difficulty == '5' ? 'bg-gray-900' : ''
)}
>
<FrownIcon
className={cn(
'w-6 h-6 group-hover:stroke-white rotate-180',
difficulty == '5' ? 'stroke-white' : ''
)}
/>
</div>
<span className="text-sm font-medium">매우 어려움</span>
</Label>
</RadioGroup>
<div className="flex items-center space-x-2">
<Checkbox
id="challenge-again"
checked={again}
onCheckedChange={(checked: any) => setAgain(checked)}
/>
<Label htmlFor="challenge-again">다시 풀고 싶은 문제</Label>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button
className="hover:bg-gray-600 text-gray-50 bg-gray-900"
onClick={handleSubmit}
type="button"
>
피드백 제출
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
30 changes: 30 additions & 0 deletions components/ui/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client';

import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import * as React from 'react';

import { cn } from '@/lib/utils';

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;

export { Checkbox };
Loading

0 comments on commit 81410a9

Please sign in to comment.