refactor: render list

This commit is contained in:
Sergey Yarkov 2021-11-02 23:51:29 +03:00
parent c92163accb
commit 5f7c27b060
14 changed files with 108 additions and 82 deletions

View File

@ -0,0 +1,24 @@
import React from 'react';
import { IconButton } from '@chakra-ui/react';
import { ChevronLeftIcon } from '@chakra-ui/icons';
import { useHistory } from 'react-router';
type PrevPageButtonProps = {
prevPage: string;
};
const PrevPageButton: React.FC<PrevPageButtonProps> = ({ prevPage }) => {
const history = useHistory();
return (
<IconButton
aria-label='Back'
borderRadius='full'
icon={<ChevronLeftIcon />}
onClick={() => history.push(prevPage)}
mr='5'
/>
);
};
export default PrevPageButton;

View File

@ -0,0 +1,3 @@
import PrevPageButton from './PrevPageButton';
export default PrevPageButton;

View File

@ -16,11 +16,11 @@ import { CoursesPageContext } from '@educt/contexts';
*/ */
import { useContext } from 'react'; import { useContext } from 'react';
type CategoryItemProps = { export type CategoryItemPropsType = {
category: ICategory; category: ICategory;
}; };
const CategoryItem: React.FC<CategoryItemProps> = ({ category }) => { const CategoryItem: React.FC<CategoryItemPropsType> = ({ category }) => {
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext); const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
/** /**

View File

@ -1,15 +1,15 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react';
import { Box, Flex, Tag, TagLabel } from '@chakra-ui/react'; import { Box, Flex, Tag, TagLabel } from '@chakra-ui/react';
/** /**
* Types * Types
*/ */
import { ICategory } from '@educt/interfaces'; import { CategoryItemPropsType } from './CategoryItem';
/** /**
* Components * Components
*/ */
import CategoryItem from './CategoryItem';
import CategoryListLoading from './CategoryListLoading'; import CategoryListLoading from './CategoryListLoading';
/** /**
@ -25,14 +25,14 @@ import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
type CategoryListPropsType = { type CategoryListPropsType = {
categories: ICategory[] | null; render: React.FC<CategoryItemPropsType>;
isLoading: boolean;
}; };
const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }) => { const CategoryList: React.FC<CategoryListPropsType> = ({ render: Item }) => {
const { categoryStore } = useRootStore(); const { categoryStore } = useRootStore();
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext); const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const { categories, isLoading } = categoryStore;
/** /**
* Fetch handler * Fetch handler
@ -41,9 +41,10 @@ const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }
categoryStore.loadCategories().catch(error => handleError(error)); categoryStore.loadCategories().catch(error => handleError(error));
}, [categoryStore, handleError]); }, [categoryStore, handleError]);
if (isLoading || categories === null) { /**
return <CategoryListLoading />; * Loading
} */
if (categories === null || isLoading) return <CategoryListLoading />;
return ( return (
<Box mb='5'> <Box mb='5'>
@ -58,13 +59,13 @@ const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }
> >
<TagLabel>All categories</TagLabel> <TagLabel>All categories</TagLabel>
</Tag> </Tag>
{/* Render items */}
{categories.map((category, i) => ( {categories.map(category => (
<CategoryItem key={category.id} category={category} /> <Item key={category.id} category={category} />
))} ))}
</Flex> </Flex>
</Box> </Box>
); );
}; };
export default CategoryList; export default observer(CategoryList);

View File

