From 682078e7c8e7b49b5ef4863b3cf5aecdf3e79499 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Mon, 9 Mar 2026 18:31:55 +0900 Subject: [PATCH 01/33] feat(fe): create problem tabs --- .../problem/_components/ProblemTabs.tsx | 56 +++++++++++++++++++ .../(client)/(main)/problem/creating/page.tsx | 7 +++ .../app/(client)/(main)/problem/layout.tsx | 23 +++++--- .../(main)/problem/my-problems/page.tsx | 7 +++ 4 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx create mode 100644 apps/frontend/app/(client)/(main)/problem/creating/page.tsx create mode 100644 apps/frontend/app/(client)/(main)/problem/my-problems/page.tsx diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx new file mode 100644 index 0000000000..a91d73e249 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx @@ -0,0 +1,56 @@ +'use client' + +import { cn } from '@/libs/utils' +import type { Route } from 'next' +import Link from 'next/link' +import { usePathname } from 'next/navigation' + +export function ProblemTabs() { + const pathname = usePathname() + + const isCurrentTab = (tab: string) => { + if (tab === '') { + return pathname === `/problem` + } + return pathname.startsWith(`/problem/${tab}`) + } + + return ( +
+
+ + Published + + + Creating + + + My problems + +
+
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/creating/page.tsx b/apps/frontend/app/(client)/(main)/problem/creating/page.tsx new file mode 100644 index 0000000000..a2de584780 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/creating/page.tsx @@ -0,0 +1,7 @@ +export default function ProblemCreatingPage() { + return ( +
+ Problem creating page is under construction. +
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/layout.tsx b/apps/frontend/app/(client)/(main)/problem/layout.tsx index 37bad0ad43..7ff5faf539 100644 --- a/apps/frontend/app/(client)/(main)/problem/layout.tsx +++ b/apps/frontend/app/(client)/(main)/problem/layout.tsx @@ -1,15 +1,20 @@ -import { Cover } from '@/app/(client)/(main)/_components/Cover' +import { ProblemTabs } from './_components/ProblemTabs' export default function Layout({ children }: { children: React.ReactNode }) { return ( - <> - -
- {children} +
+
+
+
Problem
+
+ Train Hard, Solve Fast, Code Like a Pro! +
+
+
+ + {children} +
- +
) } diff --git a/apps/frontend/app/(client)/(main)/problem/my-problems/page.tsx b/apps/frontend/app/(client)/(main)/problem/my-problems/page.tsx new file mode 100644 index 0000000000..0dc18360fa --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/my-problems/page.tsx @@ -0,0 +1,7 @@ +export default function MyProblemsPage() { + return ( +
+ My problems page is under construction. +
+ ) +} From 24f8596fee9564a24eda3639e62a10fcf69cb8ac Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 13 Mar 2026 18:07:08 +0900 Subject: [PATCH 02/33] fix(fe): fix width --- apps/frontend/app/(client)/(main)/problem/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/app/(client)/(main)/problem/layout.tsx b/apps/frontend/app/(client)/(main)/problem/layout.tsx index 7ff5faf539..c7c5743547 100644 --- a/apps/frontend/app/(client)/(main)/problem/layout.tsx +++ b/apps/frontend/app/(client)/(main)/problem/layout.tsx @@ -3,7 +3,7 @@ import { ProblemTabs } from './_components/ProblemTabs' export default function Layout({ children }: { children: React.ReactNode }) { return (
-
+
Problem
From 63e1245682ed1d4ec821862a7fba074d574b8cd0 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 13 Mar 2026 23:35:51 +0900 Subject: [PATCH 03/33] feat(fe): remake tabs design --- .../problem/_components/ProblemTabs.tsx | 45 ++++++++++++++----- .../app/(client)/(main)/problem/layout.tsx | 18 ++++---- .../{my-problems => my-problem}/page.tsx | 0 3 files changed, 43 insertions(+), 20 deletions(-) rename apps/frontend/app/(client)/(main)/problem/{my-problems => my-problem}/page.tsx (100%) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx index a91d73e249..4aec95feaa 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx @@ -1,23 +1,45 @@ 'use client' -import { cn } from '@/libs/utils' -import type { Route } from 'next' -import Link from 'next/link' -import { usePathname } from 'next/navigation' +import { Tabs, TabsList, TabsTrigger } from '@/components/shadcn/tabs' +import { usePathname, useRouter } from 'next/navigation' export function ProblemTabs() { const pathname = usePathname() + const router = useRouter() - const isCurrentTab = (tab: string) => { - if (tab === '') { - return pathname === `/problem` - } - return pathname.startsWith(`/problem/${tab}`) + let currentTab: string + if (pathname.includes('creating')) { + currentTab = 'creating' + } else if (pathname.includes('my-problem')) { + currentTab = 'my-problem' + } else { + currentTab = 'published' } return ( -
-
+ + + router.push(`/problem`)}> + Published + + router.push(`/problem/creating`)} + > + Creating + + router.push(`/problem/my-problem`)} + > + My problem + + + + + /* +
+
+ */ ) } diff --git a/apps/frontend/app/(client)/(main)/problem/layout.tsx b/apps/frontend/app/(client)/(main)/problem/layout.tsx index c7c5743547..5b6c22af69 100644 --- a/apps/frontend/app/(client)/(main)/problem/layout.tsx +++ b/apps/frontend/app/(client)/(main)/problem/layout.tsx @@ -2,19 +2,19 @@ import { ProblemTabs } from './_components/ProblemTabs' export default function Layout({ children }: { children: React.ReactNode }) { return ( -
-
-
-
Problem
-
+
+
+
+
+ Problem +
+
Train Hard, Solve Fast, Code Like a Pro!
-
- - {children} -
+
+ {children}
) } diff --git a/apps/frontend/app/(client)/(main)/problem/my-problems/page.tsx b/apps/frontend/app/(client)/(main)/problem/my-problem/page.tsx similarity index 100% rename from apps/frontend/app/(client)/(main)/problem/my-problems/page.tsx rename to apps/frontend/app/(client)/(main)/problem/my-problem/page.tsx From 8c33722bb4ae5c76f044ea65b2720ddf518ee3c8 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 13 Mar 2026 23:36:54 +0900 Subject: [PATCH 04/33] fix(fe): remove unusables --- .../problem/_components/ProblemTabs.tsx | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx index 4aec95feaa..d24e20007a 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx @@ -36,44 +36,5 @@ export function ProblemTabs() { - - /* -
-
- - Published - - - Creating - - - My problems - -
-
- */ ) } From 688af8d1f79fb0e6e074e70e0f91ab138e3eef21 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Mon, 16 Mar 2026 15:48:41 +0900 Subject: [PATCH 05/33] fix(fe): change text color and leaf node --- apps/frontend/app/(client)/(main)/problem/layout.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/layout.tsx b/apps/frontend/app/(client)/(main)/problem/layout.tsx index 5b6c22af69..4e2bed0e96 100644 --- a/apps/frontend/app/(client)/(main)/problem/layout.tsx +++ b/apps/frontend/app/(client)/(main)/problem/layout.tsx @@ -5,12 +5,10 @@ export default function Layout({ children }: { children: React.ReactNode }) {
-
- Problem -
-
+

Problem

+

Train Hard, Solve Fast, Code Like a Pro! -

+

From 67877e11d42172d1fe3600ea74e319d3019eea1f Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Mon, 16 Mar 2026 16:57:41 +0900 Subject: [PATCH 06/33] fix(fe): change tab design --- .../(main)/problem/_components/ProblemTabs.tsx | 10 ++++++++-- apps/frontend/components/shadcn/tabs.tsx | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx index d24e20007a..b5ea879b1d 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx @@ -18,18 +18,24 @@ export function ProblemTabs() { return ( - - router.push(`/problem`)}> + + router.push(`/problem`)} + > Published router.push(`/problem/creating`)} > Creating router.push(`/problem/my-problem`)} > My problem diff --git a/apps/frontend/components/shadcn/tabs.tsx b/apps/frontend/components/shadcn/tabs.tsx index 0d747181fb..dbafdbb247 100644 --- a/apps/frontend/components/shadcn/tabs.tsx +++ b/apps/frontend/components/shadcn/tabs.tsx @@ -12,6 +12,8 @@ const tabsListVariants = cva('inline-flex', { variant: { default: 'h-[46px] gap-0 rounded-full border-1 border-color-line p-1', outline: 'gap-0 rounded-full border-2 border-color-line p-1', + problem: + 'items-center gap-0 rounded-full border border-color-line bg-white p-1', editor: 'gap-1 rounded bg-editor-background-1 py-1 px-1.5' // 수정: 넓은 간격, 둥근 모서리, 어두운 배경, 패딩 } }, @@ -30,6 +32,11 @@ const tabsTriggerVariants = cva( 'data-[state=active]:bg-primary data-[state=active]:text-white', 'data-[state=inactive]:bg-transparent data-[state=inactive]:text-color-neutral-60' ], + problem: [ + 'flex h-[48px] w-40 items-center justify-center rounded-full px-4 py-3 text-lg font-medium leading-6 whitespace-nowrap tracking-normal', + 'data-[state=active]:bg-primary data-[state=active]:text-white', + 'data-[state=inactive]:bg-transparent data-[state=inactive]:text-color-neutral-60' + ], outline: [ 'rounded-full uppercase text-sm tracking-wide', 'data-[state=active]:bg-primary data-[state=active]:text-white data-[state=active]:border-2 data-[state=active]:border-primary', From 1dfa205b7bf5dddeeffd7921df2b74327d662a4d Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Mon, 16 Mar 2026 22:23:25 +0900 Subject: [PATCH 07/33] fix(fe): capitalize problem letters --- apps/frontend/app/(client)/(main)/problem/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/app/(client)/(main)/problem/layout.tsx b/apps/frontend/app/(client)/(main)/problem/layout.tsx index 4e2bed0e96..ee98dea2c3 100644 --- a/apps/frontend/app/(client)/(main)/problem/layout.tsx +++ b/apps/frontend/app/(client)/(main)/problem/layout.tsx @@ -5,7 +5,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
-

Problem

+

PROBLEM

Train Hard, Solve Fast, Code Like a Pro!

From 8d5201d02dfba661a62bc72c58694afa5d0b74db Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Wed, 18 Mar 2026 17:03:54 +0900 Subject: [PATCH 08/33] fix(fe): change letters --- .../app/(client)/(main)/problem/_components/ProblemTabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx index b5ea879b1d..af9667aa73 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx @@ -38,7 +38,7 @@ export function ProblemTabs() { variant="problem" onClick={() => router.push(`/problem/my-problem`)} > - My problem + My Problem From 0c747b65127be685932358bd4d07b305b025ca7c Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Thu, 19 Mar 2026 16:53:07 +0900 Subject: [PATCH 09/33] fix(fe): fix page design --- .../problem/_components/ProblemInfiniteTable.tsx | 16 +++++++++------- .../app/(client)/(main)/problem/layout.tsx | 2 +- apps/frontend/components/shadcn/tabs.tsx | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx index 21afab3a60..ba75c4e2e7 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx @@ -19,14 +19,16 @@ export function ProblemInfiniteTable() { return ( <> -
-
-

All

-

{data.total}

+
+
+

Published Problems

+

{data.total}

+
+
+
-
-
+
-
+

PROBLEM

diff --git a/apps/frontend/components/shadcn/tabs.tsx b/apps/frontend/components/shadcn/tabs.tsx index dbafdbb247..54edcaf0c6 100644 --- a/apps/frontend/components/shadcn/tabs.tsx +++ b/apps/frontend/components/shadcn/tabs.tsx @@ -33,9 +33,9 @@ const tabsTriggerVariants = cva( 'data-[state=inactive]:bg-transparent data-[state=inactive]:text-color-neutral-60' ], problem: [ - 'flex h-[48px] w-40 items-center justify-center rounded-full px-4 py-3 text-lg font-medium leading-6 whitespace-nowrap tracking-normal', + 'flex h-[49px] w-40 items-center justify-center rounded-full px-4 py-3 text-sub2_m_18 whitespace-nowrap', 'data-[state=active]:bg-primary data-[state=active]:text-white', - 'data-[state=inactive]:bg-transparent data-[state=inactive]:text-color-neutral-60' + 'data-[state=inactive]:bg-Common-100 data-[state=inactive]:text-zinc-500' ], outline: [ 'rounded-full uppercase text-sm tracking-wide', From 3f6fe765144d4beb5ffa531c0aa61edc5efe899f Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Thu, 26 Mar 2026 12:43:13 +0900 Subject: [PATCH 10/33] fix(fe): change table design --- .../(client)/(main)/_components/DataTable.tsx | 41 ++++- .../(main)/problem/_components/Columns.tsx | 15 +- .../_components/ProblemInfiniteTable.tsx | 71 --------- .../problem/_components/ProblemTable.tsx | 147 ++++++++++++++++++ .../app/(client)/(main)/problem/page.tsx | 9 +- .../app/(client)/_libs/queries/problem.ts | 7 + apps/frontend/components/PaginatorV2.tsx | 42 ++++- 7 files changed, 239 insertions(+), 93 deletions(-) delete mode 100644 apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx create mode 100644 apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx diff --git a/apps/frontend/app/(client)/(main)/_components/DataTable.tsx b/apps/frontend/app/(client)/(main)/_components/DataTable.tsx index 4df3766ff4..98ddc36079 100644 --- a/apps/frontend/app/(client)/(main)/_components/DataTable.tsx +++ b/apps/frontend/app/(client)/(main)/_components/DataTable.tsx @@ -29,9 +29,16 @@ interface DataTableProps { headerStyle: { [key: string]: string } + tableClassName?: string + headerClassName?: string + bodyClassName?: string + headerRowClassName?: string + headerCellClassName?: string + cellClassName?: string tableRowStyle?: string linked?: boolean emptyMessage?: string + emptyCellClassName?: string pathSegment?: string | null } @@ -76,9 +83,16 @@ export function DataTable({ columns, data, headerStyle, + tableClassName, + headerClassName, + bodyClassName, + headerRowClassName, + headerCellClassName, + cellClassName, tableRowStyle, linked = false, emptyMessage = 'No results.', + emptyCellClassName, pathSegment }: DataTableProps) { const table = useReactTable({ @@ -90,16 +104,25 @@ export function DataTable({ const router = useRouter() return ( - - +
+ {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( @@ -115,7 +138,7 @@ export function DataTable({ ))} - + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => { const href = pathSegment @@ -137,7 +160,10 @@ export function DataTable({ } > {row.getVisibleCells().map((cell) => ( - + {linked ? ( ({ }) ) : ( - + {emptyMessage} diff --git a/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx b/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx index 0a35e87ce6..215d6aed21 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx @@ -7,7 +7,7 @@ import { SortButton } from './SortButton' export const columns: ColumnDef[] = [ { - header: 'Title', + header: 'Question', accessorKey: 'title', cell: ({ row }) => { return ( @@ -32,6 +32,17 @@ export const columns: ColumnDef[] = [ { header: () => Success Rate, accessorKey: 'acceptedRate', - cell: ({ row }) => `${(row.original.acceptedRate * 100).toFixed(2)}%` + cell: ({ row }) => { + const acceptedRate = row.original.acceptedRate * 100 + let textStyle = 'text-[#1F1F1F]' + + if (acceptedRate === 100) { + textStyle = 'text-primary' + } else if (acceptedRate === 0) { + textStyle = 'text-color-neutral-50' + } + + return {`${acceptedRate.toFixed(2)}%`} + } } ] diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx deleted file mode 100644 index ba75c4e2e7..0000000000 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemInfiniteTable.tsx +++ /dev/null @@ -1,71 +0,0 @@ -'use client' - -import { DataTable } from '@/app/(client)/(main)/_components/DataTable' -import { problemQueries } from '@/app/(client)/_libs/queries/problem' -import { IntersectionArea } from '@/components/IntersectionArea' -import { Skeleton } from '@/components/shadcn/skeleton' -import { useSuspenseInfiniteQuery } from '@tanstack/react-query' -import { useSearchParams } from 'next/navigation' -import { SearchBar } from '../../_components/SearchBar' -import { columns } from './Columns' - -export function ProblemInfiniteTable() { - const searchParams = useSearchParams() - const search = searchParams.get('search') ?? '' - const order = searchParams.get('order') ?? 'id-asc' - - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = - useSuspenseInfiniteQuery(problemQueries.infiniteList({ search, order })) - - return ( - <> -
-
-

Published Problems

-

{data.total}

-
-
- -
-
-
- - - {isFetchingNextPage && - [...Array(5)].map((_, i) => ( - - ))} - -
- - ) -} - -export function ProblemInfiniteTableFallback() { - return ( - <> -
- - - -
- {[...Array(5)].map((_, i) => ( - - ))} - - ) -} diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx new file mode 100644 index 0000000000..74728ff2d4 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx @@ -0,0 +1,147 @@ +'use client' + +import { DataTable } from '@/app/(client)/(main)/_components/DataTable' +import { problemQueries } from '@/app/(client)/_libs/queries/problem' +import { + PageNavigation, + Paginator, + SlotNavigation +} from '@/components/PaginatorV2' +import { Skeleton } from '@/components/shadcn/skeleton' +import { getTakeQueryParam, usePagination } from '@/libs/hooks/usePaginationV2' +import { useSuspenseQuery } from '@tanstack/react-query' +import { useSearchParams } from 'next/navigation' +import { useState } from 'react' +import { SearchBar } from '../../_components/SearchBar' +import { columns } from './Columns' + +const ITEMS_PER_PAGE = 10 +const PAGES_PER_SLOT = 10 + +export function ProblemTable() { + const searchParams = useSearchParams() + const search = searchParams.get('search') ?? '' + const order = searchParams.get('order') ?? 'id-asc' + + return ( + + ) +} + +function ProblemPaginatedTable({ + search, + order +}: { + search: string + order: string +}) { + const [queryParams, updateQueryParams] = useState({ + take: getTakeQueryParam({ + itemsPerPage: ITEMS_PER_PAGE, + pagesPerSlot: PAGES_PER_SLOT + }) + }) + + const { data } = useSuspenseQuery( + problemQueries.list({ + ...queryParams, + search, + order + }) + ) + + const { + paginatedItems, + currentPage, + firstPage, + lastPage, + gotoPage, + gotoSlot, + prevDisabled, + nextDisabled + } = usePagination({ + data: data.data, + totalCount: data.total, + itemsPerPage: ITEMS_PER_PAGE, + pagesPerSlot: PAGES_PER_SLOT, + updateQueryParams + }) + + return ( +
+
+
+

Published Problems

+

{data.total}

+
+
+ +
+
+
+ +
+ + + + + +
+ ) +} + +export function ProblemTableFallback() { + return ( + <> +
+ + + +
+ {[...Array(5)].map((_, i) => ( + + ))} + + ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/page.tsx b/apps/frontend/app/(client)/(main)/problem/page.tsx index 6ff59d2ddd..458632c30e 100644 --- a/apps/frontend/app/(client)/(main)/problem/page.tsx +++ b/apps/frontend/app/(client)/(main)/problem/page.tsx @@ -1,16 +1,13 @@ import { FetchErrorFallback } from '@/components/FetchErrorFallback' import { TanstackQueryErrorBoundary } from '@/components/TanstackQueryErrorBoundary' import { Suspense } from 'react' -import { - ProblemInfiniteTable, - ProblemInfiniteTableFallback -} from './_components/ProblemInfiniteTable' +import { ProblemTable, ProblemTableFallback } from './_components/ProblemTable' export default function ProblemListPage() { return ( - }> - + }> + ) diff --git a/apps/frontend/app/(client)/_libs/queries/problem.ts b/apps/frontend/app/(client)/_libs/queries/problem.ts index 59692336ea..31056c3d1c 100644 --- a/apps/frontend/app/(client)/_libs/queries/problem.ts +++ b/apps/frontend/app/(client)/_libs/queries/problem.ts @@ -1,8 +1,15 @@ +import { queryOptions } from '@tanstack/react-query' import { getProblemList, type GetProblemListRequest } from '../apis/problem' import { getInfiniteQueryOptions } from './infiniteQuery' export const problemQueries = { all: () => ['problem'] as const, + lists: () => [...problemQueries.all(), 'list'] as const, + list: (params: GetProblemListRequest) => + queryOptions({ + queryKey: [...problemQueries.lists(), params] as const, + queryFn: () => getProblemList(params) + }), infiniteList: (params: GetProblemListRequest) => getInfiniteQueryOptions({ queryKey: [...problemQueries.all(), 'list', params] as const, diff --git a/apps/frontend/components/PaginatorV2.tsx b/apps/frontend/components/PaginatorV2.tsx index 7c49404fb6..1a395057dd 100644 --- a/apps/frontend/components/PaginatorV2.tsx +++ b/apps/frontend/components/PaginatorV2.tsx @@ -5,17 +5,25 @@ import { PaginationNext, PaginationPrevious } from '@/components/shadcn/pagination' -import { getPageArray } from '@/libs/utils' +import { cn, getPageArray } from '@/libs/utils' import type { ReactNode } from 'react' interface PaginatorProps { children: ReactNode + className?: string + contentClassName?: string } -export function Paginator({ children }: PaginatorProps) { +export function Paginator({ + children, + className, + contentClassName +}: PaginatorProps) { return ( - - + + {children} @@ -27,12 +35,16 @@ interface SlotNavigationProps { direction: Direction disabled: boolean gotoSlot: (dir: Direction) => void + className?: string + spacerClassName?: string } export function SlotNavigation({ direction, gotoSlot, - disabled + disabled, + className, + spacerClassName }: SlotNavigationProps) { return direction === 'prev' ? ( <> @@ -42,18 +54,20 @@ export function SlotNavigation({ }} isActive={!disabled} disabled={disabled} + className={className} /> -
+
) : ( <> -
+
{ gotoSlot('next') }} isActive={!disabled} disabled={disabled} + className={className} /> ) @@ -64,13 +78,19 @@ interface PageNavigationProps { lastPage: number currentPage: number gotoPage: (page: number) => void + buttonClassName?: string + activeButtonClassName?: string + inactiveButtonClassName?: string } export function PageNavigation({ firstPage, lastPage, currentPage, - gotoPage + gotoPage, + buttonClassName, + activeButtonClassName, + inactiveButtonClassName }: PageNavigationProps) { return ( <> @@ -78,6 +98,12 @@ export function PageNavigation({ { gotoPage(item) }} From 67f1827ae372969ebdfed242dbc028d24b3a9dd4 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Thu, 26 Mar 2026 15:31:10 +0900 Subject: [PATCH 11/33] fix(fe): korean patch --- .../(client)/(main)/problem/_components/Columns.tsx | 12 ++++++------ .../(main)/problem/_components/ProblemTable.tsx | 6 +++--- .../(main)/problem/_components/ProblemTabs.tsx | 6 +++--- apps/frontend/app/(client)/(main)/problem/layout.tsx | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx b/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx index 215d6aed21..966fdf86c3 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx @@ -7,16 +7,16 @@ import { SortButton } from './SortButton' export const columns: ColumnDef[] = [ { - header: 'Question', + header: '질문', accessorKey: 'title', cell: ({ row }) => { return ( -

{`${row.original.id}. ${row.original.title}`}

+

{`${row.original.id}. ${row.original.title}`}

) } }, { - header: () => Level, + header: () => 난이도, accessorKey: 'difficulty', cell: ({ row }) => ( @@ -25,12 +25,12 @@ export const columns: ColumnDef[] = [ ) }, { - header: () => Submission, + header: () => 제출, accessorKey: 'submissionCount', cell: ({ row }) => row.original.submissionCount }, { - header: () => Success Rate, + header: () => 성공 비율, accessorKey: 'acceptedRate', cell: ({ row }) => { const acceptedRate = row.original.acceptedRate * 100 @@ -39,7 +39,7 @@ export const columns: ColumnDef[] = [ if (acceptedRate === 100) { textStyle = 'text-primary' } else if (acceptedRate === 0) { - textStyle = 'text-color-neutral-50' + textStyle = 'text-color-neutral-80' } return {`${acceptedRate.toFixed(2)}%`} diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx index 74728ff2d4..bc6cd80c6d 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx @@ -75,7 +75,7 @@ function ProblemPaginatedTable({
-

Published Problems

+

등록된 문제

{data.total}

@@ -87,10 +87,10 @@ function ProblemPaginatedTable({ data={paginatedItems} columns={columns} tableClassName="border-line border-separate border-spacing-0" - headerClassName="border-line relative after:pointer-events-none after:absolute after:bottom-0 after:left-0 after:h-4 after:w-full after:translate-y-full after:bg-[linear-gradient(180deg,rgba(53,78,116,0.08)_0%,rgba(53,78,116,0)_100%)]" + headerClassName="border-line overflow-hidden rounded-[1000px] border-b" bodyClassName="[&_tr:last-child]:border-b [&_tr:last-child]:border-line" headerRowClassName="h-12" - headerCellClassName="border-line h-12 bg-white px-5 py-2 text-body1_m_16 text-color-cool-neutral-30 first:rounded-tl-[20px] last:rounded-tr-[20px]" + headerCellClassName="border-line h-12 bg-white px-5 py-2 text-body1_m_16 text-color-cool-neutral-30 first:rounded-l-[1000px] last:rounded-r-[1000px]" cellClassName="border-line h-16 px-5 py-0 align-middle" tableRowStyle="border-line h-16 border-b hover:bg-transparent" emptyCellClassName="border-line h-16 align-middle" diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx index af9667aa73..e3484f877c 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTabs.tsx @@ -24,21 +24,21 @@ export function ProblemTabs() { variant="problem" onClick={() => router.push(`/problem`)} > - Published + 등록된 문제 router.push(`/problem/creating`)} > - Creating + 제작 중인 문제 router.push(`/problem/my-problem`)} > - My Problem + 내가 만든 문제 diff --git a/apps/frontend/app/(client)/(main)/problem/layout.tsx b/apps/frontend/app/(client)/(main)/problem/layout.tsx index 9c1566c6d0..ff47b34a11 100644 --- a/apps/frontend/app/(client)/(main)/problem/layout.tsx +++ b/apps/frontend/app/(client)/(main)/problem/layout.tsx @@ -7,7 +7,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {

PROBLEM

- Train Hard, Solve Fast, Code Like a Pro! + 열심히 훈련하고, 빠르게 해결하며, 프로답게 코드를 작성하세요!

From 1619581e2a36c0f0f0b43983896fe94c0d408737 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 27 Mar 2026 17:21:31 +0900 Subject: [PATCH 12/33] fix(fe): change table design --- .../(main)/problem/_components/Columns.tsx | 28 ++- .../problem/_components/ProblemDataTable.tsx | 170 ++++++++++++++++++ .../problem/_components/ProblemTable.tsx | 58 +++--- 3 files changed, 209 insertions(+), 47 deletions(-) create mode 100644 apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx diff --git a/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx b/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx index 966fdf86c3..f78d08e00b 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/Columns.tsx @@ -1,9 +1,9 @@ 'use client' import { Badge } from '@/components/shadcn/badge' +import { cn } from '@/libs/utils' import type { Problem } from '@/types/type' import type { ColumnDef } from '@tanstack/react-table' -import { SortButton } from './SortButton' export const columns: ColumnDef[] = [ { @@ -16,7 +16,7 @@ export const columns: ColumnDef[] = [ } }, { - header: () => 난이도, + header: '난이도', accessorKey: 'difficulty', cell: ({ row }) => ( @@ -25,24 +25,34 @@ export const columns: ColumnDef[] = [ ) }, { - header: () => 제출, + header: '제출', accessorKey: 'submissionCount', - cell: ({ row }) => row.original.submissionCount + cell: ({ row }) => { + return ( + + {row.original.submissionCount} + + ) + } }, { - header: () => 성공 비율, + header: '성공 비율', accessorKey: 'acceptedRate', cell: ({ row }) => { const acceptedRate = row.original.acceptedRate * 100 - let textStyle = 'text-[#1F1F1F]' + let textColor = 'text-color-cool-neutral-30' if (acceptedRate === 100) { - textStyle = 'text-primary' + textColor = 'text-primary' } else if (acceptedRate === 0) { - textStyle = 'text-color-neutral-80' + textColor = 'text-color-neutral-80' } - return {`${acceptedRate.toFixed(2)}%`} + return ( + {`${acceptedRate.toFixed(2)}%`} + ) } } ] diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx new file mode 100644 index 0000000000..4dc73e7c13 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -0,0 +1,170 @@ +'use client' + +import { SearchBar } from '@/app/(client)/(main)/_components/SearchBar' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/shadcn/table' +import { cn } from '@/libs/utils' +import type { ColumnDef } from '@tanstack/react-table' +import { + flexRender, + getCoreRowModel, + useReactTable +} from '@tanstack/react-table' +import type { Route } from 'next' +import Link from 'next/link' +import { usePathname, useRouter } from 'next/navigation' + +interface Item { + id: number +} + +interface ProblemDataTableProps { + columns: ColumnDef[] + data: TData[] + total: number + itemsPerPage: number + currentPage: number + headerStyle: { + [key: string]: string + } + search: string + linked?: boolean + emptyMessage?: string +} + +export function ProblemDataTable({ + columns, + data, + total, + itemsPerPage, + currentPage, + headerStyle, + search, + linked = false, + emptyMessage = 'No results.' +}: ProblemDataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel() + }) + const router = useRouter() + const currentPath = usePathname() + + const startIndex = (currentPage - 1) * itemsPerPage + const paginatedItems = table + .getRowModel() + .rows.slice(startIndex, startIndex + itemsPerPage) + + return ( +
+
+
+

등록된 문제

+

{total}

+
+
+ +
+
+
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {paginatedItems.length ? ( + paginatedItems.map((row) => { + const href = + `${currentPath}/${row.original.id}${search ? `?search=${search}` : ''}` as Route + + const handleClick = linked + ? () => { + router.push(href) + } + : (e: React.MouseEvent) => { + e.currentTarget.classList.toggle('expanded') + } + + return ( + + {row.getVisibleCells().map((cell) => ( + + {linked ? ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ) : ( + flexRender( + cell.column.columnDef.cell, + cell.getContext() + ) + )} + + ))} + + ) + }) + ) : ( + + + {emptyMessage} + + + )} + +
+

+
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx index bc6cd80c6d..e7b194ab28 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx @@ -1,6 +1,5 @@ 'use client' -import { DataTable } from '@/app/(client)/(main)/_components/DataTable' import { problemQueries } from '@/app/(client)/_libs/queries/problem' import { PageNavigation, @@ -12,8 +11,8 @@ import { getTakeQueryParam, usePagination } from '@/libs/hooks/usePaginationV2' import { useSuspenseQuery } from '@tanstack/react-query' import { useSearchParams } from 'next/navigation' import { useState } from 'react' -import { SearchBar } from '../../_components/SearchBar' import { columns } from './Columns' +import { ProblemDataTable } from './ProblemDataTable' const ITEMS_PER_PAGE = 10 const PAGES_PER_SLOT = 10 @@ -55,7 +54,6 @@ function ProblemPaginatedTable({ ) const { - paginatedItems, currentPage, firstPage, lastPage, @@ -73,38 +71,22 @@ function ProblemPaginatedTable({ return (
-
-
-

등록된 문제

-

{data.total}

-
-
- -
-
-
- -
- + + Date: Fri, 27 Mar 2026 17:21:53 +0900 Subject: [PATCH 13/33] fix(fe): change table design --- .../(client)/(main)/_components/DataTable.tsx | 43 +++------------ .../problem/_components/ProblemDataTable.tsx | 53 +++++++++++-------- .../problem/_components/ProblemTable.tsx | 8 +-- 3 files changed, 41 insertions(+), 63 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/_components/DataTable.tsx b/apps/frontend/app/(client)/(main)/_components/DataTable.tsx index 98ddc36079..cfec49c6ee 100644 --- a/apps/frontend/app/(client)/(main)/_components/DataTable.tsx +++ b/apps/frontend/app/(client)/(main)/_components/DataTable.tsx @@ -29,16 +29,9 @@ interface DataTableProps { headerStyle: { [key: string]: string } - tableClassName?: string - headerClassName?: string - bodyClassName?: string - headerRowClassName?: string - headerCellClassName?: string - cellClassName?: string tableRowStyle?: string linked?: boolean emptyMessage?: string - emptyCellClassName?: string pathSegment?: string | null } @@ -83,16 +76,9 @@ export function DataTable({ columns, data, headerStyle, - tableClassName, - headerClassName, - bodyClassName, - headerRowClassName, - headerCellClassName, - cellClassName, tableRowStyle, linked = false, emptyMessage = 'No results.', - emptyCellClassName, pathSegment }: DataTableProps) { const table = useReactTable({ @@ -104,25 +90,16 @@ export function DataTable({ const router = useRouter() return ( - - +
+ {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( @@ -138,7 +115,7 @@ export function DataTable({ ))} - + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => { const href = pathSegment @@ -160,10 +137,7 @@ export function DataTable({ } > {row.getVisibleCells().map((cell) => ( - + {linked ? ( ({ }) ) : ( - + {emptyMessage} diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index 4dc73e7c13..309303b4f9 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -73,34 +73,41 @@ export function ProblemDataTable({ -
-
- +
+
+ {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} + {headerGroup.headers.map((header, index) => { + const isFirst = index === 0 + const isLast = index === headerGroup.headers.length - 1 + + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} ))} - + {paginatedItems.length ? ( paginatedItems.map((row) => { const href = @@ -118,13 +125,13 @@ export function ProblemDataTable({ {row.getVisibleCells().map((cell) => ( {linked ? ( From 1aea4148d28255c203659431f421422b42364da7 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 27 Mar 2026 17:41:00 +0900 Subject: [PATCH 14/33] fix(fe): change table design --- .../problem/_components/ProblemDataTable.tsx | 24 +++++++------------ .../problem/_components/ProblemTable.tsx | 12 ++++++---- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index 309303b4f9..9bf57f11d9 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -74,24 +74,16 @@ export function ProblemDataTable({
-
- +
+ {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header, index) => { - const isFirst = index === 0 - const isLast = index === headerGroup.headers.length - 1 - + + {headerGroup.headers.map((header) => { return ( @@ -107,7 +99,7 @@ export function ProblemDataTable({ ))} - + {paginatedItems.length ? ( paginatedItems.map((row) => { const href = @@ -125,13 +117,13 @@ export function ProblemDataTable({ {row.getVisibleCells().map((cell) => ( {linked ? ( From e019871f5fbeb25e0a8b907441a83876315898bc Mon Sep 17 00:00:00 2001 From: woo943 Date: Fri, 27 Mar 2026 20:12:00 +0900 Subject: [PATCH 15/33] Update apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../(client)/(main)/problem/_components/ProblemDataTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index 9bf57f11d9..7b8613357a 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -109,7 +109,7 @@ export function ProblemDataTable({ ? () => { router.push(href) } - : (e: React.MouseEvent) => { + : (e: React.MouseEvent) => { e.currentTarget.classList.toggle('expanded') } From ec5dd0fbac15742ebcbd6b0f1e8e261e560132b3 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Tue, 31 Mar 2026 00:25:36 +0900 Subject: [PATCH 16/33] feat(fe): impl basic page design --- .../(main)/problem/_components/MyProblem.tsx | 118 +++++++++++++++ .../_components/MyProblemDataTable.tsx | 141 ++++++++++++++++++ .../(main)/problem/my-problem/page.tsx | 13 +- 3 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx create mode 100644 apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx new file mode 100644 index 0000000000..dbdb5b7209 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx @@ -0,0 +1,118 @@ +'use client' + +import { Skeleton } from '@/components/shadcn/skeleton' +import type { Problem } from '@/types/type' +import { useSearchParams } from 'next/navigation' +import { useCallback, useEffect, useState } from 'react' +import { MyProblemDataTable } from './MyProblemDataTable' + +const ITEMS_PER_PAGE = 10 + +export interface MyProblemCardItem extends Problem { + state: 'ONGOING' | 'DRAFT' | 'REVIEW' + thumbnailSrc: string + timeLimit: number + memoryLimit: number + updatedAt: string +} + +const MOCK_MY_PROBLEMS: MyProblemCardItem[] = Array.from( + { length: 48 }, + (_, index) => { + const id = 3000 + index + 1 + const difficulty = `Level${(index % 5) + 1}` as Problem['difficulty'] + const submissionCount = 24 + index * 7 + const acceptedRate = ((index % 9) + 2) / 12 + const states: MyProblemCardItem['state'][] = ['ONGOING', 'DRAFT', 'REVIEW'] + + return { + id, + title: `글자 수 상관 없음. 단, 한 줄로만 노출되는 Mock My Problem ${index + 1}`, + difficulty, + submissionCount, + acceptedRate, + tags: [], + languages: ['C', 'Cpp', 'Java', 'Python3'], + hasPassed: null, + state: states[index % states.length], + thumbnailSrc: '/banners/problemInnerBanner.png', + timeLimit: 1000 + (index % 4) * 500, + memoryLimit: 128 + (index % 3) * 64, + updatedAt: `2024-01-${String((index % 24) + 1).padStart(2, '0')} 19:00` + } + } +) + +export function MyProblem() { + const searchParams = useSearchParams() + const search = searchParams.get('search') ?? '' + const normalizedSearch = search.trim().toLowerCase() + + const filteredProblems = MOCK_MY_PROBLEMS.filter((problem) => { + if (!normalizedSearch) { + return true + } + + return ( + problem.title.toLowerCase().includes(normalizedSearch) || + String(problem.id).includes(normalizedSearch) + ) + }) + + const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE) + + useEffect(() => { + setVisibleCount(ITEMS_PER_PAGE) + }, [normalizedSearch]) + + const visibleProblems = filteredProblems.slice(0, visibleCount) + const hasMore = visibleProblems.length < filteredProblems.length + + const loadMore = useCallback(() => { + setVisibleCount((prev) => + Math.min(prev + ITEMS_PER_PAGE, filteredProblems.length) + ) + }, [filteredProblems.length]) + + return ( + + ) +} + +export function MyProblemFallback() { + return ( +
+
+ +
+ + + +
+
+
+ {[...Array(8)].map((_, i) => ( +
+ +
+ + + + +
+
+ ))} +
+
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx new file mode 100644 index 0000000000..b9a1d74e26 --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -0,0 +1,141 @@ +'use client' + +import { SearchBar } from '@/app/(client)/(main)/_components/SearchBar' +import { Button } from '@/components/shadcn/button' +import { Clock3, Filter, Microchip, Plus } from 'lucide-react' +import type { Route } from 'next' +import Image from 'next/image' +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import type { ReactNode } from 'react' +import { useEffect, useRef } from 'react' +import type { MyProblemCardItem } from './MyProblem' + +interface MyProblemDataTableProps { + data: MyProblemCardItem[] + total: number + search: string + hasMore: boolean + onLoadMore: () => void + emptyMessage?: string +} + +export function MyProblemDataTable({ + data, + total, + search, + hasMore, + onLoadMore, + emptyMessage = 'No results.' +}: MyProblemDataTableProps) { + const currentPath = usePathname() + const loadMoreRef = useRef(null) + + useEffect(() => { + const node = loadMoreRef.current + + if (!node || !hasMore) { + return + } + + const observer = new IntersectionObserver( + (entries) => { + if (entries[0]?.isIntersecting) { + onLoadMore() + } + }, + { rootMargin: '160px 0px' } + ) + + observer.observe(node) + + return () => { + observer.disconnect() + } + }, [hasMore, onLoadMore, data.length]) + + return ( +
+
+
+
+

내가 만든 문제

+

{total}

+
+
+
+ + + +
+
+ {data.length ? ( +
+ {data.map((problem) => { + const href = + `${currentPath}/${problem.id}${search ? `?search=${search}` : ''}` as Route + + return ( + +
+ +
+
+
+ + {problem.state} + + + {problem.difficulty} + +
+
+

+ {problem.title} +

+
+
+ + + {problem.timeLimit}ms + + + + {problem.memoryLimit}MB + +
+

+ Last Modified: {problem.updatedAt} +

+
+ + ) + })} +
+ ) : ( +
+ {emptyMessage} +
+ )} +
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/my-problem/page.tsx b/apps/frontend/app/(client)/(main)/problem/my-problem/page.tsx index 0dc18360fa..681908aa60 100644 --- a/apps/frontend/app/(client)/(main)/problem/my-problem/page.tsx +++ b/apps/frontend/app/(client)/(main)/problem/my-problem/page.tsx @@ -1,7 +1,14 @@ +import { FetchErrorFallback } from '@/components/FetchErrorFallback' +import { TanstackQueryErrorBoundary } from '@/components/TanstackQueryErrorBoundary' +import { Suspense } from 'react' +import { MyProblem, MyProblemFallback } from '../_components/MyProblem' + export default function MyProblemsPage() { return ( -
- My problems page is under construction. -
+ + }> + + + ) } From 36021e81567dbe1597700f1b6efdaa71e50b8d01 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Tue, 31 Mar 2026 01:13:48 +0900 Subject: [PATCH 17/33] feat(fe): change card design --- .../(main)/problem/_components/MyProblem.tsx | 1 - .../_components/MyProblemDataTable.tsx | 75 ++++++++----------- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx index dbdb5b7209..b020df1cdf 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx @@ -77,7 +77,6 @@ export function MyProblem() { return ( void @@ -22,7 +19,6 @@ interface MyProblemDataTableProps { export function MyProblemDataTable({ data, - total, search, hasMore, onLoadMore, @@ -56,12 +52,9 @@ export function MyProblemDataTable({ return (
-
-
-
-

내가 만든 문제

-

{total}

-
+
+
+

내가 만든 문제

- +
{data.length ? ( -
+
{data.map((problem) => { const href = `${currentPath}/${problem.id}${search ? `?search=${search}` : ''}` as Route @@ -89,43 +82,41 @@ export function MyProblemDataTable({ -
- -
-
-
- +
+
+ {problem.state} - - {problem.difficulty} -
-
-

- {problem.title} -

+

+ {problem.title} +

+
+
+
+
+ + + {problem.timeLimit}ms + +
+
+ + + {problem.memoryLimit}MB + +
-
- - - {problem.timeLimit}ms +
+ + Last Modified: - - - {problem.memoryLimit}MB + + {problem.updatedAt}
-

- Last Modified: {problem.updatedAt} -

) From ce0835bf1eb6e74783d95525bf2eb33f51e81b31 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Tue, 31 Mar 2026 01:59:21 +0900 Subject: [PATCH 18/33] feat(fe): add icons --- .codex | 0 .../_components/MyProblemDataTable.tsx | 37 +++++++++++++++---- apps/frontend/public/icons/clock_gray.svg | 3 ++ apps/frontend/public/icons/memory.svg | 3 ++ .../public/icons/plus-circle-blue.svg | 3 ++ 5 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 .codex create mode 100644 apps/frontend/public/icons/clock_gray.svg create mode 100644 apps/frontend/public/icons/memory.svg create mode 100644 apps/frontend/public/icons/plus-circle-blue.svg diff --git a/.codex b/.codex new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx index c41fc2350f..2602df7856 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -2,11 +2,15 @@ import { SearchBar } from '@/app/(client)/(main)/_components/SearchBar' import { Button } from '@/components/shadcn/button' -import { Clock3, Filter, Microchip, Plus } from 'lucide-react' +import ClockGray from '@/public/icons/clock_gray.svg' +import Memory from '@/public/icons/memory.svg' +import PlusCircle from '@/public/icons/plus-circle-blue.svg' import type { Route } from 'next' +import Image from 'next/image' import Link from 'next/link' import { usePathname } from 'next/navigation' import { useEffect, useRef } from 'react' +import { IoFilter } from 'react-icons/io5' import type { MyProblemCardItem } from './MyProblem' interface MyProblemDataTableProps { @@ -59,15 +63,27 @@ export function MyProblemDataTable({
-
@@ -97,13 +113,18 @@ export function MyProblemDataTable({
- + {problem.timeLimit}ms
- + {problem.memoryLimit}MB diff --git a/apps/frontend/public/icons/clock_gray.svg b/apps/frontend/public/icons/clock_gray.svg new file mode 100644 index 0000000000..40a9163768 --- /dev/null +++ b/apps/frontend/public/icons/clock_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/frontend/public/icons/memory.svg b/apps/frontend/public/icons/memory.svg new file mode 100644 index 0000000000..207053af24 --- /dev/null +++ b/apps/frontend/public/icons/memory.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/frontend/public/icons/plus-circle-blue.svg b/apps/frontend/public/icons/plus-circle-blue.svg new file mode 100644 index 0000000000..5f3899e391 --- /dev/null +++ b/apps/frontend/public/icons/plus-circle-blue.svg @@ -0,0 +1,3 @@ + + + From 8cc971cc7e129ca14f05d456010035249608754e Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Tue, 31 Mar 2026 02:01:41 +0900 Subject: [PATCH 19/33] fix(fe): common change --- .codex | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .codex diff --git a/.codex b/.codex deleted file mode 100644 index e69de29bb2..0000000000 From 7702ee75afbb74770240added834754775f0d60d Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Tue, 31 Mar 2026 02:08:03 +0900 Subject: [PATCH 20/33] feat(fe): infinite scroll --- .../(main)/problem/_components/MyProblem.tsx | 22 +------------- .../_components/MyProblemDataTable.tsx | 29 ------------------- 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx index b020df1cdf..448d77f749 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx @@ -3,11 +3,8 @@ import { Skeleton } from '@/components/shadcn/skeleton' import type { Problem } from '@/types/type' import { useSearchParams } from 'next/navigation' -import { useCallback, useEffect, useState } from 'react' import { MyProblemDataTable } from './MyProblemDataTable' -const ITEMS_PER_PAGE = 10 - export interface MyProblemCardItem extends Problem { state: 'ONGOING' | 'DRAFT' | 'REVIEW' thumbnailSrc: string @@ -59,27 +56,10 @@ export function MyProblem() { ) }) - const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE) - - useEffect(() => { - setVisibleCount(ITEMS_PER_PAGE) - }, [normalizedSearch]) - - const visibleProblems = filteredProblems.slice(0, visibleCount) - const hasMore = visibleProblems.length < filteredProblems.length - - const loadMore = useCallback(() => { - setVisibleCount((prev) => - Math.min(prev + ITEMS_PER_PAGE, filteredProblems.length) - ) - }, [filteredProblems.length]) - return ( ) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx index 2602df7856..ceb70db3c4 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -9,50 +9,21 @@ import type { Route } from 'next' import Image from 'next/image' import Link from 'next/link' import { usePathname } from 'next/navigation' -import { useEffect, useRef } from 'react' import { IoFilter } from 'react-icons/io5' import type { MyProblemCardItem } from './MyProblem' interface MyProblemDataTableProps { data: MyProblemCardItem[] search: string - hasMore: boolean - onLoadMore: () => void emptyMessage?: string } export function MyProblemDataTable({ data, search, - hasMore, - onLoadMore, emptyMessage = 'No results.' }: MyProblemDataTableProps) { const currentPath = usePathname() - const loadMoreRef = useRef(null) - - useEffect(() => { - const node = loadMoreRef.current - - if (!node || !hasMore) { - return - } - - const observer = new IntersectionObserver( - (entries) => { - if (entries[0]?.isIntersecting) { - onLoadMore() - } - }, - { rootMargin: '160px 0px' } - ) - - observer.observe(node) - - return () => { - observer.disconnect() - } - }, [hasMore, onLoadMore, data.length]) return (
From 80c167590d1e303710b110ef1baf401361229457 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Wed, 1 Apr 2026 21:33:23 +0900 Subject: [PATCH 21/33] feat(fe): impl empty page --- .codex | 0 .../(main)/problem/_components/MyProblem.tsx | 18 ++++----- .../_components/MyProblemDataTable.tsx | 39 ++++++++++++------- .../(main)/problem/my-problem/empty/page.tsx | 14 +++++++ 4 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 .codex create mode 100644 apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx diff --git a/.codex b/.codex new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx index 448d77f749..c04fe7ecb9 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx @@ -7,12 +7,15 @@ import { MyProblemDataTable } from './MyProblemDataTable' export interface MyProblemCardItem extends Problem { state: 'ONGOING' | 'DRAFT' | 'REVIEW' - thumbnailSrc: string timeLimit: number memoryLimit: number updatedAt: string } +interface MyProblemProps { + forceEmpty?: boolean +} + const MOCK_MY_PROBLEMS: MyProblemCardItem[] = Array.from( { length: 48 }, (_, index) => { @@ -32,7 +35,6 @@ const MOCK_MY_PROBLEMS: MyProblemCardItem[] = Array.from( languages: ['C', 'Cpp', 'Java', 'Python3'], hasPassed: null, state: states[index % states.length], - thumbnailSrc: '/banners/problemInnerBanner.png', timeLimit: 1000 + (index % 4) * 500, memoryLimit: 128 + (index % 3) * 64, updatedAt: `2024-01-${String((index % 24) + 1).padStart(2, '0')} 19:00` @@ -40,7 +42,7 @@ const MOCK_MY_PROBLEMS: MyProblemCardItem[] = Array.from( } ) -export function MyProblem() { +export function MyProblem({ forceEmpty = false }: MyProblemProps) { const searchParams = useSearchParams() const search = searchParams.get('search') ?? '' const normalizedSearch = search.trim().toLowerCase() @@ -56,13 +58,9 @@ export function MyProblem() { ) }) - return ( - - ) + const data = forceEmpty ? [] : filteredProblems + + return } export function MyProblemFallback() { diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx index ceb70db3c4..f0d94e839b 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -15,14 +15,9 @@ import type { MyProblemCardItem } from './MyProblem' interface MyProblemDataTableProps { data: MyProblemCardItem[] search: string - emptyMessage?: string } -export function MyProblemDataTable({ - data, - search, - emptyMessage = 'No results.' -}: MyProblemDataTableProps) { +export function MyProblemDataTable({ data, search }: MyProblemDataTableProps) { const currentPath = usePathname() return ( @@ -34,15 +29,15 @@ export function MyProblemDataTable({
- +
) } + +function MyProblemEmptyState() { + return ( +
+
+

+ 내가 만든 문제가 +
+ 존재하지 않습니다. +

+ +
+
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx b/apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx new file mode 100644 index 0000000000..a29008c12e --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx @@ -0,0 +1,14 @@ +import { FetchErrorFallback } from '@/components/FetchErrorFallback' +import { TanstackQueryErrorBoundary } from '@/components/TanstackQueryErrorBoundary' +import { Suspense } from 'react' +import { MyProblem, MyProblemFallback } from '../../_components/MyProblem' + +export default function MyProblemsEmptyPage() { + return ( + + }> + + + + ) +} From 04fc60dfb953135a9ea108be6ab915bfc99493fb Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Wed, 1 Apr 2026 21:35:30 +0900 Subject: [PATCH 22/33] fix(fe): add state button on published tab --- .../(main)/problem/_components/ProblemDataTable.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index 7b8613357a..cd152d9520 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -1,6 +1,7 @@ 'use client' import { SearchBar } from '@/app/(client)/(main)/_components/SearchBar' +import { Button } from '@/components/shadcn/button' import { Table, TableBody, @@ -19,6 +20,7 @@ import { import type { Route } from 'next' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' +import { IoFilter } from 'react-icons/io5' interface Item { id: number @@ -70,6 +72,13 @@ export function ProblemDataTable({

{total}

+
From dd38462e5f7ca8938c17eb4130e82eb4b8849b40 Mon Sep 17 00:00:00 2001 From: woo943 Date: Wed, 1 Apr 2026 21:56:33 +0900 Subject: [PATCH 23/33] Update apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../(client)/(main)/problem/_components/ProblemDataTable.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index cd152d9520..7f689d5d66 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -59,10 +59,7 @@ export function ProblemDataTable({ const router = useRouter() const currentPath = usePathname() - const startIndex = (currentPage - 1) * itemsPerPage - const paginatedItems = table - .getRowModel() - .rows.slice(startIndex, startIndex + itemsPerPage) + const paginatedItems = table.getRowModel().rows return (
From 060472a5d0431e2ab646ab6dbc4497fba792ff8d Mon Sep 17 00:00:00 2001 From: woo943 Date: Wed, 1 Apr 2026 21:57:08 +0900 Subject: [PATCH 24/33] Update apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../(client)/(main)/problem/_components/MyProblemDataTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx index f0d94e839b..4ca31c5403 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -58,7 +58,7 @@ export function MyProblemDataTable({ data, search }: MyProblemDataTableProps) {
{data.map((problem) => { const href = - `${currentPath}/${problem.id}${search ? `?search=${search}` : ''}` as Route + `/problem/my-problem/${problem.id}${search ? `?search=${search}` : ''}` as Route return ( Date: Wed, 1 Apr 2026 22:11:09 +0900 Subject: [PATCH 25/33] fix(fe): fix gemini code review --- .../problem/_components/ProblemDataTable.tsx | 11 ++-- .../problem/_components/ProblemTable.tsx | 64 ++++++++++++++++--- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index cd152d9520..832ec76d0f 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -19,7 +19,7 @@ import { } from '@tanstack/react-table' import type { Route } from 'next' import Link from 'next/link' -import { usePathname, useRouter } from 'next/navigation' +import { usePathname } from 'next/navigation' import { IoFilter } from 'react-icons/io5' interface Item { @@ -56,7 +56,6 @@ export function ProblemDataTable({ columns, getCoreRowModel: getCoreRowModel() }) - const router = useRouter() const currentPath = usePathname() const startIndex = (currentPage - 1) * itemsPerPage @@ -114,13 +113,11 @@ export function ProblemDataTable({ const href = `${currentPath}/${row.original.id}${search ? `?search=${search}` : ''}` as Route - const handleClick = linked - ? () => { - router.push(href) - } - : (e: React.MouseEvent) => { + const handleClick = !linked + ? (e: React.MouseEvent) => { e.currentTarget.classList.toggle('expanded') } + : undefined return ( -
- - - +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {[...Array(10)].map((_, i) => ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ))}
- {[...Array(5)].map((_, i) => ( - - ))} - +
+ +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ +
+
) } From 1e40e6c85d687c80dcb569243159170d9c1cb679 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 3 Apr 2026 14:38:48 +0900 Subject: [PATCH 26/33] fix(fe): fix commented design --- .../app/(client)/(main)/problem/_components/MyProblem.tsx | 2 +- .../(main)/problem/_components/MyProblemDataTable.tsx | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx index c04fe7ecb9..c46ae41698 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblem.tsx @@ -74,7 +74,7 @@ export function MyProblemFallback() {
-
+
{[...Array(8)].map((_, i) => (
@@ -55,7 +52,7 @@ export function MyProblemDataTable({ data, search }: MyProblemDataTableProps) {
{data.length ? ( -
+
{data.map((problem) => { const href = `/problem/my-problem/${problem.id}${search ? `?search=${search}` : ''}` as Route @@ -68,7 +65,7 @@ export function MyProblemDataTable({ data, search }: MyProblemDataTableProps) { >
- + {problem.state}
From 069aa5fb5faf56eebdcbaa8e88722007f6825cd6 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 8 May 2026 16:45:47 +0900 Subject: [PATCH 27/33] chore(fe): erase unused varibales --- .../(client)/(main)/problem/_components/ProblemDataTable.tsx | 4 ---- .../app/(client)/(main)/problem/_components/ProblemTable.tsx | 2 -- 2 files changed, 6 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx index 2c7138956b..9a935ab60e 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemDataTable.tsx @@ -30,8 +30,6 @@ interface ProblemDataTableProps { columns: ColumnDef[] data: TData[] total: number - itemsPerPage: number - currentPage: number headerStyle: { [key: string]: string } @@ -44,8 +42,6 @@ export function ProblemDataTable({ columns, data, total, - itemsPerPage, - currentPage, headerStyle, search, linked = false, diff --git a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx index 5b305655cd..e23f98e068 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/ProblemTable.tsx @@ -75,8 +75,6 @@ function ProblemPaginatedTable({ data={data.data} total={data.total} columns={columns} - itemsPerPage={ITEMS_PER_PAGE} - currentPage={currentPage} search={search} headerStyle={{ title: From 5284994c0020bf047e9b1ccb2994f6b2e9e46203 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Fri, 8 May 2026 16:55:11 +0900 Subject: [PATCH 28/33] fix(fe): change search bar height --- .../(client)/(main)/problem/_components/MyProblemDataTable.tsx | 2 +- .../(client)/(main)/problem/_components/ProblemDataTable.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx index 2f8b771069..a914da98f9 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -31,7 +31,7 @@ export function MyProblemDataTable({ data, search }: MyProblemDataTableProps) { State - + - +
From a63020130f63c353081c42982c83efaf90be6362 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Tue, 12 May 2026 18:11:38 +0900 Subject: [PATCH 29/33] fix(fe): change about comments --- .../(main)/problem/_components/MyProblemDataTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx index a914da98f9..c675f6379f 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemDataTable.tsx @@ -34,7 +34,7 @@ export function MyProblemDataTable({ data, search }: MyProblemDataTableProps) { +
- {data.length ? ( + {filteredData.length ? (
- {data.map((problem) => { + {filteredData.map((problem) => { const href = `/problem/my-problem/${problem.id}${search ? `?search=${search}` : ''}` as Route const badgeClassName = getStateBadgeClassName(problem.state) diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx new file mode 100644 index 0000000000..c3b89b083d --- /dev/null +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx @@ -0,0 +1,110 @@ +'use client' + +import { Badge } from '@/components/shadcn/badge' +import { Button } from '@/components/shadcn/button' +import { Checkbox } from '@/components/shadcn/checkbox' +import { + Command, + CommandGroup, + CommandItem, + CommandList +} from '@/components/shadcn/command' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/components/shadcn/popover' +import { Separator } from '@/components/shadcn/separator' +import { IoFilter } from 'react-icons/io5' +import type { MyProblemCardItem } from './MyProblem' + +export const STATE_FILTER_OPTIONS: MyProblemCardItem['state'][] = [ + 'Published', + 'Ready', + 'Draft' +] + +interface MyProblemStateFilterProps { + selectedStates: MyProblemCardItem['state'][] + onSelectedStatesChange: (states: MyProblemCardItem['state'][]) => void +} + +export function MyProblemStateFilter({ + selectedStates, + onSelectedStatesChange +}: MyProblemStateFilterProps) { + const selectedValues = new Set(selectedStates) + + const handleFilterSelect = (value: MyProblemCardItem['state']) => { + const nextSelectedValues = new Set(selectedValues) + + if (nextSelectedValues.has(value)) { + nextSelectedValues.delete(value) + } else { + nextSelectedValues.add(value) + } + + onSelectedStatesChange(Array.from(nextSelectedValues)) + } + + return ( + + + + + + + + + + {STATE_FILTER_OPTIONS.map((state) => ( + handleFilterSelect(state)} + > + + {state} + + ))} + + + + + + ) +} From 976783ac3e46bb3d6dce43b2b5a8456f9c8d0715 Mon Sep 17 00:00:00 2001 From: hyeonwooseo Date: Sat, 16 May 2026 23:31:08 +0900 Subject: [PATCH 33/33] fix(fe): delete empty page --- .../problem/_components/MyProblemStateFilter.tsx | 4 ++-- .../(main)/problem/my-problem/empty/page.tsx | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx diff --git a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx index c3b89b083d..9623f1868b 100644 --- a/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx +++ b/apps/frontend/app/(client)/(main)/problem/_components/MyProblemStateFilter.tsx @@ -86,7 +86,7 @@ export function MyProblemStateFilter({ - + @@ -94,7 +94,7 @@ export function MyProblemStateFilter({ handleFilterSelect(state)} > diff --git a/apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx b/apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx deleted file mode 100644 index a29008c12e..0000000000 --- a/apps/frontend/app/(client)/(main)/problem/my-problem/empty/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { FetchErrorFallback } from '@/components/FetchErrorFallback' -import { TanstackQueryErrorBoundary } from '@/components/TanstackQueryErrorBoundary' -import { Suspense } from 'react' -import { MyProblem, MyProblemFallback } from '../../_components/MyProblem' - -export default function MyProblemsEmptyPage() { - return ( - - }> - - - - ) -}