Skip to content

Commit 24d1e20

Browse files
authored
Merge pull request #21 from CS3219-AY2425S1/PEER-248-Question-Display
Peer 248 Question List Table (to do: link up with backend endpoints)
2 parents 2482ae0 + bbb7945 commit 24d1e20

File tree

14 files changed

+768
-21
lines changed

14 files changed

+768
-21
lines changed

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
"@radix-ui/react-icons": "^1.3.0",
1818
"@radix-ui/react-label": "^2.1.0",
1919
"@radix-ui/react-navigation-menu": "^1.2.0",
20+
"@radix-ui/react-select": "^2.1.1",
2021
"@radix-ui/react-scroll-area": "^1.1.0",
2122
"@radix-ui/react-separator": "^1.1.0",
2223
"@radix-ui/react-slot": "^1.1.0",
2324
"@radix-ui/react-tabs": "^1.1.0",
2425
"@tanstack/react-query": "^5.56.2",
2526
"@tanstack/react-query-devtools": "^5.56.2",
27+
"@tanstack/react-table": "^8.20.5",
2628
"axios": "^1.7.7",
2729
"class-variance-authority": "^0.7.0",
2830
"clsx": "^2.1.1",

frontend/src/assets/questions.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export const questions = [
1+
import { Question } from '@/types/question-types';
2+
3+
export const questionDetails = [
24
{
35
id: 1,
46
title: 'Reverse a String',
@@ -176,3 +178,11 @@ export const questions = [
176178
leetcode: 'https://leetcode.com/problems/trips-and-users/',
177179
},
178180
];
181+
182+
export const questions: Question[] = questionDetails.map((question) => ({
183+
id: question.id,
184+
title: question.title,
185+
difficulty: question.difficulty as 'Easy' | 'Medium' | 'Hard',
186+
topics: question.topics,
187+
attempted: false,
188+
}));
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import * as React from 'react';
2+
import { ChevronLeftIcon, ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons';
3+
4+
import { cn } from '@/lib/utils';
5+
import { ButtonProps, buttonVariants } from '@/components/ui/button';
6+
7+
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
8+
<nav
9+
role='navigation'
10+
aria-label='pagination'
11+
className={cn('mx-auto flex w-full justify-center', className)}
12+
{...props}
13+
/>
14+
);
15+
Pagination.displayName = 'Pagination';
16+
17+
const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<'ul'>>(
18+
({ className, ...props }, ref) => (
19+
<ul ref={ref} className={cn('flex flex-row items-center gap-1', className)} {...props} />
20+
)
21+
);
22+
PaginationContent.displayName = 'PaginationContent';
23+
24+
const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(
25+
({ className, ...props }, ref) => <li ref={ref} className={cn('', className)} {...props} />
26+
);
27+
PaginationItem.displayName = 'PaginationItem';
28+
29+
type PaginationLinkProps = {
30+
isActive?: boolean;
31+
} & Pick<ButtonProps, 'size'> &
32+
React.ComponentProps<'a'>;
33+
34+
const PaginationLink = ({ className, isActive, size = 'icon', ...props }: PaginationLinkProps) => (
35+
<a
36+
aria-current={isActive ? 'page' : undefined}
37+
className={cn(
38+
buttonVariants({
39+
variant: isActive ? 'outline' : 'ghost',
40+
size,
41+
}),
42+
className
43+
)}
44+
{...props}
45+
/>
46+
);
47+
PaginationLink.displayName = 'PaginationLink';
48+
49+
const PaginationPrevious = ({
50+
className,
51+
...props
52+
}: React.ComponentProps<typeof PaginationLink>) => (
53+
<PaginationLink
54+
aria-label='Go to previous page'
55+
size='default'
56+
className={cn('gap-1 pl-2.5', className)}
57+
{...props}
58+
>
59+
<ChevronLeftIcon className='size-4' />
60+
<span>Previous</span>
61+
</PaginationLink>
62+
);
63+
PaginationPrevious.displayName = 'PaginationPrevious';
64+
65+
const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
66+
<PaginationLink
67+
aria-label='Go to next page'
68+
size='default'
69+
className={cn('gap-1 pr-2.5', className)}
70+
{...props}
71+
>
72+
<span>Next</span>
73+
<ChevronRightIcon className='size-4' />
74+
</PaginationLink>
75+
);
76+
PaginationNext.displayName = 'PaginationNext';
77+
78+
const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => (
79+
<span
80+
aria-hidden
81+
className={cn('flex h-9 w-9 items-center justify-center', className)}
82+
{...props}
83+
>
84+
<DotsHorizontalIcon className='size-4' />
85+
<span className='sr-only'>More pages</span>
86+
</span>
87+
);
88+
PaginationEllipsis.displayName = 'PaginationEllipsis';
89+
90+
export {
91+
Pagination,
92+
PaginationContent,
93+
PaginationLink,
94+
PaginationItem,
95+
PaginationPrevious,
96+
PaginationNext,
97+
PaginationEllipsis,
98+
};
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import * as React from 'react';
2+
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons';
3+
import * as SelectPrimitive from '@radix-ui/react-select';
4+
5+
import { cn } from '@/lib/utils';
6+
7+
const Select = SelectPrimitive.Root;
8+
9+
const SelectGroup = SelectPrimitive.Group;
10+
11+
const SelectValue = SelectPrimitive.Value;
12+
13+
const SelectTrigger = React.forwardRef<
14+
React.ElementRef<typeof SelectPrimitive.Trigger>,
15+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
16+
>(({ className, children, ...props }, ref) => (
17+
<SelectPrimitive.Trigger
18+
ref={ref}
19+
className={cn(
20+
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
21+
className
22+
)}
23+
{...props}
24+
>
25+
{children}
26+
<SelectPrimitive.Icon asChild>
27+
<CaretSortIcon className='size-4 opacity-50' />
28+
</SelectPrimitive.Icon>
29+
</SelectPrimitive.Trigger>
30+
));
31+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
32+
33+
const SelectScrollUpButton = React.forwardRef<
34+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
35+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
36+
>(({ className, ...props }, ref) => (
37+
<SelectPrimitive.ScrollUpButton
38+
ref={ref}
39+
className={cn('flex cursor-default items-center justify-center py-1', className)}
40+
{...props}
41+
>
42+
<ChevronUpIcon />
43+
</SelectPrimitive.ScrollUpButton>
44+
));
45+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
46+
47+
const SelectScrollDownButton = React.forwardRef<
48+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
49+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
50+
>(({ className, ...props }, ref) => (
51+
<SelectPrimitive.ScrollDownButton
52+
ref={ref}
53+
className={cn('flex cursor-default items-center justify-center py-1', className)}
54+
{...props}
55+
>
56+
<ChevronDownIcon />
57+
</SelectPrimitive.ScrollDownButton>
58+
));
59+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
60+
61+
const SelectContent = React.forwardRef<
62+
React.ElementRef<typeof SelectPrimitive.Content>,
63+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
64+
>(({ className, children, position = 'popper', ...props }, ref) => (
65+
<SelectPrimitive.Portal>
66+
<SelectPrimitive.Content
67+
ref={ref}
68+
className={cn(
69+
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
70+
position === 'popper' &&
71+
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
72+
className
73+
)}
74+
position={position}
75+
{...props}
76+
>
77+
<SelectScrollUpButton />
78+
<SelectPrimitive.Viewport
79+
className={cn(
80+
'p-1',
81+
position === 'popper' &&
82+
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
83+
)}
84+
>
85+
{children}
86+
</SelectPrimitive.Viewport>
87+
<SelectScrollDownButton />
88+
</SelectPrimitive.Content>
89+
</SelectPrimitive.Portal>
90+
));
91+
SelectContent.displayName = SelectPrimitive.Content.displayName;
92+
93+
const SelectLabel = React.forwardRef<
94+
React.ElementRef<typeof SelectPrimitive.Label>,
95+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
96+
>(({ className, ...props }, ref) => (
97+
<SelectPrimitive.Label
98+
ref={ref}
99+
className={cn('px-2 py-1.5 text-sm font-semibold', className)}
100+
{...props}
101+
/>
102+
));
103+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
104+
105+
const SelectItem = React.forwardRef<
106+
React.ElementRef<typeof SelectPrimitive.Item>,
107+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
108+
>(({ className, children, ...props }, ref) => (
109+
<SelectPrimitive.Item
110+
ref={ref}
111+
className={cn(
112+
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
113+
className
114+
)}
115+
{...props}
116+
>
117+
<span className='absolute right-2 flex size-3.5 items-center justify-center'>
118+
<SelectPrimitive.ItemIndicator>
119+
<CheckIcon className='size-4' />
120+
</SelectPrimitive.ItemIndicator>
121+
</span>
122+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
123+
</SelectPrimitive.Item>
124+
));
125+
SelectItem.displayName = SelectPrimitive.Item.displayName;
126+
127+
const SelectSeparator = React.forwardRef<
128+
React.ElementRef<typeof SelectPrimitive.Separator>,
129+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
130+
>(({ className, ...props }, ref) => (
131+
<SelectPrimitive.Separator
132+
ref={ref}
133+
className={cn('-mx-1 my-1 h-px bg-muted', className)}
134+
{...props}
135+
/>
136+
));
137+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
138+
139+
export {
140+
Select,
141+
SelectGroup,
142+
SelectValue,
143+
SelectTrigger,
144+
SelectContent,
145+
SelectLabel,
146+
SelectItem,
147+
SelectSeparator,
148+
SelectScrollUpButton,
149+
SelectScrollDownButton,
150+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as React from 'react';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
6+
({ className, ...props }, ref) => (
7+
<div className='relative w-full overflow-auto'>
8+
<table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
9+
</div>
10+
)
11+
);
12+
Table.displayName = 'Table';
13+
14+
const TableHeader = React.forwardRef<
15+
HTMLTableSectionElement,
16+
React.HTMLAttributes<HTMLTableSectionElement>
17+
>(({ className, ...props }, ref) => (
18+
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
19+
));
20+
TableHeader.displayName = 'TableHeader';
21+
22+
const TableBody = React.forwardRef<
23+
HTMLTableSectionElement,
24+
React.HTMLAttributes<HTMLTableSectionElement>
25+
>(({ className, ...props }, ref) => (
26+
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
27+
));
28+
TableBody.displayName = 'TableBody';
29+
30+
const TableFooter = React.forwardRef<
31+
HTMLTableSectionElement,
32+
React.HTMLAttributes<HTMLTableSectionElement>
33+
>(({ className, ...props }, ref) => (
34+
<tfoot
35+
ref={ref}
36+
className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
37+
{...props}
38+
/>
39+
));
40+
TableFooter.displayName = 'TableFooter';
41+
42+
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
43+
({ className, ...props }, ref) => (
44+
<tr
45+
ref={ref}
46+
className={cn(
47+
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
48+
className
49+
)}
50+
{...props}
51+
/>
52+
)
53+
);
54+
TableRow.displayName = 'TableRow';
55+
56+
const TableHead = React.forwardRef<
57+
HTMLTableCellElement,
58+
React.ThHTMLAttributes<HTMLTableCellElement>
59+
>(({ className, ...props }, ref) => (
60+
<th
61+
ref={ref}
62+
className={cn(
63+
'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
64+
className
65+
)}
66+
{...props}
67+
/>
68+
));
69+
TableHead.displayName = 'TableHead';
70+
71+
const TableCell = React.forwardRef<
72+
HTMLTableCellElement,
73+
React.TdHTMLAttributes<HTMLTableCellElement>
74+
>(({ className, ...props }, ref) => (
75+
<td
76+
ref={ref}
77+
className={cn(
78+
'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
79+
className
80+
)}
81+
{...props}
82+
/>
83+
));
84+
TableCell.displayName = 'TableCell';
85+
86+
const TableCaption = React.forwardRef<
87+
HTMLTableCaptionElement,
88+
React.HTMLAttributes<HTMLTableCaptionElement>
89+
>(({ className, ...props }, ref) => (
90+
<caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
91+
));
92+
TableCaption.displayName = 'TableCaption';
93+
94+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };

frontend/src/lib/router.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { SignUp } from '@/routes/signup';
1111

1212
import { queryClient } from './query-client';
1313
import { ROUTES } from './routes';
14-
import { QuestionsList } from '@/routes/questions/list';
14+
import { Questions } from '@/routes/questions/main';
1515

1616
export const router = createBrowserRouter([
1717
{
@@ -27,7 +27,7 @@ export const router = createBrowserRouter([
2727
children: [
2828
{
2929
path: ROUTES.QUESTIONS,
30-
element: <QuestionsList />,
30+
element: <Questions />,
3131
},
3232
{
3333
path: ROUTES.QUESTION_DETAILS,

frontend/src/routes/questions/index.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)