Skip to content

Commit 4b65fca

Browse files
committed
add query and filter to list questions
1 parent 75c0147 commit 4b65fca

16 files changed

+341
-95
lines changed

jupyterlab_leetcode/handlers/leetcode_handler.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,28 @@ async def post(self):
254254
"categorySlug": "algorithms",
255255
"filters": {
256256
"filterCombineType": "ALL",
257-
"statusFilter": {"questionStatuses": [], "operator": "IS"},
258-
"difficultyFilter": {"difficulties": [], "operator": "IS"},
257+
"statusFilter": {
258+
"questionStatuses": query["statuses"],
259+
"operator": "IS",
260+
},
261+
"difficultyFilter": {
262+
"difficulties": query["difficulties"],
263+
"operator": "IS",
264+
},
259265
"languageFilter": {"languageSlugs": [], "operator": "IS"},
260-
"topicFilter": {"topicSlugs": [], "operator": "IS"},
266+
"topicFilter": {
267+
"topicSlugs": query["topics"],
268+
"operator": "IS",
269+
},
261270
"acceptanceFilter": {},
262271
"frequencyFilter": {},
263272
"frontendIdFilter": {},
264273
"lastSubmittedFilter": {},
265274
"publishedFilter": {},
266-
"companyFilter": {"companySlugs": [], "operator": "IS"},
275+
"companyFilter": {
276+
"companySlugs": query["companies"],
277+
"operator": "IS",
278+
},
267279
"positionFilter": {"positionSlugs": [], "operator": "IS"},
268280
"premiumFilter": {"premiumStatus": [], "operator": "IS"},
269281
},

