Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add feature of leaderboard search and sort and multiple correct to the application #55

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
12 changes: 7 additions & 5 deletions src/modules/checkQuiz/api/generateLeaderboard.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow code structure, patch get requests will not come here

Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import axiosInstance from './axiosInstance'
export const GenerateLeaderboard = async ({
quizId,
sectionIndex = null,
searchQuery = null,
}: {
quizId: string
sectionIndex: number | null
searchQuery: string | null
}) => {
try {
if (sectionIndex === null) {
const res = await axiosInstance.patch(`/checkQuiz/leaderboard/${quizId}`)
if (searchQuery === null) {
const res = await axiosInstance.patch(`/checkQuiz/generateSectionLeaderboard/${quizId}/${sectionIndex}`,)
return res.data
} else {
} else{
const res = await axiosInstance.patch(
`/checkQuiz/generateSectionLeaderboard/${quizId}/${sectionIndex}`,
`/checkQuiz/generateSectionLeaderboard/${quizId}/${sectionIndex}?search=${searchQuery}`,
)
return res.data
}
}
} catch (e: any) {
if (axios.isAxiosError(e)) {
return e.response?.data || e.message
Expand Down
12 changes: 9 additions & 3 deletions src/modules/checkQuiz/api/getDashboard.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import axios from 'axios'
import axiosInstance from './axiosInstance'

export const getDashboard = async (quizId: string, sectionIndex: number | null = null) => {
export const getDashboard = async (quizId: string, sectionIndex: number | null = null, searchQuery: string | null = null) => {
try {
if (sectionIndex === null) {
if (sectionIndex === null && searchQuery === null) {
const res = await axiosInstance.get(`/checkQuiz/dashboard/${quizId}`)
return res.data
} else {
} else if (sectionIndex !== null && searchQuery === null) {
const res = await axiosInstance.get(`/checkQuiz/sectionLeaderboard/${quizId}/${sectionIndex}`)
return res.data
} else if (sectionIndex === null && searchQuery !== null) {
const res = await axiosInstance.get(`/checkQuiz/dashboard/${quizId}?search=${searchQuery}`)
return res.data
} else {
const res = await axiosInstance.get(`/checkQuiz/sectionLeaderboard/${quizId}/${sectionIndex}?search=${searchQuery}`)
return res.data
}
} catch (e: unknown) {
if (axios.isAxiosError(e)) {
Expand Down
6 changes: 3 additions & 3 deletions src/modules/checkQuiz/api/useDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { useQuery, useMutation } from '@tanstack/react-query'
import axiosInstance from './axiosInstance'
import { getDashboard } from './getDashboard'

export const useFetchDashboard = (quizId: string, sectionIndex: number | null = null) => {
export const useFetchDashboard = (quizId: string, sectionIndex: number | null = null, searchQuery: string | null = null) => {
const query = useQuery({
queryKey: ['fetchDashboard', quizId, sectionIndex],
queryFn: () => getDashboard(quizId, sectionIndex),
queryKey: ['fetchDashboard', quizId, sectionIndex, searchQuery],
queryFn: () => getDashboard(quizId, sectionIndex, searchQuery),
})
return query
}
1 change: 0 additions & 1 deletion src/modules/checkQuiz/api/useLeaderboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useMutation } from '@tanstack/react-query'
import axiosInstance from './axiosInstance'
import { GenerateLeaderboard } from './generateLeaderboard'

export const useLeaderboard = () => {
Expand Down
173 changes: 94 additions & 79 deletions src/modules/checkQuiz/components/Filters.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of handleAddClick function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to find a better way than window.location.reload()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put this setdebounced function in hooks folder

Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useEffect, useState } from 'react'
import { Select } from 'chakra-react-select'
import { Button, HStack, Input, Select as SelectChakra, Text } from '@chakra-ui/react'
import { Button, HStack, Input, IconButton, Select as SelectChakra, Text } from '@chakra-ui/react'
import { useLeaderboard } from '@checkQuiz/api/useLeaderboard'
import AutocheckModal from './Modals/Autocheck'
import useCheckQuizStore from '@checkQuiz/store/checkQuizStore'
import { Section } from '@checkQuiz/types'
import { useFetchDashboard } from '@checkQuiz/api/useDashboard'
import { getDashboard } from '@checkQuiz/api/getDashboard'
import { AddIcon } from '@chakra-ui/icons';
import useDebouncedValue from '@checkQuiz/hooks/useDebouncedValue'
import { useNavigate, useLocation } from 'react-router-dom'; // For URL handling

interface FiltersProps {
question?: boolean
Expand All @@ -22,14 +23,24 @@ const Filters: React.FC<FiltersProps> = ({
const [assignees, setAssignees] = useState<any>([])
const [isAutocheckModalOpen, setIsAutocheckModalOpen] = useState<boolean>(false)
const [totalAutocheckQuestions, setTotalAutocheckQuestions] = useState<number>(0)
const [sectionIndex, setSectionIndex] = useState<number | null>(null)
const [sectionIndex, setSectionIndex] = useState<number | null>(null) // Actual sectionIndex state
const [tempSectionIndex, setTempSectionIndex] = useState<number | null>(null) // Temporary sectionIndex state
const [quizId] = useCheckQuizStore((state) => [state.quizId])
const [leaderboard, setLeaderboard] = useCheckQuizStore((state) => [
state.leaderboard,
state.setLeaderboard,
])
const [searchQuery, setSearchQuery] = useState('')
const debouncedSearchQuery = useDebouncedValue(searchQuery, 300)

const { data, isFetched, refetch } = useFetchDashboard(quizId, sectionIndex)
const navigate = useNavigate();
const location = useLocation();

const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value.toLocaleLowerCase())
}

const { data, isFetched, refetch } = useFetchDashboard(quizId, sectionIndex, debouncedSearchQuery)

useEffect(() => {
if (isFetched && data) {
Expand All @@ -42,10 +53,8 @@ const Filters: React.FC<FiltersProps> = ({
setLeaderboard(data?.sectionLeaderboard[0].participants || [])
}
}
} else {
refetch()
}
}, [sectionIndex, isFetched, data])
}, [sectionIndex, isFetched, data, setLeaderboard])

useEffect(() => {
if (sections) {
Expand All @@ -59,94 +68,102 @@ const Filters: React.FC<FiltersProps> = ({
})
setTotalAutocheckQuestions(totalAutocheckQuestionsCount)
}
}, [sections])
}, [sections, searchQuery])

const { mutate: generateLeaderboard } = useLeaderboard()
const {
data: sectionData,
isFetched: sectionDataIsFetched,
refetch: sectionDataRefetch,
} = useFetchDashboard(quizId, sectionIndex)

const handleLeaderboard = (sectionIndex: number | null) => {
generateLeaderboard(
{ quizId, sectionIndex },
{
onSuccess: () => {
window.location.reload()
},
},
)
}

// TODO: fetch assignees from athena
const [availableAssignees] = useState([
{ value: '1', label: 'A' },
{ value: '2', label: 'B' },
{ value: '3', label: 'C' },
{ value: '4', label: 'D' },
{ value: '5', label: 'E' },
])

const handleAssigneesChange = (selectedOptions: any) => {
const selectedAssignees = Array.isArray(selectedOptions) ? selectedOptions : [selectedOptions]
setAssignees(selectedAssignees)
const handleLeaderboard = () => {
setSectionIndex(tempSectionIndex);
}

useEffect(() => {
if (quizId !== null) {
generateLeaderboard(
{ quizId, sectionIndex: sectionIndex, searchQuery: debouncedSearchQuery },
{
onSuccess: () => {
refetch();
},
}
);
}
}, [sectionIndex, quizId, debouncedSearchQuery, generateLeaderboard, refetch]);

const handleSectionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
if (e.target.value != '') {
setSectionIndex(parseInt(e.target.value))
const value = e.target.value;
if (value !== '') {
setTempSectionIndex(parseInt(value, 10));
} else {
setSectionIndex(null)
setTempSectionIndex(null);
}
}

useEffect(() => {
sectionDataRefetch()
}, [sectionIndex])
const handleAddClick = () => {
console.log('Add button clicked');
};

return (
<>
<HStack spacing={4} alignItems='center' width='full' justifyContent='space-between' mt={6}>
<HStack spacing={4} alignItems='center' width='full'>
{question && (
<>
<Text fontSize='0.875rem' color='#939393'>
Assigned to:
</Text>
<Select
styles={{
option: (provided, state) => ({
...provided,
padding: '0.75rem',
fontSize: '0.875rem',
fontWeight: '600',
borderRadius: '0.25rem',
}),
}}
value={assignees}
options={availableAssignees}
isMulti
placeholder='Assignees'
onChange={handleAssigneesChange}
/>
</>
<Input
maxWidth='20rem'
placeholder='Search or add assignee'
variant='outline'
borderColor='grey'
borderRadius='0.25rem'
fontSize='0.875rem'
fontWeight='600'
color='grey'
value={searchQuery}
onChange={handleSearchChange}
_placeholder={{ color: 'grey' }}
/>
<IconButton
aria-label="Add"
icon={<AddIcon />}
onClick={handleAddClick}
size="sm"
bgColor="brand"
color="white"
borderRadius="0.25rem"
/>
</>
)}

{participants && (
<>
<Input
maxWidth='20rem'
placeholder='Search'
variant='outline'
borderColor='#939393'
borderRadius='0.25rem'
fontSize='0.875rem'
fontWeight='600'
color='#939393'
_placeholder={{ color: '#939393' }}
/>
</>
<Input
maxWidth='20rem'
placeholder='Search'
variant='outline'
borderColor='grey'
borderRadius='0.25rem'
fontSize='0.875rem'
fontWeight='600'
color='grey'
value={searchQuery}
onChange={handleSearchChange}
_placeholder={{ color: 'grey' }}
/>
<Text fontSize='0.875rem' color='grey'>
Sort by
</Text>
<SelectChakra
width='12rem'
placeholder='None'
color='grey'
onChange={handleSectionChange}
>
{sections.map((section, index) => (
<option value={index} key={section.name}>
{section.name}
</option>
))}
</SelectChakra>
</>
)}
</HStack>
{participants && (
Expand All @@ -157,9 +174,7 @@ const Filters: React.FC<FiltersProps> = ({
py={3}
fontSize='0.875rem'
fontWeight='400'
onClick={() => {
handleLeaderboard(sectionIndex)
}}
onClick={handleLeaderboard}
>
Generate Leaderboard
</Button>
Expand Down Expand Up @@ -190,4 +205,4 @@ const Filters: React.FC<FiltersProps> = ({
)
}

export default Filters
export default Filters;
6 changes: 3 additions & 3 deletions src/modules/checkQuiz/components/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Leaderboard = () => {
columnHelper.accessor('Sr', {
cell: (info) => info.row.index + 1,
header: 'Sr.',
enableColumnFilter: true,
enableColumnFilter: false,
}),

columnHelper.accessor('name', {
Expand Down Expand Up @@ -67,7 +67,7 @@ const Leaderboard = () => {
columnHelper.accessor('rank', {
cell: (info) => info.row.index + 1,
header: 'Current rank',
enableColumnFilter: true,
enableColumnFilter: false,
}),
columnHelper.accessor('marks', {
cell: (info) => info.row.original.marks,
Expand Down Expand Up @@ -95,4 +95,4 @@ const Leaderboard = () => {
)
}

export default Leaderboard
export default Leaderboard
2 changes: 1 addition & 1 deletion src/modules/checkQuiz/components/TabViewDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ const TabViewDashboard = () => {
)
}

export default TabViewDashboard
export default TabViewDashboard
19 changes: 19 additions & 0 deletions src/modules/checkQuiz/hooks/useDebouncedValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {useState, useEffect} from 'react'

const useDebouncedValue = <T>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value)

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)

return () => {
clearTimeout(handler)
}
}, [value, delay])

return debouncedValue
}

export default useDebouncedValue
Loading