@ -24,7 +24,7 @@ import CourseStatusBadge from '@educt/components/CourseStatusBadge';
import { useColorMode } from '@chakra-ui/color-mode'; import { useColorMode } from '@chakra-ui/color-mode';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
type CourseItemPropsType = { export type CourseItemPropsType = {
course: Omit<ICourse, 'teacher' | 'students' | 'lessons'>; course: Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
onDelete: (course: Pick<ICourse, 'id' | 'title'>) => void; onDelete: (course: Pick<ICourse, 'id' | 'title'>) => void;
onSetStatus: (id: string, status: CourseStatusEnum) => Promise<void>; onSetStatus: (id: string, status: CourseStatusEnum) => Promise<void>;

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react';
import { Box, Grid, Text } from '@chakra-ui/react'; import { Box, Grid, Text } from '@chakra-ui/react';
/** /**
@ -10,7 +11,7 @@ import { CourseStatusEnum } from '@educt/enums';
/** /**
* Components * Components
*/ */
import CourseItem from './CourseItem'; import CourseItem, { CourseItemPropsType } from './CourseItem';
import CourseListLoading from './CourseListLoading'; import CourseListLoading from './CourseListLoading';
import DeleteCourseDialog from '../DeleteCourseDialog'; import DeleteCourseDialog from '../DeleteCourseDialog';
@ -29,11 +30,10 @@ import { useErrorHandler } from 'react-error-boundary';
import { CoursesPageContext } from '@educt/contexts'; import { CoursesPageContext } from '@educt/contexts';
type CourseListPropsType = { type CourseListPropsType = {
courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null; render: React.FC<CourseItemPropsType>;
isLoading: boolean;
}; };
const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => { const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
const { const {
userStore: { me }, userStore: { me },
courseStore, courseStore,
@ -42,6 +42,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const toast = useToast(); const toast = useToast();
const { courses, isLoading } = courseStore;
/** /**
* Fetch courses handler * Fetch courses handler
@ -80,27 +81,31 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
} }
}; };
if (isLoading || courses === null) { /**
return <CourseListLoading />; * Loading
} */
if (courses === null || isLoading) return <CourseListLoading />;
/**
* No courses
*/
if (courses.length === 0)
return ( return (
<>
{deletingCourse && <DeleteCourseDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
{courses.length !== 0 ? (
<Grid templateColumns={{ sm: 'repeat(auto-fill, minmax(400px, 1fr))' }} gap='4'>
{/* Render items */}
{courses.map(course => (
<CourseItem key={course.id} course={course} onDelete={onDeleteCourse} onSetStatus={onSetStatus} />
))}
</Grid>
) : (
<Box textAlign='center' mt='10' userSelect='none'> <Box textAlign='center' mt='10' userSelect='none'>
<Text color='gray.500'>There are no courses in this category {courseStatus && 'or with this status'}.</Text> <Text color='gray.500'>There are no courses in this category {courseStatus && 'or with this status'}.</Text>
</Box> </Box>
)} );
</>
return (
<Box>
{deletingCourse && <DeleteCourseDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
<Grid templateColumns={{ sm: 'repeat(auto-fill, minmax(400px, 1fr))' }} gap='4'>
{courses.map(course => (
<Item key={course.id} course={course} onDelete={onDeleteCourse} onSetStatus={onSetStatus} />
))}
</Grid>
</Box>
); );
}; };
export default CourseList; export default observer(CourseList);

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react';
import { Flex, Box, Heading, Text, Button } from '@chakra-ui/react'; import { Flex, Box, Heading, Text, Button } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons'; import { AddIcon } from '@chakra-ui/icons';
@ -26,6 +25,8 @@ import { CoursesPageContextProvider } from '@educt/providers';
*/ */
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import CategoryItem from './components/CategoryList/CategoryItem';
import CourseItem from './components/CourseList/CourseItem';
/** /**
* Courses page * Courses page
@ -33,8 +34,6 @@ import { useHistory } from 'react-router';
const CoursesPage: React.FC<IPageProps> = () => { const CoursesPage: React.FC<IPageProps> = () => {
const { const {
userStore: { me }, userStore: { me },
categoryStore,
courseStore,
} = useRootStore(); } = useRootStore();
const history = useHistory(); const history = useHistory();
@ -62,8 +61,8 @@ const CoursesPage: React.FC<IPageProps> = () => {
<Box mt='7'> <Box mt='7'>
{(me.isAdmin || me.isTeacher) && <CourseStatusTabs />} {(me.isAdmin || me.isTeacher) && <CourseStatusTabs />}
<Box mt='5'> <Box mt='5'>
<CategoryList categories={categoryStore.categories} isLoading={categoryStore.isLoading} /> <CategoryList render={CategoryItem} />
<CourseList courses={courseStore.courses} isLoading={courseStore.isLoading} /> <CourseList render={CourseItem} />
</Box> </Box>
</Box> </Box>
</Box> </Box>
@ -71,4 +70,4 @@ const CoursesPage: React.FC<IPageProps> = () => {
); );
}; };
export default observer(CoursesPage); export default CoursesPage;

View File

@ -1,21 +1,29 @@
import React from 'react'; import React from 'react';
import { Box, Heading, Text } from '@chakra-ui/react'; import { Flex, Box, Heading, Text } from '@chakra-ui/react';
/** /**
* Types * Types
*/ */
import { IPageProps } from '@educt/interfaces'; import { IPageProps } from '@educt/interfaces';
/**
* Components
*/
import PrevPageButton from '@educt/components/PrevPageButton';
import CreateCourseForm from './components'; import CreateCourseForm from './components';
/** /**
* Create course page * Create course page
*/ */
const CreateCoursePage: React.FC<IPageProps> = ({ title }) => { const CreateCoursePage: React.FC<IPageProps> = () => {
return ( return (
<Box maxW='1200px'> <Box maxW='1200px'>
<Box> <Box>
<Flex alignItems='center'>
<PrevPageButton prevPage='/courses' />
<Heading as='h1'>Create course</Heading> <Heading as='h1'>Create course</Heading>
<Text mt='2'>Fill in the required fields to create a course</Text> </Flex>
<Text mt='3'>Fill in the required fields to create a course</Text>
</Box> </Box>
<Box mt='6'> <Box mt='6'>
<CreateCourseForm /> <CreateCourseForm />

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react';
import { Box, Flex, Heading, Text } from '@chakra-ui/layout'; import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
import { IconButton } from '@chakra-ui/button';
import { ChevronLeftIcon } from '@chakra-ui/icons';
/** /**
* Types * Types
@ -22,9 +21,7 @@ import ConfirmEmailContainer from './containers/ConfirmEmailContainer';
/** /**
* Hooks * Hooks
*/ */
import { useHistory } from 'react-router';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { observer } from 'mobx-react';
/** /**
* Contexts * Contexts
@ -35,6 +32,7 @@ import { ChangeEmailPageContext } from '@educt/contexts';
* Providers * Providers
*/ */
import { ChangeEmailPageContextProvider } from '@educt/providers'; import { ChangeEmailPageContextProvider } from '@educt/providers';
import PrevPageButton from '@educt/components/PrevPageButton';
/** /**
* Change email page * Change email page
@ -43,7 +41,6 @@ const ChangeEmailPage: React.FC<IPageProps> = () => {
const { const {
userStore: { me }, userStore: { me },
} = useRootStore(); } = useRootStore();
const history = useHistory();
if (me === null) return <LoadingPage />; if (me === null) return <LoadingPage />;
@ -55,13 +52,7 @@ const ChangeEmailPage: React.FC<IPageProps> = () => {
{!isCodeSent ? ( {!isCodeSent ? (
<Box maxW='700px'> <Box maxW='700px'>
<Flex alignItems='center'> <Flex alignItems='center'>
<IconButton <PrevPageButton prevPage='/profile' />
aria-label='Back'
borderRadius='full'
icon={<ChevronLeftIcon />}
onClick={() => history.push('/profile')}
mr='5'
/>
<Heading as='h1'>Edit email address</Heading> <Heading as='h1'>Edit email address</Heading>
</Flex> </Flex>
<Box mt='3'> <Box mt='3'>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react'; import { observer } from 'mobx-react';
import { ChevronLeftIcon } from '@chakra-ui/icons'; import { Box, Heading, Flex, Text } from '@chakra-ui/react';
/** /**
* Types * Types
@ -11,14 +11,13 @@ import { IPageProps } from '@educt/interfaces';
* Components * Components
*/ */
import UpdatePasswordForm from './components/UpdatePasswordForm'; import UpdatePasswordForm from './components/UpdatePasswordForm';
import PrevPageButton from '@educt/components/PrevPageButton';
import LoadingPage from '@educt/components/LoadingPage';
/** /**
* Hooks * Hooks
*/ */
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useHistory } from 'react-router';
import LoadingPage from '@educt/components/LoadingPage';
import { observer } from 'mobx-react';
/** /**
* Change password page * Change password page
@ -27,20 +26,13 @@ const ChangePasswordPage: React.FC<IPageProps> = () => {
const { const {
userStore: { me }, userStore: { me },
} = useRootStore(); } = useRootStore();
const history = useHistory();
if (me === null) return <LoadingPage />; if (me === null) return <LoadingPage />;
return ( return (
<Box maxW='700px'> <Box maxW='700px'>
<Flex alignItems='center'> <Flex alignItems='center'>
<IconButton <PrevPageButton prevPage='/profile' />
aria-label='Profile page'
borderRadius='full'
icon={<ChevronLeftIcon />}
onClick={() => history.push('/profile')}
mr='5'
/>
<Heading as='h1'>Update password</Heading> <Heading as='h1'>Update password</Heading>
</Flex> </Flex>
<Box mt='3'> <Box mt='3'>

View File

@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
import { SubmitHandler, useForm } from 'react-hook-form'; import { SubmitHandler, useForm } from 'react-hook-form';
import { MdSave } from 'react-icons/md'; import { MdSave } from 'react-icons/md';
import { Text, Input, Button } from '@chakra-ui/react'; import { Text, Input, Button } from '@chakra-ui/react';
import { Box, Heading, Stack } from '@chakra-ui/layout'; import { Box, Stack } from '@chakra-ui/layout';
import { FormControl, FormHelperText } from '@chakra-ui/form-control'; import { FormControl, FormHelperText } from '@chakra-ui/form-control';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
@ -90,7 +90,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
return ( return (
<Box> <Box>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='5'> <Box borderRadius='lg' borderWidth='1px' padding='5'>
<Stack spacing='5px'> <Stack spacing='5px'>
<FormControl id='phone_number'> <FormControl id='phone_number'>
<FormHelperText color='gray.500'>Phone number with country code</FormHelperText> <FormHelperText color='gray.500'>Phone number with country code</FormHelperText>

View File

@ -83,7 +83,7 @@ const ProfilePage: React.FC<IPageProps> = () => {
<TabPanels mt='2'> <TabPanels mt='2'>
<TabPanel> <TabPanel>
<form> <form>
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='5'> <Box borderRadius='lg' borderWidth='1px' padding='5'>
<Stack spacing='5px'> <Stack spacing='5px'>
<FormControl isDisabled> <FormControl isDisabled>
<FormHelperText color='gray.500'>First name</FormHelperText> <FormHelperText color='gray.500'>First name</FormHelperText>

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Link as ReactRouterLink } from 'react-router-dom';
import * as helpres from '@educt/helpers'; import * as helpres from '@educt/helpers';
import { Box, Flex, Avatar, Text, IconButton, Link } from '@chakra-ui/react'; import { Box, Flex, Avatar, Text, IconButton, Link } from '@chakra-ui/react';
import { Link as ReactRouterLink } from 'react-router-dom'; import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
import UserBadge from '@educt/components/UserBadge';
/** /**
* Types * Types
@ -10,11 +10,16 @@ import UserBadge from '@educt/components/UserBadge';
import { IUser } from '@educt/interfaces'; import { IUser } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums'; import { UserRoleEnum } from '@educt/enums';
/**
* Components
*/
import UserBadge from '@educt/components/UserBadge';
/** /**
* Hooks * Hooks
*/ */
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { DeleteIcon, EditIcon } from '@chakra-ui/icons'; import { useColorModeValue } from '@chakra-ui/react';
type UserItemPropsType = { type UserItemPropsType = {
user: IUser; user: IUser;
@ -46,7 +51,7 @@ const UserItem: React.FC<UserItemPropsType> = ({ user, onEdit, onDelete }) => {
})(); })();
return ( return (
<Box borderWidth='1px' borderRadius='lg' w='full' p='3'> <Box borderBottomWidth='1px' borderRadius='lg' w='full' p='3' bg={useColorModeValue('gray.50', 'gray.700')}>
<Flex alignItems='center' justifyContent='space-between' sx={{ gap: '20px' }}> <Flex alignItems='center' justifyContent='space-between' sx={{ gap: '20px' }}>
<Flex alignItems='center' flexBasis='230px'> <Flex alignItems='center' flexBasis='230px'>
<Avatar name={user.fullname} size='sm' mr='3' /> <Avatar name={user.fullname} size='sm' mr='3' />

View File

@ -8,6 +8,7 @@ import { Button } from '@chakra-ui/button';
*/ */
import UserItem from './UserItem'; import UserItem from './UserItem';
import EditUserForm from '../EditUserForm'; import EditUserForm from '../EditUserForm';
import DeleteUserDialog from '../DeleteUserDialog';
/** /**
* Types * Types
@ -27,7 +28,6 @@ import { useContext } from 'react';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks'; import { useDisclosure } from '@chakra-ui/hooks';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import DeleteUserDialog from '../DeleteUserDialog';
type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta }; type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta };
@ -85,7 +85,6 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
const onEditUser = (user: IUser) => { const onEditUser = (user: IUser) => {
setEditingUser(user); setEditingUser(user);
onOpenEditModal(); onOpenEditModal();
console.log(`[LOG]: Editing user "${user.id}"`);
}; };
/** /**
@ -95,7 +94,6 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
const onDeleteUser = (user: IUser) => { const onDeleteUser = (user: IUser) => {
setDeletingUser(user); setDeletingUser(user);
onOpenDeleteDialog(); onOpenDeleteDialog();
console.log(`[LOG]: Alert delete "${user.id}"`);
}; };
return ( return (