src/components/BrowserMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ const BrowserMenu: React.FC<{
125125
>
126126
<Menu.Target>
127127
<Button
128-
rightSection={<IconChevronDown size={18} stroke={1.5} />}
128+
rightSection=<IconChevronDown size={18} stroke={1.5} />
129129
pr={12}
130130
radius="md"
131131
size="md"

src/components/DifficultyStatistics.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const DifficultyStatistics: React.FC<{ text: string; color: string }> = ({
77
}) => {
88
return (
99
<Stack>
10-
<Text>{text.charAt(0).toUpperCase() + text.slice(1)}</Text>
10+
<Text tt="capitalize">{text}</Text>
1111
</Stack>
1212
);
1313
};

src/components/LeetCodeMain.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ const LeetCodeMain: React.FC<{ docManager: IDocumentManager }> = ({
4444
};
4545

4646
return (
47-
<Container fluid={true} h={'100%'} p="lg" id="jll-main">
47+
<Container fluid={true} h="100%" p="lg" id="jll-main">
4848
<Stack>
49-
<Group id="jll-profile">
49+
<Group id="jll-profile" h={146}>
5050
{profile && <Profile profile={profile} />}
5151
{profile && <Statistics username={profile.username} />}
5252
</Group>

src/components/Profile.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const Profile: React.FC<{
1111
radius="md"
1212
withBorder
1313
p="sm"
14-
miw={'20%'}
15-
maw={'40%'}
14+
miw="20%"
15+
maw="40%"
1616
bg="var(--mantine-color-body)"
1717
>
1818
<Avatar src={profile.avatar} size={60} radius={60} mx="auto" />
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { useState } from 'react';
2+
import { Badge, MultiSelect, MultiSelectProps } from '@mantine/core';
3+
import { IconBuildings, IconCheck } from '@tabler/icons-react';
4+
import classes from '../styles/Filter.module.css';
5+
6+
const CheckedIcon = <IconCheck size={12} stroke={1.5} />;
7+
8+
const Data = ['facebook', 'google'];
9+
10+
const renderMultiSelectOption: MultiSelectProps['renderOption'] = ({
11+
option,
12+
checked
13+
}) => (
14+
<Badge
15+
leftSection={checked ? CheckedIcon : null}
16+
color="blue"
17+
variant="light"
18+
tt="capitalize"
19+
>
20+
{option.value}
21+
</Badge>
22+
);
23+
24+
const QuestionCompanyFilter: React.FC<{
25+
updateCompanies: (companies: string[]) => void;
26+
}> = ({ updateCompanies }) => {
27+
const [selected, setSelected] = useState(false);
28+
29+
return (
30+
<MultiSelect
31+
tt="capitalize"
32+
data={Data}
33+
renderOption={renderMultiSelectOption}
34+
maxDropdownHeight={300}
35+
placeholder="Company"
36+
checkIconPosition="left"
37+
leftSection={<IconBuildings size={16} stroke={1.5} />}
38+
leftSectionPointerEvents="none"
39+
clearable
40+
searchable
41+
onChange={v => {
42+
setSelected(v.length > 0);
43+
updateCompanies(v);
44+
}}
45+
className={selected ? classes.filter_selected : classes.filter_empty}
46+
/>
47+
);
48+
};
49+
50+
export default QuestionCompanyFilter;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { useState } from 'react';
2+
import { Badge, MultiSelect, MultiSelectProps } from '@mantine/core';
3+
import { IconCheck, IconGauge } from '@tabler/icons-react';
4+
import { DifficultyColors } from './Statistics';
5+
import classes from '../styles/Filter.module.css';
6+
7+
const CheckedIcon = <IconCheck size={12} stroke={1.5} />;
8+
9+
const Data = Object.keys(DifficultyColors);
10+
11+
const renderMultiSelectOption: MultiSelectProps['renderOption'] = ({
12+
option,
13+
checked
14+
}) => (
15+
<Badge
16+
leftSection={checked ? CheckedIcon : null}
17+
color={DifficultyColors[option.value.toLowerCase()] || 'blue'}
18+
variant="light"
19+
tt="capitalize"
20+
>
21+
{option.value}
22+
</Badge>
23+
);
24+
25+
const QuestionDifficultyFilter: React.FC<{
26+
updateDifficulties: (ds: string[]) => void;
27+
}> = ({ updateDifficulties }) => {
28+
const [selected, setSelected] = useState(false);
29+
30+
return (
31+
<MultiSelect
32+
tt="capitalize"
33+
data={Data}
34+
renderOption={renderMultiSelectOption}
35+
maxDropdownHeight={300}
36+
placeholder="Difficulty"
37+
checkIconPosition="left"
38+
leftSection={<IconGauge size={16} stroke={1.5} />}
39+
leftSectionPointerEvents="none"
40+
clearable
41+
searchable
42+
onChange={v => {
43+
setSelected(v.length > 0);
44+
updateDifficulties(v.map(v => v.toUpperCase()));
45+
}}
46+
className={selected ? classes.filter_selected : classes.filter_empty}
47+
/>
48+
);
49+
};
50+
51+
export default QuestionDifficultyFilter;

src/components/QuestionItem.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,44 +29,51 @@ const DifficultyAbbreviations: Record<string, string> = {
2929
hard: 'Hard'
3030
};
3131

32+
export const StatusColors: Record<string, string> = {
33+
SOLVED: 'green',
34+
ATTEMPTED: 'violet',
35+
TO_DO: 'gray'
36+
};
37+
38+
const IconProps = { size: 16, stroke: 1.5 };
39+
3240
const QuestionItem: React.FC<{
3341
question: LeetCodeQuestion;
3442
onGenerateSuccess: (p: string) => void;
3543
}> = ({ question, onGenerateSuccess }) => {
3644
const [showGenerateIcon, setShowGenerateIcon] = React.useState(false);
3745

3846
const statusIcon = () => {
39-
const size = 16;
4047
if (!question.status) {
4148
return null;
4249
}
4350
switch (question.status) {
4451
case 'SOLVED':
4552
return (
4653
<Tooltip fz="xs" label="Solved">
47-
<IconCheck size={size} color="green" stroke={1.5} />
54+
<IconCheck color={StatusColors[question.status]} {...IconProps} />
4855
</Tooltip>
4956
);
5057
case 'ATTEMPTED':
5158
return (
5259
<Tooltip fz="xs" label="Attempted">
53-
<IconCircle size={size} color="gray" stroke={1.5} />
60+
<IconCircle color={StatusColors[question.status]} {...IconProps} />
5461
</Tooltip>
5562
);
5663
case 'TO_DO':
5764
default:
5865
return question.paidOnly ? (
5966
<Tooltip fz="xs" label="Paid Only">
60-
<IconLock size={size} color={LeetCodeMainColor} stroke={1.5} />
67+
<IconLock color={LeetCodeMainColor} {...IconProps} />
6168
</Tooltip>
6269
) : (
63-
<div style={{ width: size, height: size }}></div>
70+
<div style={{ width: IconProps.size, height: IconProps.size }}></div>
6471
);
6572
}
6673
};
6774

68-
const generate = (slug: string) => {
69-
generateNotebook(slug)
75+
const generate = () => {
76+
generateNotebook(question.titleSlug)
7077
.then(({ filePath }) => {
7178
onGenerateSuccess(filePath);
7279
})
@@ -88,13 +95,15 @@ const QuestionItem: React.FC<{
8895
fz="sm"
8996
fw={600}
9097
>
98+
{question.questionFrontendId}
99+
{'. '}
91100
{question.title}
92101
</Anchor>
93102
</Group>
94103
</Table.Td>
95104

96105
<Table.Td className={classes.ac_column}>
97-
<Tooltip fz="xs" label="Acceptance Rate" position={'top-start'}>
106+
<Tooltip fz="xs" label="Acceptance Rate" position="top-start">
98107
<Text fz="sm" c="gray">
99108
{(question.acRate * 100).toFixed(2)}%
100109
</Text>
@@ -122,7 +131,7 @@ const QuestionItem: React.FC<{
122131
size="sm"
123132
variant="transparent"
124133
color={LeetCodeMainColor}
125-
onClick={() => generate(question.titleSlug)}
134+
onClick={() => generate()}
126135
>
127136
<IconBrandLeetcode stroke={1.5} />
128137
</ActionIcon>

src/components/QuestionQueryBar.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { Group, TextInput } from '@mantine/core';
3+
import { IconSearch } from '@tabler/icons-react';
4+
import { useDebouncedCallback } from '@mantine/hooks';
5+
6+
const QuestionQueryBar: React.FC<{
7+
updateKeyword: (keyword: string) => void;
8+
}> = ({ updateKeyword }) => {
9+
const debounced = useDebouncedCallback(updateKeyword, 200);
10+
11+
return (
12+
<Group>
13+
<TextInput
14+
placeholder="Search questions"
15+
leftSection=<IconSearch size={16} stroke={1.5} />
16+
onChange={e => debounced(e.target.value)}
17+
/>
18+
</Group>
19+
);
20+
};
21+
22+
export default QuestionQueryBar;

0 commit comments

Comments
 (0)