diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/CollaboratorsColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/CollaboratorsColumn.tsx index 4a984addbe..c5ff813d96 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/CollaboratorsColumn.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/CollaboratorsColumn.tsx @@ -1,7 +1,7 @@ -import { Question } from "@/types/Question"; import { ColumnDef } from "@tanstack/react-table"; import UserAvatar from "@/components/UserAvatar"; import { UserProfile } from "@/types/User"; +import { HistoryItem } from "@/types/History"; const mockCollaborators: UserProfile[] = [ { @@ -26,7 +26,7 @@ const mockCollaborators: UserProfile[] = [ }, ]; -const StatusColumn: ColumnDef = { +const StatusColumn: ColumnDef = { accessorKey: "_id", header: () =>
Collaborators
, cell: () => { diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DateColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DateColumn.tsx new file mode 100644 index 0000000000..294c556c02 --- /dev/null +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DateColumn.tsx @@ -0,0 +1,43 @@ +import { HistoryItem, HistoryItemSchema } from "@/types/History"; +import { ColumnDef } from "@tanstack/react-table"; +import { DataTableColumnHeader } from "../data-table-column-header"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card"; + +function formatDate(dateString: string): string { + const date = new Date(dateString); + const day = String(date.getDate()).padStart(2, '0'); + const month = date.toLocaleString('default', { month: 'long' }); + const year = date.getFullYear(); + return `${month} ${day}, ${year}`; // "November 05, 2024" +} + +const DateColumn: ColumnDef = { + accessorKey: "question.date", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const historyItem = HistoryItemSchema.parse(row.original); + const formattedDate = formatDate(historyItem.date); + // const formattedDate =historyItem.date; + + return ( + + + {formattedDate} + + +

{formattedDate}

+
+
+ ); + }, +}; + +export default DateColumn; diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DifficultyColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DifficultyColumn.tsx index d7deca443e..8ad792bb4a 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DifficultyColumn.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DifficultyColumn.tsx @@ -1,48 +1,29 @@ -import { Question, QuestionSchema } from "@/types/Question"; +import { HistoryItem, HistoryItemSchema } from "@/types/History"; import { ColumnDef } from "@tanstack/react-table"; import { DataTableColumnHeader } from "../data-table-column-header"; import { cn } from "@/lib/utils"; -const DifficultyColumn: ColumnDef = { - accessorKey: "difficulty", +const DifficultyPreferenceColumn: ColumnDef = { + accessorKey: "difficultyPreference", header: ({ column }) => { return ( - + ); }, cell: ({ row }) => { - const question = QuestionSchema.parse(row.original); - + const historyItem = HistoryItemSchema.parse(row.original); return ( - {question.difficulty} - + className={cn( + historyItem.difficultyPreference == "Easy" && "text-difficulty-easy", + historyItem.difficultyPreference == "Medium" && "text-difficulty-medium", + historyItem.difficultyPreference == "Hard" && "text-difficulty-hard" + )} + > + {historyItem.difficultyPreference} + ); }, - sortingFn: (rowA, rowB) => { - const difficultyMap = { - Easy: 0, - Medium: 1, - Hard: 2, - }; - return ( - difficultyMap[rowA.original.difficulty] - - difficultyMap[rowB.original.difficulty] - ); - }, - filterFn: (row, key, value) => { - return value.includes(row.getValue(key)); - }, }; -export default DifficultyColumn; +export default DifficultyPreferenceColumn; diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DurationColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DurationColumn.tsx index 741a5c0926..de35f4b470 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DurationColumn.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/DurationColumn.tsx @@ -1,8 +1,8 @@ -import { Question } from "@/types/Question"; import { ColumnDef } from "@tanstack/react-table"; import { DataTableColumnHeader } from "../data-table-column-header"; +import { HistoryItem } from "@/types/History"; -const DurationColumn: ColumnDef = { +const DurationColumn: ColumnDef = { accessorKey: "duration", header: ({ column }) => { return ( diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/EloColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/EloColumn.tsx index 5c4555bef5..df1b25acc3 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/EloColumn.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/EloColumn.tsx @@ -1,8 +1,8 @@ -import { Question } from "@/types/Question"; +import { HistoryItem } from "@/types/History"; import { ColumnDef } from "@tanstack/react-table"; import { Zap } from "lucide-react"; -const StatusColumn: ColumnDef = { +const StatusColumn: ColumnDef = { accessorKey: "_id", header: () =>
Elo
, cell: () => { diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TitleColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TitleColumn.tsx index 13838fdaec..6f078a906b 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TitleColumn.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TitleColumn.tsx @@ -1,32 +1,35 @@ -import { Question, QuestionSchema } from "@/types/Question"; import { ColumnDef } from "@tanstack/react-table"; import { DataTableColumnHeader } from "../data-table-column-header"; import { - HoverCard, - HoverCardContent, - HoverCardTrigger, -} from "@/components/ui/hover-card"; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { HistoryItem, HistoryItemSchema } from "@/types/History"; -const TitleColumn: ColumnDef = { - accessorKey: "title", +const TitleColumn: ColumnDef = { + accessorKey: "questionTitle", header: ({ column }) => { return ( ); }, cell: ({ row }) => { - const question = QuestionSchema.parse(row.original); + const historyItem = HistoryItemSchema.parse(row.original); return ( - - - {question.title} - - -

{question.title}

-

{question.description}

-
-
+ + +

{historyItem.question.title}

+
+ + + {historyItem.question.title} + + +
); }, }; diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TopicsColumn.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TopicsColumn.tsx index 6d903cf6b9..40fa326337 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TopicsColumn.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionColumns/TopicsColumn.tsx @@ -1,42 +1,42 @@ -import { Question, QuestionSchema } from "@/types/Question"; +import { HistoryItem, HistoryItemSchema } from "@/types/History"; import { ColumnDef } from "@tanstack/react-table"; import { DataTableColumnHeader } from "../data-table-column-header"; -const TopicsColumn: ColumnDef = { - accessorKey: "topics", +const TopicPreferenceColumn: ColumnDef = { + accessorKey: "topicPreference", header: ({ column }) => { return ( - + ); }, cell: ({ row }) => { - const question = QuestionSchema.parse(row.original); + const historyItem = HistoryItemSchema.parse(row.original); return (
- {question.categories.map((category, id) => ( - - ))} -
+ {historyItem.topicPreference.map((category, id) => ( + + ))} + ); }, sortingFn: (rowA, rowB) => { - const a = rowA.original.categories.sort(); - const b = rowB.original.categories.sort(); + const a = rowA.original.topicPreference.sort(); + const b = rowB.original.topicPreference.sort(); return a.length !== b.length ? a.length - b.length : a.join().localeCompare(b.join()); }, filterFn: (row, key, value: string[]) => { - const rowValue = row.original.categories; + const rowValue = row.original.topicPreference; return rowValue.some((v) => value.includes(v)); }, }; -export default TopicsColumn; +export default TopicPreferenceColumn; diff --git a/frontend/src/app/profile/_components/QuestionHistory/QuestionHistory.tsx b/frontend/src/app/profile/_components/QuestionHistory/QuestionHistory.tsx index fd8a0f3eef..90105d77ad 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/QuestionHistory.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/QuestionHistory.tsx @@ -1,31 +1,39 @@ import { - getQuestionCategories, - getQuestions, - } from "@/services/questionService"; - import { QuestionsResponse } from "@/types/Question"; - import { CategoriesResponse } from "@/types/Category"; - import { questionTableColumns } from "./column"; - import { DataTable } from "./data-table"; - - export default async function QuestionTable() { - const questionsResponse: QuestionsResponse = await getQuestions(); - const categoriesResponse: CategoriesResponse = await getQuestionCategories(); - - if (!questionsResponse.data) { - return
{questionsResponse.message}
; - } - - const questions = questionsResponse.data.questions; - const categories = categoriesResponse.data - ? categoriesResponse.data.categories - : []; - - return ( - - ); + getQuestionCategories, + getQuestions, +} from "@/services/questionService"; +import { QuestionsResponse } from "@/types/Question"; +import { CategoriesResponse } from "@/types/Category"; +import { questionTableColumns } from "./column"; +import { DataTable } from "./data-table"; +import { HistoryResponseSchema } from "@/types/History"; +import { fetchHistory } from "@/services/userService"; + +export default async function HistoryTable() { + const questionsResponse: QuestionsResponse = await getQuestions(); + const categoriesResponse: CategoriesResponse = await getQuestionCategories(); + + let historyResponse; + historyResponse = await fetchHistory(); + + if (historyResponse.statusCode !== 200) { + throw new Error(historyResponse.message); } - \ No newline at end of file + + const validatedHistoryResponse = HistoryResponseSchema.parse(historyResponse); + + const history = validatedHistoryResponse.data; + + const questions = questionsResponse.data.questions; + const categories = categoriesResponse.data + ? categoriesResponse.data.categories + : []; + + return ( + + ); +} diff --git a/frontend/src/app/profile/_components/QuestionHistory/column.tsx b/frontend/src/app/profile/_components/QuestionHistory/column.tsx index 091acbef5e..9296cb7bf6 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/column.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/column.tsx @@ -1,6 +1,5 @@ "use client"; -import { Question } from "@/types/Question"; import { ColumnDef } from "@tanstack/react-table"; import TitleColumn from "./QuestionColumns/TitleColumn"; import TopicsColumn from "./QuestionColumns/TopicsColumn"; @@ -8,11 +7,12 @@ import DifficultyColumn from "./QuestionColumns/DifficultyColumn"; import DurationColumn from "./QuestionColumns/DurationColumn"; import CollaboratorsColumn from "./QuestionColumns/CollaboratorsColumn"; import EloColumn from "./QuestionColumns/EloColumn"; +import { HistoryItem } from "@/types/History"; +import DateColumn from "./QuestionColumns/DateColumn"; - - -export const questionTableColumns: ColumnDef[] = [ +export const questionTableColumns: ColumnDef[] = [ TitleColumn, + DateColumn, TopicsColumn, DifficultyColumn, DurationColumn, diff --git a/frontend/src/app/profile/_components/QuestionHistory/data-table-toolbar.tsx b/frontend/src/app/profile/_components/QuestionHistory/data-table-toolbar.tsx index 332d806daf..bd45a4a601 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/data-table-toolbar.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/data-table-toolbar.tsx @@ -4,8 +4,8 @@ import { Input } from "@/components/ui/input"; import { LucideSearch } from "lucide-react"; import { DataTableFacetedFilter } from "./data-table-faceted-filter"; import { Table } from "@tanstack/react-table"; -import { QuestionTableContext } from "@/contexts/QuestionTableContext"; import { useContext } from "react"; +import { HistoryTableContext } from "@/contexts/HistoryTableContext"; interface DataTableToolbarProps { table: Table; @@ -15,15 +15,15 @@ interface DataTableToolbarProps { export default function DataTableToolbar({ table, }: DataTableToolbarProps) { - const { categories } = useContext(QuestionTableContext); + const { categories } = useContext(HistoryTableContext); return (
- {table.getColumn("topics") && ( + {table.getColumn("topicPreference") && ( ({ @@ -33,9 +33,9 @@ export default function DataTableToolbar({ } /> )} - {table.getColumn("difficulty") && ( + {table.getColumn("difficultyPreference") && ( ({ - table.getColumn("title")?.setFilterValue(event.target.value) + table.getColumn("questionTitle")?.setFilterValue(event.target.value) } placeholder="Search" className="max-w-sm pl-10" diff --git a/frontend/src/app/profile/_components/QuestionHistory/data-table.tsx b/frontend/src/app/profile/_components/QuestionHistory/data-table.tsx index bd4cc90027..3e7e66492a 100644 --- a/frontend/src/app/profile/_components/QuestionHistory/data-table.tsx +++ b/frontend/src/app/profile/_components/QuestionHistory/data-table.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client" import { ColumnDef, @@ -28,18 +28,19 @@ import { DataTablePagination } from "./data-table-pagination"; import DataTableToolbar from "./data-table-toolbar"; import { Card } from "@/components/ui/card"; -import { QuestionTableProvider } from "@/contexts/QuestionTableContext"; +import { HistoryTableProvider } from "@/contexts/HistoryTableContext"; -interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; +interface DataTableProps { + columns: ColumnDef[]; + data: HistoryItem[]; categories?: string[]; } -export function DataTable({ +export function DataTable({ columns, data, -}: DataTableProps) { + categories, +}: DataTableProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); @@ -59,7 +60,7 @@ export function DataTable({ }); return ( - +
@@ -69,18 +70,16 @@ export function DataTable({ {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} ))} @@ -118,6 +117,6 @@ export function DataTable({
-
+ ); } diff --git a/frontend/src/contexts/HistoryTableContext.tsx b/frontend/src/contexts/HistoryTableContext.tsx new file mode 100644 index 0000000000..4a8b678402 --- /dev/null +++ b/frontend/src/contexts/HistoryTableContext.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { createContext, PropsWithChildren, useState, useEffect } from "react"; +import { fetchHistory } from "@/services/userService"; +import { HistoryResponse } from "@/types/History"; +import { Categories } from "@/types/Category"; +import { getQuestionCategories } from "@/services/questionService"; + +export const HistoryTableContext = createContext<{ + historyData: HistoryResponse['data']; + categories: Categories; +}>({ + historyData: [], + categories: [], +}); + +export function HistoryTableProvider(props: PropsWithChildren) { + const [historyData, setHistoryData] = useState([]); + const [categories, setCategories] = useState([]); + + useEffect(() => { + const loadHistory = async () => { + try { + const response = await fetchHistory(); + if (response.statusCode === 200) { + setHistoryData(response.data); + } else { + console.error("Failed to fetch history:", response.message); + } + } catch (error) { + console.error("Error fetching history:", error); + } + }; + + loadHistory(); + }, []); + + useEffect(() => { + getQuestionCategories().then((categoriesResponse) => { + if (!categoriesResponse.data) { + return; + } + setCategories(categoriesResponse.data.categories); + }); + }, []); + + const providerValue = { + historyData, + categories + }; + + return ( + + {props.children} + + ); +} diff --git a/frontend/src/services/userService.ts b/frontend/src/services/userService.ts index 9a43526577..bf33928f31 100644 --- a/frontend/src/services/userService.ts +++ b/frontend/src/services/userService.ts @@ -7,6 +7,7 @@ import { UserProfileResponse, UserProfileResponseSchema, } from "@/types/User"; +import { HistoryResponse, HistoryResponseSchema } from "@/types/History"; import { revalidateTag } from "next/cache"; import { cache } from "react"; @@ -70,3 +71,37 @@ export async function editUserProfile( }; } } + +export const fetchHistory = cache( + async function (): Promise { + const access_token = await getAccessToken(); + + try { + const res = await fetch( + process.env.PUBLIC_API_URL + `/api/collaboration/history`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${access_token}`, + }, + } + ); + + if (!res.ok) { + const errorData = await res.json(); + throw new Error(errorData.message || 'Failed to fetch history'); + } + + const resObj = await res.json(); + return HistoryResponseSchema.parse(resObj); + + } catch (error) { + return { + statusCode: 500, + message: String(error), + data: [], + }; + } + } +); diff --git a/frontend/src/types/History.ts b/frontend/src/types/History.ts new file mode 100644 index 0000000000..f2607f2216 --- /dev/null +++ b/frontend/src/types/History.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +export const HistoryItemSchema = z.object({ + sessionId: z.string(), + difficultyPreference: z.string(), + topicPreference: z.array(z.string()), + question: z.object({ + id: z.string(), + title: z.string(), + }), + status: z.string(), + date: z.string(), +}); + +const HistoryResponseSchema = z.object({ + statusCode: z.number(), + message: z.string(), + data: z.array(HistoryItemSchema), // Data should be an array of history items +}); + +type HistoryItem = z.infer; +type HistoryResponse = z.infer; + +export { + HistoryResponseSchema, + type HistoryItem, + type HistoryResponse, + }; + \ No newline at end of file