From 1cefa219a9420512da1e40602b7042a314357d02 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 18 Jan 2022 22:40:17 +0300 Subject: [PATCH] feat: added categories page --- .../CategoryTableList/CategoryTableHead.tsx | 41 ++++++++++++ .../CategoryTableList/CategoryTableList.tsx | 64 +++++++++++++++++++ .../CategoryTableList/CategoryTableRow.tsx | 26 ++++++++ .../Tables/CategoryTableList/Cells/index.tsx | 54 ++++++++++++++++ .../Tables/CategoryTableList/index.tsx | 4 ++ src/config.ts | 8 +++ .../queries/category/useFetchCategories.ts | 29 +++++++++ src/hooks/queries/index.ts | 6 ++ src/interfaces/index.ts | 2 + src/pages/categories/categories.tsx | 26 ++++++++ .../components/CourseList/CourseList.tsx | 5 +- src/pages/courses/edit/components/index.tsx | 2 +- src/routes.config.ts | 9 +++ 13 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 src/components/Tables/CategoryTableList/CategoryTableHead.tsx create mode 100644 src/components/Tables/CategoryTableList/CategoryTableList.tsx create mode 100644 src/components/Tables/CategoryTableList/CategoryTableRow.tsx create mode 100644 src/components/Tables/CategoryTableList/Cells/index.tsx create mode 100644 src/components/Tables/CategoryTableList/index.tsx create mode 100644 src/hooks/queries/category/useFetchCategories.ts create mode 100644 src/pages/categories/categories.tsx diff --git a/src/components/Tables/CategoryTableList/CategoryTableHead.tsx b/src/components/Tables/CategoryTableList/CategoryTableHead.tsx new file mode 100644 index 0000000..5697236 --- /dev/null +++ b/src/components/Tables/CategoryTableList/CategoryTableHead.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Icon, Flex, Text } from '@chakra-ui/react'; +import { Thead, Tr, Th } from '@chakra-ui/react'; +import { MdCategory, MdColorLens, MdSettings } from 'react-icons/md'; + +/** + * Hooks + */ +import { useColorModeValue } from '@chakra-ui/react'; + +const CategoryTableHead: React.FC = () => { + return ( + + + + + + + + + + Name + + + + Created + Last update + + + + + Actions + + + + + + ); +}; + +export default CategoryTableHead; diff --git a/src/components/Tables/CategoryTableList/CategoryTableList.tsx b/src/components/Tables/CategoryTableList/CategoryTableList.tsx new file mode 100644 index 0000000..fca6a31 --- /dev/null +++ b/src/components/Tables/CategoryTableList/CategoryTableList.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Flex, Box, Text, Table, Tbody } from '@chakra-ui/react'; +import { CreateButton } from '@educt/components/Buttons'; +import { ICategory } from '@educt/interfaces'; +import CategoryTableHead from './CategoryTableHead'; +import { CategoryTableRowPropsType } from './CategoryTableRow'; + +/** + * Hooks + */ +import { useState } from 'react'; + +type CategoryTableListPropsType = { + render: React.FC; + categories: ICategory[]; +}; + +const CategoryTableList: React.FC = props => { + const { render: Row, categories } = props; + const [rows, setRows] = useState(categories); + + const isEmptyRows = rows.length === 0; + + // TODO make delete query + const handleDelete = () => () => undefined; + + // TODO make edit query + const handleEdit = () => () => undefined; + + return ( + + + + + + + + {rows.map(category => ( + + ))} + +
+ {isEmptyRows && ( + + + Cannot find any category. + + + )} + + {rows.length} categories + +
+ ); +}; + +export default CategoryTableList; diff --git a/src/components/Tables/CategoryTableList/CategoryTableRow.tsx b/src/components/Tables/CategoryTableList/CategoryTableRow.tsx new file mode 100644 index 0000000..f48b1ad --- /dev/null +++ b/src/components/Tables/CategoryTableList/CategoryTableRow.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { ICategory } from '@educt/interfaces'; +import { Tr } from '@chakra-ui/react'; +import { ActionsCell, ColorCell, CreatedCell, NameCell, UpdatedCell } from './Cells'; + +export type CategoryTableRowPropsType = { + category: ICategory; + actions: { + onEdit: (category: ICategory) => () => void; + onDelete: (id: string) => () => void; + }; +}; + +const CategoryTableRow: React.FC = ({ category, actions }) => { + return ( + + + + + + + + ); +}; + +export default CategoryTableRow; diff --git a/src/components/Tables/CategoryTableList/Cells/index.tsx b/src/components/Tables/CategoryTableList/Cells/index.tsx new file mode 100644 index 0000000..86372ed --- /dev/null +++ b/src/components/Tables/CategoryTableList/Cells/index.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Box, Td, IconButton } from '@chakra-ui/react'; +import { Menu, MenuList, MenuButton, MenuItem } from '@chakra-ui/react'; +import { MdEdit, MdMoreHoriz } from 'react-icons/md'; +import { DeleteIcon } from '@chakra-ui/icons'; +import { ICategory } from '@educt/interfaces'; + +type ColorCellPropsType = { + color: ICategory['color']; +}; +export const ColorCell: React.FC = ({ color }) => ( + + + +); + +type NameCellPropsType = { + name: string; +}; +export const NameCell: React.FC = ({ name }) => {name}; + +type CreatedCellPropsType = { + created: string; +}; +export const CreatedCell: React.FC = ({ created }) => ( + {new Date(created).toLocaleDateString()} +); + +type UpdatedCellPropsType = { + updated: string; +}; +export const UpdatedCell: React.FC = ({ updated }) => ( + {new Date(updated).toLocaleDateString()} +); + +type ActionsCellPropsType = { + onEdit: () => void; + onDelete: () => void; +}; +export const ActionsCell: React.FC = ({ onEdit, onDelete }) => ( + + + } variant='ghost' /> + + }> + Edit + + } color='red.400'> + Delete + + + + +); diff --git a/src/components/Tables/CategoryTableList/index.tsx b/src/components/Tables/CategoryTableList/index.tsx new file mode 100644 index 0000000..5d6e569 --- /dev/null +++ b/src/components/Tables/CategoryTableList/index.tsx @@ -0,0 +1,4 @@ +import CategoryTableList from './CategoryTableList'; +import CategoryTableRow from './CategoryTableRow'; + +export { CategoryTableList, CategoryTableRow }; diff --git a/src/config.ts b/src/config.ts index 99c03cf..cf64b60 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ import { MdOutlineVideoLibrary, MdOutlineGroup, MdOutlineAccountCircle, + MdOutlineCategory, } from 'react-icons/md'; import { IAppConfig } from './interfaces'; import packageJson from '../package.json'; @@ -33,6 +34,13 @@ const config: IAppConfig = { icon: MdOutlineVideoLibrary, roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER], }, + { + location: '/categories', + title: 'Categories', + public: false, + roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER], + icon: MdOutlineCategory, + }, { location: '/messages', title: 'Messages', public: true, icon: MdOutlineMessage }, { location: '/profile', title: 'Profile', public: true, icon: MdOutlineAccountCircle }, { diff --git a/src/hooks/queries/category/useFetchCategories.ts b/src/hooks/queries/category/useFetchCategories.ts new file mode 100644 index 0000000..38967ba --- /dev/null +++ b/src/hooks/queries/category/useFetchCategories.ts @@ -0,0 +1,29 @@ +import useAsync from '@educt/hooks/useAsync'; +import { ICategory } from '@educt/interfaces'; +import { CategoryServiceInstance } from '@educt/services'; +import { useEffect } from 'react'; +import { useErrorHandler } from 'react-error-boundary'; + +const useFetchCategories = () => { + const handleError = useErrorHandler(); + + const fetchCategories = async () => { + try { + const result = await CategoryServiceInstance.fetchAll(); + return result.data; + } catch (error) { + handleError(error); + return Promise.reject(error); + } + }; + + const { execute: fetch, ...state } = useAsync>(fetchCategories); + + useEffect(() => { + fetch(); + }, []); + + return { ...state }; +}; + +export { useFetchCategories }; diff --git a/src/hooks/queries/index.ts b/src/hooks/queries/index.ts index f120192..29fba6e 100644 --- a/src/hooks/queries/index.ts +++ b/src/hooks/queries/index.ts @@ -29,6 +29,11 @@ import { useDeleteUser } from './user/useDeleteUser'; import { useUpdateUser } from './user/useUpdateUser'; import { useUpdateUserEmail } from './user/useUpdateUserEmail'; +/** + * Category + */ +import { useFetchCategories } from './category/useFetchCategories'; + export { useLogin, useLogout, @@ -45,4 +50,5 @@ export { useDeleteUser, useUpdateUser, useUpdateUserEmail, + useFetchCategories, }; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 91675ab..45f73d7 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -75,6 +75,8 @@ export interface ICategory { title: string; description: string; color?: ColorType | undefined | null; + created_at: string; + updated_at: string; } /** diff --git a/src/pages/categories/categories.tsx b/src/pages/categories/categories.tsx new file mode 100644 index 0000000..676047c --- /dev/null +++ b/src/pages/categories/categories.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { IPageProps } from '@educt/interfaces'; +import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements'; +import { CategoryTableList, CategoryTableRow } from '@educt/components/Tables/CategoryTableList'; +import { useFetchCategories } from '@educt/hooks/queries'; +import LoadingPage from '@educt/components/LoadingPage'; + +/** + * Categories page + */ +const CategoriesPage: React.FC = () => { + const { isLoading, data: categories } = useFetchCategories(); + + if (isLoading || categories === null) return ; + + return ( + + + + + + + ); +}; + +export default CategoriesPage; diff --git a/src/pages/courses/components/CourseList/CourseList.tsx b/src/pages/courses/components/CourseList/CourseList.tsx index 3151dab..85a172a 100644 --- a/src/pages/courses/components/CourseList/CourseList.tsx +++ b/src/pages/courses/components/CourseList/CourseList.tsx @@ -17,7 +17,7 @@ import LoadingList from '@educt/components/LoadingList'; /** * Hooks */ -import { useContext, useEffect } from 'react'; +import { useEffect } from 'react'; import { useSetCourseStatus } from '@educt/hooks/queries'; import { useRootStore } from '@educt/hooks/useRootStore'; import { useDisclosure } from '@chakra-ui/hooks'; @@ -25,7 +25,6 @@ import { useDisclosure } from '@chakra-ui/hooks'; /** * Contexts */ -import { CoursesPageContext } from '@educt/contexts'; import { ICourse } from '@educt/interfaces'; type CourseListPropsType = { @@ -41,7 +40,7 @@ const CourseList: React.FC = ({ render: Item }) => { const [deleting, setDeleting] = useState | null>(null); const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); const { setCourseStatus } = useSetCourseStatus(); - const { courses, isLoading, loadCourses } = courseStore; + const { courses, isLoading } = courseStore; const handleDeleteCourse = (course: Pick) => { setDeleting(course); diff --git a/src/pages/courses/edit/components/index.tsx b/src/pages/courses/edit/components/index.tsx index 594ca5c..ba99758 100644 --- a/src/pages/courses/edit/components/index.tsx +++ b/src/pages/courses/edit/components/index.tsx @@ -24,7 +24,7 @@ export const StatusButton: React.FC = props => { setStatus(newStatus); }; - const Components: { [k in keyof typeof CourseStatusEnum]: any } = { + const Components: { [k in keyof typeof CourseStatusEnum]: React.ReactElement } = { PUBLISHED: (