[go: up one dir, main page]

Skip to content

Commit

Permalink
✨feat: 💻client & 🌐server: quizzes API
Browse files Browse the repository at this point in the history
ad features added
  • Loading branch information
b-l-i-n-d committed Jun 15, 2023
1 parent 4e9adf9 commit 3a74982
Show file tree
Hide file tree
Showing 32 changed files with 1,525 additions and 101 deletions.
20 changes: 12 additions & 8 deletions client/components/Menus/AdminMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,23 @@ const AdminMenu: React.FC = () => {
),
},
{
key: "/quizzes",
key: "/admin/quizzes",
label: (
<Button type="text">
<Typography.Text strong>Quizzes</Typography.Text>
</Button>
<Link href="/admin/quizzes">
<Button type="text">
<Typography.Text strong>Quizzes</Typography.Text>
</Button>
</Link>
),
},
{
key: "/assignments",
key: "/admin/assignments",
label: (
<Button type="text">
<Typography.Text strong>Assignments</Typography.Text>
</Button>
<Link href="/admin/assignments">
<Button type="text">
<Typography.Text strong>Assignments</Typography.Text>
</Button>
</Link>
),
},
{
Expand Down
130 changes: 130 additions & 0 deletions client/components/common/DebounceSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Select, Spin } from "antd";
import debounce from "lodash/debounce";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { apiConfig } from "../../configs";
import { DebounceSelectProps } from "../../interfaces";
import { useLazyGetVideosQuery } from "../../redux/features/videos/videosApi";

