mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: course status badge
This commit is contained in:
parent
962e43c81c
commit
02b2a4ee43
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
|
|||||||
30
src/components/CourseStatusBadge/CourseStatusBadge.tsx
Normal file
30
src/components/CourseStatusBadge/CourseStatusBadge.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Badge } from '@chakra-ui/layout';
|
||||||
|
import { CourseStatusEnum } from 'enums';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type CourseStatusBadgeProps = {
|
||||||
|
status: CourseStatusEnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CourseStatusBadge: React.FC<CourseStatusBadgeProps> = ({ status }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(() => {
|
||||||
|
switch (status) {
|
||||||
|
case CourseStatusEnum.PUBLISHED:
|
||||||
|
return (
|
||||||
|
<Badge variant='subtle' colorScheme='green'>
|
||||||
|
Published
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
case CourseStatusEnum.DRAFT:
|
||||||
|
return <Badge variant='subtle'>Draft</Badge>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CourseStatusBadge;
|
||||||
3
src/components/CourseStatusBadge/index.ts
Normal file
3
src/components/CourseStatusBadge/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import CourseStatusBadge from './CourseStatusBadge';
|
||||||
|
|
||||||
|
export default CourseStatusBadge;
|
||||||
@ -4,6 +4,11 @@ import { CoursesPageContext } from 'contexts';
|
|||||||
import CategoryItem from './CategoryItem';
|
import CategoryItem from './CategoryItem';
|
||||||
import { ICategory } from 'interfaces';
|
import { ICategory } from 'interfaces';
|
||||||
import CategoryListLoading from './CategoryListLoading';
|
import CategoryListLoading from './CategoryListLoading';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
|
|
||||||
type CategoryListPropsType = {
|
type CategoryListPropsType = {
|
||||||
@ -12,16 +17,13 @@ type CategoryListPropsType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }) => {
|
const CategoryList: React.FC<CategoryListPropsType> = ({ categories, isLoading }) => {
|
||||||
const { courseStore } = useRootStore();
|
const { categoryStore } = useRootStore();
|
||||||
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
|
const { selectedCategory, setSelectedCategory } = useContext(CoursesPageContext);
|
||||||
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle on changing category
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(`[LOG:] Selected category: ${selectedCategory?.title}`);
|
categoryStore.loadCategories().catch(error => handleError(error));
|
||||||
courseStore.loadCourses({ category_id: selectedCategory?.id });
|
}, [categoryStore, handleError]);
|
||||||
}, [courseStore, selectedCategory]);
|
|
||||||
|
|
||||||
if (isLoading || categories === null) {
|
if (isLoading || categories === null) {
|
||||||
return <CategoryListLoading />;
|
return <CategoryListLoading />;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import { ICourse } from 'interfaces';
|
|||||||
*/
|
*/
|
||||||
import { useColorMode } from '@chakra-ui/color-mode';
|
import { useColorMode } from '@chakra-ui/color-mode';
|
||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
|
import CourseStatusBadge from 'components/CourseStatusBadge';
|
||||||
|
|
||||||
type CourseItemPropsType = {
|
type CourseItemPropsType = {
|
||||||
course: Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
|
course: Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
|
||||||
@ -97,7 +98,10 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box p='0 10px' mt='10px' pb='20px'>
|
<Box p='0 10px' mt='10px' pb='20px'>
|
||||||
<Badge colorScheme='blue'>{course.category.title}</Badge>
|
<Flex justifyContent='space-between'>
|
||||||
|
<Badge colorScheme='blue'>{course.category.title}</Badge>
|
||||||
|
{(me.isAdmin || me.isTeacher) && <CourseStatusBadge status={course.status} />}
|
||||||
|
</Flex>
|
||||||
<Heading as='h2' fontSize='xl' mt='5px'>
|
<Heading as='h2' fontSize='xl' mt='5px'>
|
||||||
{course.title}
|
{course.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { Grid } from '@chakra-ui/react';
|
import { Box, Grid, Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
@ -12,33 +12,58 @@ import { ICourse } from 'interfaces';
|
|||||||
import CourseItem from './CourseItem';
|
import CourseItem from './CourseItem';
|
||||||
import CourseListLoading from './CourseListLoading';
|
import CourseListLoading from './CourseListLoading';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contexts
|
||||||
|
*/
|
||||||
|
import { CoursesPageContext } from 'contexts';
|
||||||
|
|
||||||
type CourseListPropsType = {
|
type CourseListPropsType = {
|
||||||
courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null;
|
courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
||||||
|
const { courseStore } = useRootStore();
|
||||||
|
const { selectedCategory } = useContext(CoursesPageContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
courseStore.loadCourses({ category_id: selectedCategory?.id });
|
||||||
|
}, [courseStore, selectedCategory]);
|
||||||
|
|
||||||
if (isLoading || courses === null) {
|
if (isLoading || courses === null) {
|
||||||
return <CourseListLoading />;
|
return <CourseListLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<>
|
||||||
templateColumns='repeat(3, 1fr)'
|
{courses.length !== 0 ? (
|
||||||
gap='6'
|
<Grid
|
||||||
sx={{
|
templateColumns='repeat(3, 1fr)'
|
||||||
'@media (max-width: 1280px)': {
|
gap='6'
|
||||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
sx={{
|
||||||
},
|
'@media (max-width: 1280px)': {
|
||||||
'@media (max-width: 768px)': {
|
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||||
gridTemplateColumns: 'repeat(1, 1fr)',
|
},
|
||||||
},
|
'@media (max-width: 768px)': {
|
||||||
}}
|
gridTemplateColumns: 'repeat(1, 1fr)',
|
||||||
>
|
},
|
||||||
{courses.map(course => {
|
}}
|
||||||
return <CourseItem key={course.id} course={course} />;
|
>
|
||||||
})}
|
{courses.map(course => (
|
||||||
</Grid>
|
<CourseItem key={course.id} course={course} />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Box textAlign='center' mt='10' userSelect='none'>
|
||||||
|
<Text color='gray.500'>There are no courses in this category.</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { Flex, Tabs, TabList, Tab, Box, Heading, Text, Button } from '@chakra-ui/react';
|
import { Flex, Tabs, TabList, Tab, Box, Heading, Text, Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
@ -10,14 +10,13 @@ import { AddIcon } from '@chakra-ui/icons';
|
|||||||
*/
|
*/
|
||||||
import CourseList from './components/CourseList';
|
import CourseList from './components/CourseList';
|
||||||
import CategoryList from './components/CategoryList';
|
import CategoryList from './components/CategoryList';
|
||||||
|
import LoadingPage from 'components/LoadingPage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
import { CoursesPageContextProvider } from 'providers';
|
import { CoursesPageContextProvider } from 'providers';
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
|
||||||
import LoadingPage from 'components/LoadingPage';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Courses page
|
* Courses page
|
||||||
@ -28,14 +27,6 @@ const CoursesPage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
categoryStore,
|
categoryStore,
|
||||||
courseStore,
|
courseStore,
|
||||||
} = useRootStore();
|
} = useRootStore();
|
||||||
const handleError = useErrorHandler();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (me !== null) {
|
|
||||||
categoryStore.loadCategories().catch(error => handleError(error));
|
|
||||||
// courseStore.loadCourses().catch(error => handleError(error));
|
|
||||||
}
|
|
||||||
}, [me, categoryStore, handleError]);
|
|
||||||
|
|
||||||
if (me === null) return <LoadingPage />;
|
if (me === null) return <LoadingPage />;
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormProps> = ({ contact
|
|||||||
<Box>
|
<Box>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Box boxShadow='sm' borderRadius='sm' borderWidth='1px' padding='20px'>
|
<Box boxShadow='sm' borderRadius='sm' borderWidth='1px' padding='20px'>
|
||||||
<Heading as='h3' size='lg'>
|
<Heading as='h3' size='md'>
|
||||||
My Contacts
|
My Contacts
|
||||||
</Heading>
|
</Heading>
|
||||||
<Stack spacing='5px' margin='20px 0'>
|
<Stack spacing='5px' margin='20px 0'>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box borderRadius='md' borderWidth='1px' padding='20px' boxShadow='sm'>
|
<Box borderRadius='md' borderWidth='1px' padding='20px' boxShadow='sm'>
|
||||||
<Heading as='h3' size='lg'>
|
<Heading as='h3' size='md'>
|
||||||
Account information
|
Account information
|
||||||
</Heading>
|
</Heading>
|
||||||
<Stack spacing='10px' mt='20px' divider={<StackDivider />}>
|
<Stack spacing='10px' mt='20px' divider={<StackDivider />}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user