diff --git a/web/app/solutions/[id]/page.tsx b/web/app/solutions/[id]/page.tsx index 64e7cc1..02b8b2e 100644 --- a/web/app/solutions/[id]/page.tsx +++ b/web/app/solutions/[id]/page.tsx @@ -15,6 +15,7 @@ import NavigateBack from "@/components/NavigateBack"; import FileStructureDisplay from "@/components/FileStructureDisplay"; import QuestionAnswersDisplay from "@/components/solution/questions/QuestionAnswersDisplay"; import {useSpotlightStage2} from "@/hooks/spotlight/stage2"; +import CommentTab from "@/components/solution/CommentTab"; // Every 30s const REFETCH_INTERVAL = 1000 * 30; @@ -120,6 +121,7 @@ const SolutionDetailsPage = ({ params }: { params: { id: string } }) => { Code )} + Comments {solution.assignment.language === AssignmentLanguage.QuestionBased ? ( @@ -144,6 +146,9 @@ const SolutionDetailsPage = ({ params }: { params: { id: string } }) => { )} + + + {executorModalOpen && solution.job !== undefined && diff --git a/web/components/solution/CommentTab.tsx b/web/components/solution/CommentTab.tsx new file mode 100644 index 0000000..8f4b6ad --- /dev/null +++ b/web/components/solution/CommentTab.tsx @@ -0,0 +1,53 @@ +import useApiServiceClient from "@/hooks/useApiServiceClient"; +import useClientQuery from "@/hooks/useClientQuery"; +import { Solution } from "@/service/types/tasky"; +import {Badge, Button, Card, Group, Stack, Title} from "@mantine/core"; +import AssignmentDateDisplay from "@/components/assignments/AssignmentDateDisplay"; +import RichTextDisplay from "@/components/display/RichTextDisplay"; +import useCurrentUser from "@/hooks/useCurrentUser"; +import {IconPlus} from "@tabler/icons-react"; +import {useState} from "react"; +import CreateCommentModal from "@/components/solution/CreateCommentModal"; + +interface CommentTabProps { + solution: Solution; +} + +const CommentTab = ({solution}: CommentTabProps) => { + + const api = useApiServiceClient(); + const {user} = useCurrentUser(); + const [createModalOpen, setCreateModalOpen] = useState(false); + const [comments, refetch] = useClientQuery(() => api.getCodeComments(solution.id)); + + return ( + <> + + + + + {(comments ?? []).map((comment) => ( + + + {comment.title} + {comment.commentor === user?.id && ( + Your comment + )} + + + + ))} + + {createModalOpen && ( + setCreateModalOpen(false)} + /> + )} + + ); +} + +export default CommentTab; diff --git a/web/components/solution/CreateCommentModal.tsx b/web/components/solution/CreateCommentModal.tsx new file mode 100644 index 0000000..be7337c --- /dev/null +++ b/web/components/solution/CreateCommentModal.tsx @@ -0,0 +1,63 @@ +import { Solution } from "@/service/types/tasky"; +import {Button, Divider, Group, Modal, Stack, TextInput} from "@mantine/core"; +import {useForm} from "@mantine/form"; +import RichTextInput from "@/components/form/RichTextInput"; +import useApiServiceClient from "@/hooks/useApiServiceClient"; +import {notifications} from "@mantine/notifications"; + + +interface CreateCommentModalProps { + solution: Solution; + refetch: () => void; + onClose: () => void; +} + +const CreateCommentModal = ({solution, refetch, onClose}: CreateCommentModalProps) => { + + const form = useForm({ + initialValues: { + title: '', + content: '' + } + }); + const api = useApiServiceClient(); + + const onSubmit = form.onSubmit(async (values) => { + try { + await api.createCodeComment(solution.id, values.title, values.content); + refetch(); + onClose(); + } catch (e: any) { + notifications.show({ + title: 'Error', + message: e?.message ?? "Failed to create comment", + }) + } + }); + + return ( + +
+ + + + + + + + + + +
+ ); +} + +export default CreateCommentModal; diff --git a/web/service/ApiService.ts b/web/service/ApiService.ts index 4b95144..a7bdbde 100644 --- a/web/service/ApiService.ts +++ b/web/service/ApiService.ts @@ -15,7 +15,7 @@ import { Solution, SolutionFilesResponse, SolutionsResponse, - AssignmentWish + AssignmentWish, CodeComment } from "@/service/types/tasky"; import { FileStructureTree } from "@/components/FileStructure"; @@ -219,6 +219,14 @@ class ApiService { await this.delete(`/tasky/groups/${groupId}/assignment_wishes/${wishId}`); } + public async getCodeComments(solutionId: number): Promise { + return await this.get(`/tasky/solutions/${solutionId}/code_comments`); + } + + public async createCodeComment(solutionId: number, title: string, content: string): Promise { + return await this.post(`/tasky/solutions/${solutionId}/code_comments`, {title, content}); + } + public async createCodeTests( groupId: number, assignmentId: number, diff --git a/web/service/types/tasky.ts b/web/service/types/tasky.ts index 5fe8ad1..ab4ae99 100644 --- a/web/service/types/tasky.ts +++ b/web/service/types/tasky.ts @@ -144,3 +144,10 @@ export interface AssignmentWish { title: string; description: string; } + +export interface CodeComment { + id: number; + title: string; + content: string; + commentor: number; +}