mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
refactor: render list
This commit is contained in:
parent
c92163accb
commit
5f7c27b060
24
src/components/PrevPageButton/PrevPageButton.tsx
Normal file
24
src/components/PrevPageButton/PrevPageButton.tsx
Normal 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;
|
||||
3
src/components/PrevPageButton/index.ts
Normal file
3
src/components/PrevPageButton/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import PrevPageButton from './PrevPageButton';
|
||||
|
||||
export default PrevPageButton;
|
||||
@ -16,11 +16,11 @@ import { CoursesPageContext } from '@educt/contexts';
|
||||
*/
|
||||
import { useContext } from 'react';
|
||||
|
||||
type CategoryItemProps = {
|
||||
export type CategoryItemPropsType = {
|
||||
category: ICategory;
|
||||
};
|
||||
|
||||
const CategoryItem: React.FC<CategoryItemProps> = ({ category }) => {
|
||||
const CategoryItem: React.FC<CategoryItemPropsType> = ({ category }) => {
|
||||
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
|
||||
|
||||
/**
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Box, Flex, Tag, TagLabel } from '@chakra-ui/react';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { ICategory } from '@educt/interfaces';
|
||||
import { CategoryItemPropsType } from './CategoryItem';
|
||||
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
import CategoryItem from './CategoryItem';
|
||||
import CategoryListLoading from './CategoryListLoading';
|
||||
|
||||
/**
|
||||
@ -25,14 +25,14 @@ import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
|
||||
type CategoryListPropsType = {
|
||||
categories: ICategory[] | null;
|
||||
isLoading: boolean;
|
||||
render: React.FC<CategoryItemPropsType>;
|
||||
};
|
||||
|
||||
const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }) => {
|
||||
const CategoryList: React.FC<CategoryListPropsType> = ({ render: Item }) => {
|
||||
const { categoryStore } = useRootStore();
|
||||
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
|
||||
const handleError = useErrorHandler();
|
||||
const { categories, isLoading } = categoryStore;
|
||||
|
||||
/**
|
||||
* Fetch handler
|
||||
@ -41,9 +41,10 @@ const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }
|
||||
categoryStore.loadCategories().catch(error => handleError(error));
|
||||
}, [categoryStore, handleError]);
|
||||
|
||||
if (isLoading || categories === null) {
|
||||
return <CategoryListLoading />;
|
||||
}
|
||||
/**
|
||||
* Loading
|
||||
*/
|
||||
if (categories === null || isLoading) return <CategoryListLoading />;
|
||||
|
||||
return (
|
||||
<Box mb='5'>
|
||||
@ -58,13 +59,13 @@ const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }
|
||||
>
|
||||
<TagLabel>All categories</TagLabel>
|
||||
</Tag>
|
||||
{/* Render items */}
|
||||
{categories.map((category, i) => (
|
||||
<CategoryItem key={category.id} category={category} />
|
||||
|
||||
{categories.map(category => (
|
||||
<Item key={category.id} category={category} />
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryList;
|
||||
export default observer(CategoryList);
|
||||
|
||||
@ -24,7 +24,7 @@ import CourseStatusBadge from '@educt/components/CourseStatusBadge';
|
||||
import { useColorMode } from '@chakra-ui/color-mode';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
|
||||
type CourseItemPropsType = {
|
||||
export type CourseItemPropsType = {
|
||||
course: Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
|
||||
onDelete: (course: Pick<ICourse, 'id' | 'title'>) => void;
|
||||
onSetStatus: (id: string, status: CourseStatusEnum) => Promise<void>;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Box, Grid, Text } from '@chakra-ui/react';
|
||||
|
||||
/**
|
||||
@ -10,7 +11,7 @@ import { CourseStatusEnum } from '@educt/enums';
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
import CourseItem from './CourseItem';
|
||||
import CourseItem, { CourseItemPropsType } from './CourseItem';
|
||||
import CourseListLoading from './CourseListLoading';
|
||||
import DeleteCourseDialog from '../DeleteCourseDialog';
|
||||
|
||||
@ -29,11 +30,10 @@ import { useErrorHandler } from 'react-error-boundary';
|
||||
import { CoursesPageContext } from '@educt/contexts';
|
||||
|
||||
type CourseListPropsType = {
|
||||
courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null;
|
||||
isLoading: boolean;
|
||||
render: React.FC<CourseItemPropsType>;
|
||||
};
|
||||
|
||||
const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
||||
const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
|
||||
const {
|
||||
userStore: { me },
|
||||
courseStore,
|
||||
@ -42,6 +42,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
||||
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
|
||||
const handleError = useErrorHandler();
|
||||
const toast = useToast();
|
||||
const { courses, isLoading } = courseStore;
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<>
|
||||
{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'>
|
||||
<Text color='gray.500'>There are no courses in this category {courseStatus && 'or with this status'}.</Text>
|
||||
</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);
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Flex, Box, Heading, Text, Button } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
|
||||
@ -26,6 +25,8 @@ import { CoursesPageContextProvider } from '@educt/providers';
|
||||
*/
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useHistory } from 'react-router';
|
||||
import CategoryItem from './components/CategoryList/CategoryItem';
|
||||
import CourseItem from './components/CourseList/CourseItem';
|
||||
|
||||
/**
|
||||
* Courses page
|
||||
@ -33,8 +34,6 @@ import { useHistory } from 'react-router';
|
||||
const CoursesPage: React.FC<IPageProps> = () => {
|
||||
const {
|
||||
userStore: { me },
|
||||
categoryStore,
|
||||
courseStore,
|
||||
} = useRootStore();
|
||||
const history = useHistory();
|
||||
|
||||
@ -62,8 +61,8 @@ const CoursesPage: React.FC<IPageProps> = () => {
|
||||
<Box mt='7'>
|
||||
{(me.isAdmin || me.isTeacher) && <CourseStatusTabs />}
|
||||
<Box mt='5'>
|
||||
<CategoryList categories={categoryStore.categories} isLoading={categoryStore.isLoading} />
|
||||
<CourseList courses={courseStore.courses} isLoading={courseStore.isLoading} />
|
||||
<CategoryList render={CategoryItem} />
|
||||
<CourseList render={CourseItem} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@ -71,4 +70,4 @@ const CoursesPage: React.FC<IPageProps> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(CoursesPage);
|
||||
export default CoursesPage;
|
||||
|
||||
@ -1,21 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Box, Heading, Text } from '@chakra-ui/react';
|
||||
import { Flex, Box, Heading, Text } from '@chakra-ui/react';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { IPageProps } from '@educt/interfaces';
|
||||
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
import PrevPageButton from '@educt/components/PrevPageButton';
|
||||
import CreateCourseForm from './components';
|
||||
|
||||
/**
|
||||
* Create course page
|
||||
*/
|
||||
const CreateCoursePage: React.FC<IPageProps> = ({ title }) => {
|
||||
const CreateCoursePage: React.FC<IPageProps> = () => {
|
||||
return (
|
||||
<Box maxW='1200px'>
|
||||
<Box>
|
||||
<Flex alignItems='center'>
|
||||
<PrevPageButton prevPage='/courses' />
|
||||
<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 mt='6'>
|
||||
<CreateCourseForm />
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||
|
||||
/**
|
||||
* Types
|
||||
@ -22,9 +21,7 @@ import ConfirmEmailContainer from './containers/ConfirmEmailContainer';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useHistory } from 'react-router';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
/**
|
||||
* Contexts
|
||||
@ -35,6 +32,7 @@ import { ChangeEmailPageContext } from '@educt/contexts';
|
||||
* Providers
|
||||
*/
|
||||
import { ChangeEmailPageContextProvider } from '@educt/providers';
|
||||
import PrevPageButton from '@educt/components/PrevPageButton';
|
||||
|
||||
/**
|
||||
* Change email page
|
||||
@ -43,7 +41,6 @@ const ChangeEmailPage: React.FC<IPageProps> = () => {
|
||||
const {
|
||||
userStore: { me },
|
||||
} = useRootStore();
|
||||
const history = useHistory();
|
||||
|
||||
if (me === null) return <LoadingPage />;
|
||||
|
||||
@ -55,13 +52,7 @@ const ChangeEmailPage: React.FC<IPageProps> = () => {
|
||||
{!isCodeSent ? (
|
||||
<Box maxW='700px'>
|
||||
<Flex alignItems='center'>
|
||||
<IconButton
|
||||
aria-label='Back'
|
||||
borderRadius='full'
|
||||
icon={<ChevronLeftIcon />}
|
||||
onClick={() => history.push('/profile')}
|
||||
mr='5'
|
||||
/>
|
||||
<PrevPageButton prevPage='/profile' />
|
||||
<Heading as='h1'>Edit email address</Heading>
|
||||
</Flex>
|
||||
<Box mt='3'>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react';
|
||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Box, Heading, Flex, Text } from '@chakra-ui/react';
|
||||
|
||||
/**
|
||||
* Types
|
||||
@ -11,14 +11,13 @@ import { IPageProps } from '@educt/interfaces';
|
||||
* Components
|
||||
*/
|
||||
import UpdatePasswordForm from './components/UpdatePasswordForm';
|
||||
import PrevPageButton from '@educt/components/PrevPageButton';
|
||||
import LoadingPage from '@educt/components/LoadingPage';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
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
|
||||
@ -27,20 +26,13 @@ const ChangePasswordPage: React.FC<IPageProps> = () => {
|
||||
const {
|
||||
userStore: { me },
|
||||
} = useRootStore();
|
||||
const history = useHistory();
|
||||
|
||||
if (me === null) return <LoadingPage />;
|
||||
|
||||
return (
|
||||
<Box maxW='700px'>
|
||||
<Flex alignItems='center'>
|
||||
<IconButton
|
||||
aria-label='Profile page'
|
||||
borderRadius='full'
|
||||
icon={<ChevronLeftIcon />}
|
||||
onClick={() => history.push('/profile')}
|
||||
mr='5'
|
||||
/>
|
||||
<PrevPageButton prevPage='/profile' />
|
||||
<Heading as='h1'>Update password</Heading>
|
||||
</Flex>
|
||||
<Box mt='3'>
|
||||
|
||||
@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { MdSave } from 'react-icons/md';
|
||||
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 { yupResolver } from '@hookform/resolvers/yup';
|
||||
|
||||
@ -90,7 +90,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
|
||||
return (
|
||||
<Box>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='5'>
|
||||
<Box borderRadius='lg' borderWidth='1px' padding='5'>
|
||||
<Stack spacing='5px'>
|
||||
<FormControl id='phone_number'>
|
||||
<FormHelperText color='gray.500'>Phone number with country code</FormHelperText>
|
||||
|
||||
@ -83,7 +83,7 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
||||
<TabPanels mt='2'>
|
||||
<TabPanel>
|
||||
<form>
|
||||
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='5'>
|
||||
<Box borderRadius='lg' borderWidth='1px' padding='5'>
|
||||
<Stack spacing='5px'>
|
||||
<FormControl isDisabled>
|
||||
<FormHelperText color='gray.500'>First name</FormHelperText>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||
import * as helpres from '@educt/helpers';
|
||||
import { Box, Flex, Avatar, Text, IconButton, Link } from '@chakra-ui/react';
|
||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||
import UserBadge from '@educt/components/UserBadge';
|
||||
import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
|
||||
|
||||
/**
|
||||
* Types
|
||||
@ -10,11 +10,16 @@ import UserBadge from '@educt/components/UserBadge';
|
||||
import { IUser } from '@educt/interfaces';
|
||||
import { UserRoleEnum } from '@educt/enums';
|
||||
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
import UserBadge from '@educt/components/UserBadge';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
|
||||
import { useColorModeValue } from '@chakra-ui/react';
|
||||
|
||||
type UserItemPropsType = {
|
||||
user: IUser;
|
||||
@ -46,7 +51,7 @@ const UserItem: React.FC<UserItemPropsType> = ({ user, onEdit, onDelete }) => {
|
||||
})();
|
||||
|
||||
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' flexBasis='230px'>
|
||||
<Avatar name={user.fullname} size='sm' mr='3' />
|
||||
|
||||
@ -8,6 +8,7 @@ import { Button } from '@chakra-ui/button';
|
||||
*/
|
||||
import UserItem from './UserItem';
|
||||
import EditUserForm from '../EditUserForm';
|
||||
import DeleteUserDialog from '../DeleteUserDialog';
|
||||
|
||||
/**
|
||||
* Types
|
||||
@ -27,7 +28,6 @@ import { useContext } from 'react';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useDisclosure } from '@chakra-ui/hooks';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import DeleteUserDialog from '../DeleteUserDialog';
|
||||
|
||||
type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta };
|
||||
|
||||
@ -85,7 +85,6 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
|
||||
const onEditUser = (user: IUser) => {
|
||||
setEditingUser(user);
|
||||
onOpenEditModal();
|
||||
console.log(`[LOG]: Editing user "${user.id}"`);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -95,7 +94,6 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
|
||||
const onDeleteUser = (user: IUser) => {
|
||||
setDeletingUser(user);
|
||||
onOpenDeleteDialog();
|
||||
console.log(`[LOG]: Alert delete "${user.id}"`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user