const DebounceSelect = <
ValueType extends {
key?: string;
label: React.ReactNode;
value: string | number;
} = any
>({
debounceTimeout = 800,
form,
...props
}: DebounceSelectProps<ValueType>) => {
const [fetching, setFetching] = useState(false);
const [options, setOptions] = useState<ValueType[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const fetchRef = useRef(0);
const [getVideos, { data: videos, isLoading, error }] =
useLazyGetVideosQuery();

const debounceFetcher = useMemo(() => {
const loadOptions = async (value: string) => {
fetchRef.current += 1;
const fetchId = fetchRef.current;
setOptions([]);
setFetching(true);

const searchValue = value.trim();
const queryOptions = searchValue
? { search: searchValue, page: 1, limit: apiConfig.PAGE_SIZE }
: { page: 1, limit: apiConfig.PAGE_SIZE };

await getVideos(queryOptions);

if (fetchId !== fetchRef.current) {
// for fetch callback order
return;
}
setFetching(false);
};

return debounce(loadOptions, debounceTimeout);
}, [getVideos, debounceTimeout]);

const handlePopupScroll = (e: React.UIEvent<HTMLDivElement>) => {
const { target }: any = e;
if (
target.scrollTop + target.offsetHeight === target.scrollHeight &&
!isLoading &&
!error &&
videos &&
videos.totalResults > options.length
) {
setCurrentPage((prev) => prev + 1);
}
};

useEffect(() => {
getVideos({ page: currentPage, limit: apiConfig.PAGE_SIZE });
}, [currentPage, getVideos]);

useEffect(() => {
if (!isLoading && !error && videos) {
setOptions((prevOptions) => {
const newOptions = videos.results.map((video) => ({
key: String(video.id),
label: video.title,
value: video.id,
}));

const updatedOptions = [...prevOptions, ...newOptions];

const uniqueOptions = updatedOptions.reduce((acc, current) => {
const x = acc.find((item) => item.value === current.value);
if (!x) {
return acc.concat([current] as ValueType[]);
} else {
return acc;
}
}, [] as ValueType[]);

return uniqueOptions as ValueType[];
});
}
}, [videos, isLoading, error]);

useEffect(() => {
if (form && form.getFieldValue("videoTitle") && props.value) {
setOptions((prevOptions) => {
const newOption = {
key: String(props.value),
label: form.getFieldValue("videoTitle"),
value: props.value,
};

const updatedOptions = [...prevOptions, newOption];

const uniqueOptions = updatedOptions.reduce((acc, current) => {
const x = acc.find((item) => item.value === current.value);
if (!x) {
return acc.concat([current] as ValueType[]);
} else {
return acc;
}
}, [] as ValueType[]);

return uniqueOptions as ValueType[];
});
}
}, [form, props.value]);

return (
<Select
showSearch
filterOption={false}
onSearch={debounceFetcher}
notFoundContent={fetching ? <Spin size="small" /> : null}
{...props}
onPopupScroll={handlePopupScroll}
options={options}
/>
);
};

export default DebounceSelect;
2 changes: 2 additions & 0 deletions client/components/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Loader from "./Loader";
import DebounceSelect from "./DebounceSelect";

const Common = {
DebounceSelect,
Loader,
};

Expand Down
4 changes: 2 additions & 2 deletions client/helpers/generateQueryUrl.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { VideosQueryParams } from "../interfaces";
import { QuizzesQueryParams, VideosQueryParams } from "../interfaces";

export default function generateQueryUrl(
url: string,
params: VideosQueryParams
params: VideosQueryParams | QuizzesQueryParams
) {
let query = url + "?";
for (const [key, value] of Object.entries(params)) {
Expand Down
45 changes: 45 additions & 0 deletions client/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { FormInstance, FormProps } from "antd";
import type { SelectProps } from "antd/es/select";
export interface VideosQueryParams {
title?: string;
description?: string;
sortBy?: string;
page?: number;
limit?: number;
search?: string;
}

export interface Videos {
Expand All @@ -22,3 +25,45 @@ export interface Video {
thumbnail: string;
duration: number;
}

export interface QuizzesQueryParams {
question?: string;
videoId?: string;
sortBy?: string;
page?: number;
limit?: number;
}

export interface Quizzes {
results: Quizz[];
totalResults: number;
limit: number;
page: number;
totalPages: number;
}

export interface Quizz {
id: string;
question: string;
video: string | Video;
options: Options[];
}

export interface QuizzParams {
question: string;
video: string;
options: Options[];
}

export interface Options {
option: string;
isCorrect: boolean;
}

export type ModalType = "add" | "edit" | "show";

export interface DebounceSelectProps<ValueType = any>
extends Omit<SelectProps<ValueType | ValueType[]>, "options" | "children"> {
debounceTimeout?: number;
form?: FormInstance<any>;
}
34 changes: 30 additions & 4 deletions client/pages/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import Head from "next/head";
import { useState } from "react";
import { Auth } from "../../components";
import { apiConfig } from "../../configs";
import { useGetQuizzesQuery } from "../../redux/features/quizzes/quizzesApi";
import { useGetVideosQuery } from "../../redux/features/videos/videosApi";
import { NumberOutlined } from "@ant-design/icons";

const Dashboard: NextPage = () => {
const [currentPage, setCurrentPage] = useState(1);
Expand All @@ -16,6 +18,14 @@ const Dashboard: NextPage = () => {
page: currentPage,
limit: apiConfig.PAGE_SIZE,
});
const {
data: quizzes,
isLoading: isGetQuizzesLoading,
error: getQuizzesError,
} = useGetQuizzesQuery({
page: currentPage,
limit: apiConfig.PAGE_SIZE,
});

return (
<Auth.AdminOnly>
Expand All @@ -30,7 +40,14 @@ const Dashboard: NextPage = () => {
textAlign: "center",
fontWeight: "bold",
}}
value={videos?.totalResults}
prefix={<NumberOutlined />}
value={
(!isGetVideosLoading &&
!getVideosError &&
videos &&
videos.totalResults) ||
0
}
/>
</Card>
</Col>
Expand All @@ -41,7 +58,14 @@ const Dashboard: NextPage = () => {
textAlign: "center",
fontWeight: "bold",
}}
value={videos?.totalResults}
prefix={<NumberOutlined />}
value={
(!isGetQuizzesLoading &&
!getQuizzesError &&
quizzes &&
quizzes.totalResults) ||
0
}
/>
</Card>
</Col>
Expand All @@ -52,7 +76,8 @@ const Dashboard: NextPage = () => {
textAlign: "center",
fontWeight: "bold",
}}
value={videos?.totalResults}
prefix={<NumberOutlined />}
value={0}
/>
</Card>
</Col>
Expand All @@ -63,7 +88,8 @@ const Dashboard: NextPage = () => {
textAlign: "center",
fontWeight: "bold",
}}
value={videos?.totalResults}
prefix={<NumberOutlined />}
value={0}
/>
</Card>
</Col>
Expand Down
Loading

0 comments on commit 3a74982

Please sign in to comment.