Skip to content

Commit 31e851b

Browse files
committed
PEER-248: Table with client side pagination and filtering
1 parent 0d7886e commit 31e851b

File tree

9 files changed

+411
-127
lines changed

9 files changed

+411
-127
lines changed

backend/question/src/services/get/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export type IGetQuestionsResponse = IServiceResponse<{
2525
* - attempted?: May need joining with users table, or not
2626
*/
2727
}>;
28-
totalQuestions: number;
28+
hasNextPage: boolean;
2929
}>;
3030

3131
//=============================================================================

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
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-slot": "^1.1.0",
2122
"@radix-ui/react-tabs": "^1.1.0",
2223
"@tanstack/react-query": "^5.56.2",

frontend/src/assets/dummyData.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Question } from '@/routes/question/logic';
2+
3+
export const dummyData: Question[] = [
4+
{
5+
number: 1,
6+
attempted: true,
7+
name: 'Basic Arrays',
8+
difficulty: 'Easy',
9+
topic: ['Arrays', 'Basic'],
10+
},
11+
{
12+
number: 2,
13+
attempted: false,
14+
name: 'Sorting Algorithms',
15+
difficulty: 'Medium',
16+
topic: ['Algorithms', 'Sorting'],
17+
},
18+
{
19+
number: 3,
20+
attempted: true,
21+
name: 'Graph Traversal',
22+
difficulty: 'Hard',
23+
topic: ['Graphs', 'DFS', 'BFS'],
24+
},
25+
{
26+
number: 4,
27+
attempted: false,
28+
name: 'Dynamic Programming',
29+
difficulty: 'Hard',
30+
topic: ['Dynamic Programming'],
31+
},
32+
{
33+
number: 5,
34+
attempted: true,
35+
name: 'Binary Search Trees',
36+
difficulty: 'Medium',
37+
topic: ['Trees', 'Binary Search'],
38+
},
39+
{
40+
number: 6,
41+
attempted: false,
42+
name: 'Recursion Basics',
43+
difficulty: 'Easy',
44+
topic: ['Recursion'],
45+
},
46+
{
47+
number: 7,
48+
attempted: true,
49+
name: 'Hash Tables',
50+
difficulty: 'Easy',
51+
topic: ['Data Structures', 'Hashing'],
52+
},
53+
{
54+
number: 8,
55+
attempted: false,
56+
name: 'Graph Algorithms',
57+
difficulty: 'Medium',
58+
topic: ['Graphs', 'Shortest Path'],
59+
},
60+
{
61+
number: 9,
62+
attempted: true,
63+
name: 'Object-Oriented Programming',
64+
difficulty: 'Medium',
65+
topic: ['OOP', 'Classes', 'Inheritance'],
66+
},
67+
{
68+
number: 10,
69+
attempted: false,
70+
name: 'Concurrency Basics',
71+
difficulty: 'Hard',
72+
topic: ['Concurrency', 'Multithreading'],
73+
},
74+
{
75+
number: 11,
76+
attempted: true,
77+
name: 'Machine Learning Introduction',
78+
difficulty: 'Medium',
79+
topic: ['Machine Learning', 'Algorithms'],
80+
},
81+
{
82+
number: 12,
83+
attempted: false,
84+
name: 'Database Normalization',
85+
difficulty: 'Medium',
86+
topic: ['Databases', 'Normalization'],
87+
},
88+
];
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+
};

frontend/src/routes/question/logic.ts

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IGetQuestionsResponse } from '../../types/question-types';
2+
import { dummyData } from '@/assets/dummyData';
23

34
export type Question = {
45
number: number;
@@ -8,94 +9,10 @@ export type Question = {
89
attempted: boolean;
910
};
1011

11-
export async function fetchQuestions(): Promise<IGetQuestionsResponse> {
12-
const dummyData: Question[] = [
13-
{
14-
number: 1,
15-
attempted: true,
16-
name: 'Basic Arrays',
17-
difficulty: 'Easy',
18-
topic: ['Arrays', 'Basic'],
19-
},
20-
{
21-
number: 2,
22-
attempted: false,
23-
name: 'Sorting Algorithms',
24-
difficulty: 'Medium',
25-
topic: ['Algorithms', 'Sorting'],
26-
},
27-
{
28-
number: 3,
29-
attempted: true,
30-
name: 'Graph Traversal',
31-
difficulty: 'Hard',
32-
topic: ['Graphs', 'DFS', 'BFS'],
33-
},
34-
{
35-
number: 4,
36-
attempted: false,
37-
name: 'Dynamic Programming',
38-
difficulty: 'Hard',
39-
topic: ['Dynamic Programming'],
40-
},
41-
{
42-
number: 5,
43-
attempted: true,
44-
name: 'Binary Search Trees',
45-
difficulty: 'Medium',
46-
topic: ['Trees', 'Binary Search'],
47-
},
48-
{
49-
number: 6,
50-
attempted: false,
51-
name: 'Recursion Basics',
52-
difficulty: 'Easy',
53-
topic: ['Recursion'],
54-
},
55-
{
56-
number: 7,
57-
attempted: true,
58-
name: 'Hash Tables',
59-
difficulty: 'Easy',
60-
topic: ['Data Structures', 'Hashing'],
61-
},
62-
{
63-
number: 8,
64-
attempted: false,
65-
name: 'Graph Algorithms',
66-
difficulty: 'Medium',
67-
topic: ['Graphs', 'Shortest Path'],
68-
},
69-
{
70-
number: 9,
71-
attempted: true,
72-
name: 'Object-Oriented Programming',
73-
difficulty: 'Medium',
74-
topic: ['OOP', 'Classes', 'Inheritance'],
75-
},
76-
{
77-
number: 10,
78-
attempted: false,
79-
name: 'Concurrency Basics',
80-
difficulty: 'Hard',
81-
topic: ['Concurrency', 'Multithreading'],
82-
},
83-
{
84-
number: 11,
85-
attempted: true,
86-
name: 'Machine Learning Introduction',
87-
difficulty: 'Medium',
88-
topic: ['Machine Learning', 'Algorithms'],
89-
},
90-
{
91-
number: 12,
92-
attempted: false,
93-
name: 'Database Normalization',
94-
difficulty: 'Medium',
95-
topic: ['Databases', 'Normalization'],
96-
},
97-
];
12+
export const DIFFICULTY_OPTIONS = ['Easy', 'Medium', 'Hard'];
13+
export const ROWS_PER_PAGE = 12;
9814

15+
export async function fetchQuestions(): Promise<IGetQuestionsResponse> {
9916
return {
10017
questions: dummyData,
10118
totalQuestions: 20,

0 commit comments

Comments
 (0)