diff --git a/src/components/icon-extension-with-title/IconExtensionWithTitle.styles.ts b/src/components/icon-extension-with-title/IconExtensionWithTitle.styles.ts index 07d1ffa9e..f60362bf1 100644 --- a/src/components/icon-extension-with-title/IconExtensionWithTitle.styles.ts +++ b/src/components/icon-extension-with-title/IconExtensionWithTitle.styles.ts @@ -1,23 +1,30 @@ import { TypographyVariantEnum } from '~/types' +const iconContainer = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '32px', + height: '32px', + mr: '20px' +} + export const styles = { container: { display: 'flex', alignItems: 'center' }, iconBox: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - minWidth: '40px', - height: '40px', + ...iconContainer, backgroundColor: 'basic.turquoise', borderRadius: '5px', - marginRight: '20px', color: 'basic.white', fontSize: '12px', typography: TypographyVariantEnum.Caption }, + svgBox: { + ...iconContainer + }, titleWithDescription: { title: { typography: TypographyVariantEnum.Subtitle2 diff --git a/src/components/icon-extension-with-title/IconExtensionWithTitle.tsx b/src/components/icon-extension-with-title/IconExtensionWithTitle.tsx index 7ef60b4d0..75c4450b3 100644 --- a/src/components/icon-extension-with-title/IconExtensionWithTitle.tsx +++ b/src/components/icon-extension-with-title/IconExtensionWithTitle.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { ReactElement, FC } from 'react' import { useTranslation } from 'react-i18next' import Box from '@mui/material/Box' @@ -11,12 +11,14 @@ interface IconExtensionWithTitleProps { title: string description?: string size?: number + icon?: ReactElement } const IconExtensionWithTitle: FC = ({ title, description, - size + size, + icon }) => { const { t } = useTranslation() @@ -29,7 +31,11 @@ const IconExtensionWithTitle: FC = ({ return ( - {fileExtension} + {icon ? ( + {icon} + ) : ( + {fileExtension} + )} ( - - ) - }, - { - label: 'myResourcesPage.attachments.size', - field: 'size', - calculatedCellValue: ( - item: Attachment, - { t }: AdditionalPropsInterface - ) => { - const { size, unit } = convertBytesToProperFormat(item.size) - return {size + ' ' + t(`common.${unit}`)} - } - }, - { - label: 'myResourcesPage.attachments.lastUpdate', - field: 'updatedAt', - calculatedCellValue: (item: Attachment) => - getFormattedDate({ date: item.updatedAt }) - } -] - -export const removeColumnRules: RemoveColumnRules = { - tablet: ['myResourcesPage.attachments.lastUpdate'] -} diff --git a/src/containers/add-attachments/AddAttachments.tsx b/src/containers/add-attachments/AddAttachments.tsx deleted file mode 100644 index 9c25e4bb3..000000000 --- a/src/containers/add-attachments/AddAttachments.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { FC, useCallback, useState } from 'react' - -import { useModalContext } from '~/context/modal-context' -import { useSnackBarContext } from '~/context/snackbar-context' -import { ResourceService } from '~/services/resource-service' -import useSort from '~/hooks/table/use-sort' -import useAxios from '~/hooks/use-axios' -import useBreakpoints from '~/hooks/use-breakpoints' -import AddResourceModal from '~/containers/my-resources/add-resource-modal/AddResourceModal' - -import { - columns, - initialSort, - removeColumnRules -} from '~/containers/add-attachments/AddAttachments.constants' -import { ajustColumns } from '~/utils/helper-functions' -import { defaultResponses, snackbarVariants } from '~/constants' -import { Attachment, ErrorResponse, ItemsWithCount } from '~/types' -interface AddAttachmentsProps { - attachments: Attachment[] - onAddAttachments: (attachments: Attachment[]) => void -} - -const AddAttachments: FC = ({ - attachments = [], - onAddAttachments -}) => { - const [selectedRows, setSelectedRows] = useState(attachments) - - const { closeModal } = useModalContext() - const { setAlert } = useSnackBarContext() - - const breakpoints = useBreakpoints() - const sortOptions = useSort({ - initialSort - }) - const { sort } = sortOptions - - const columnsToShow = ajustColumns( - breakpoints, - columns, - removeColumnRules - ) - - const getMyAttachments = useCallback( - () => - ResourceService.getAttachments({ - sort - }), - [sort] - ) - - const { - loading, - response, - fetchData: fetchDataAttachments - } = useAxios>({ - service: getMyAttachments, - defaultResponse: defaultResponses.itemsWithCount - }) - - const onRowClick = (item: Attachment) => { - if (selectedRows.find((attachment) => attachment._id === item._id)) { - setSelectedRows((prevSelectedRows) => - prevSelectedRows.filter((attachment) => attachment._id !== item._id) - ) - } else { - setSelectedRows((prevSelectedAttachments) => [ - ...prevSelectedAttachments, - item - ]) - } - } - - const onAddItems = () => { - onAddAttachments(selectedRows) - closeModal() - } - const getItems = useCallback( - (inputValue: string) => - response.items.filter((item) => { - const lowerCaseFileName = item.fileName.toLowerCase() - const lowerCaseInputValue = inputValue.toLocaleLowerCase() - const fileNameWithoutExtension = lowerCaseFileName - .split('.') - .slice(0, -1) - .join('.') - - return fileNameWithoutExtension.includes(lowerCaseInputValue) - }), - [response.items] - ) - - const createAttachments = useCallback( - (data?: FormData) => ResourceService.createAttachments(data), - [] - ) - - const onCreateAttachmentsError = (error: ErrorResponse) => { - setAlert({ - severity: snackbarVariants.error, - message: error ? `errors.${error.code}` : '' - }) - } - - const { fetchData: fetchCreateAttachment } = useAxios({ - service: createAttachments, - fetchOnMount: false, - defaultResponse: null, - onResponseError: onCreateAttachmentsError - }) - - const uploadItem = async (data: FormData) => { - await fetchCreateAttachment(data) - await fetchDataAttachments() - } - - const props = { - columns: columnsToShow, - sort: sortOptions, - selectedRows, - onAddItems, - data: { loading, getItems }, - onRowClick, - uploadItem, - resource: 'attachments' - } - - return {...props} /> -} - -export default AddAttachments diff --git a/src/containers/add-resources/AddAttachments.constants.tsx b/src/containers/add-resources/AddAttachments.constants.tsx new file mode 100644 index 000000000..4e638e475 --- /dev/null +++ b/src/containers/add-resources/AddAttachments.constants.tsx @@ -0,0 +1,50 @@ +import Typography from '@mui/material/Typography' + +import AppChip from '~/components/app-chip/AppChip' +import IconExtensionWithTitle from '~/components/icon-extension-with-title/IconExtensionWithTitle' + +import { getFormattedDate } from '~/utils/helper-functions' +import { styles } from '~/containers/add-resources/AddResources.styles' +import { + AdditionalPropsInterface, + Attachment, + RemoveColumnRules +} from '~/types' + +export const columns = [ + { + label: 'myResourcesPage.attachments.file', + field: 'fileName', + calculatedCellValue: (attachment: Attachment) => ( + + ) + }, + + { + label: 'myResourcesPage.categories.category', + field: 'category', + calculatedCellValue: ( + attachment: Attachment, + { t }: AdditionalPropsInterface + ) => + attachment.category ? ( + + {attachment.category.name} + + ) : ( + + {t('myResourcesPage.categories.noCategory')} + + ) + }, + { + label: 'myResourcesPage.attachments.lastUpdate', + field: 'updatedAt', + calculatedCellValue: (attachment: Attachment) => + getFormattedDate({ date: attachment.updatedAt }) + } +] + +export const removeColumnRules: RemoveColumnRules = { + tablet: ['myResourcesPage.attachments.lastUpdate'] +} diff --git a/src/containers/add-resources/AddLessons.constants.tsx b/src/containers/add-resources/AddLessons.constants.tsx new file mode 100644 index 000000000..a24f85451 --- /dev/null +++ b/src/containers/add-resources/AddLessons.constants.tsx @@ -0,0 +1,43 @@ +import ListAltIcon from '@mui/icons-material/ListAlt' +import Typography from '@mui/material/Typography' + +import AppChip from '~/components/app-chip/AppChip' +import IconExtensionWithTitle from '~/components/icon-extension-with-title/IconExtensionWithTitle' + +import { styles } from '~/containers/add-resources/AddResources.styles' +import { getFormattedDate } from '~/utils/helper-functions' +import { AdditionalPropsInterface, Lesson, RemoveColumnRules } from '~/types' + +export const columns = [ + { + label: 'myResourcesPage.lessons.title', + field: 'title', + calculatedCellValue: (lesson: Lesson) => ( + } title={lesson.title} /> + ) + }, + { + label: 'myResourcesPage.categories.category', + field: 'category', + calculatedCellValue: (lesson: Lesson, { t }: AdditionalPropsInterface) => + lesson.category ? ( + + {lesson.category.name} + + ) : ( + + {t('myResourcesPage.categories.noCategory')} + + ) + }, + { + label: 'myResourcesPage.lessons.lastUpdates', + field: 'updatedAt', + calculatedCellValue: (lesson: Lesson) => + getFormattedDate({ date: lesson.updatedAt }) + } +] + +export const removeColumnRules: RemoveColumnRules = { + tablet: ['myResourcesPage.lessons.lastUpdates'] +} diff --git a/src/containers/my-resources/add-questions/AddQuestions.constants.tsx b/src/containers/add-resources/AddQuestions.constants.tsx similarity index 70% rename from src/containers/my-resources/add-questions/AddQuestions.constants.tsx rename to src/containers/add-resources/AddQuestions.constants.tsx index 5e5854026..8a34b6fe5 100644 --- a/src/containers/my-resources/add-questions/AddQuestions.constants.tsx +++ b/src/containers/add-resources/AddQuestions.constants.tsx @@ -5,24 +5,23 @@ import AppChip from '~/components/app-chip/AppChip' import TitleWithDescription from '~/components/title-with-description/TitleWithDescription' import { getFormattedDate } from '~/utils/helper-functions' +import { styles } from '~/containers/add-resources/AddResources.styles' import { - ComponentEnum, Question, RemoveColumnRules, - SortEnum, TableColumn, - AdditionalPropsInterface + AdditionalPropsInterface, + ComponentEnum } from '~/types' -import { styles } from '~/containers/my-resources/add-questions/AddQuestions.styles' export const columns: TableColumn[] = [ { label: 'myResourcesPage.questions.title', field: 'title', - calculatedCellValue: (item: Question) => { + calculatedCellValue: (question: Question) => { return ( [] = [ sx={styles.questionTitle} > - {item.title} + {question.title} } /> @@ -39,10 +38,13 @@ export const columns: TableColumn[] = [ }, { label: 'myResourcesPage.categories.category', - calculatedCellValue: (item: Question, { t }: AdditionalPropsInterface) => - item.category ? ( + calculatedCellValue: ( + question: Question, + { t }: AdditionalPropsInterface + ) => + question.category ? ( - {item.category.name} + {question.category.name} ) : ( @@ -53,9 +55,9 @@ export const columns: TableColumn[] = [ { label: 'myResourcesPage.questions.updated', field: 'updatedAt', - calculatedCellValue: (item: Question) => ( + calculatedCellValue: (question: Question) => ( - {getFormattedDate({ date: item.updatedAt })} + {getFormattedDate({ date: question.updatedAt })} ) } @@ -64,5 +66,3 @@ export const columns: TableColumn[] = [ export const removeColumnRules: RemoveColumnRules = { tablet: ['myResourcesPage.questions.updated'] } - -export const initialSort = { order: SortEnum.Desc, orderBy: 'updatedAt' } diff --git a/src/containers/add-resources/AddQuizzes.constants.tsx b/src/containers/add-resources/AddQuizzes.constants.tsx new file mode 100644 index 000000000..b135333e6 --- /dev/null +++ b/src/containers/add-resources/AddQuizzes.constants.tsx @@ -0,0 +1,46 @@ +import NoteAltOutlinedIcon from '@mui/icons-material/NoteAltOutlined' +import Typography from '@mui/material/Typography' + +import AppChip from '~/components/app-chip/AppChip' +import IconExtensionWithTitle from '~/components/icon-extension-with-title/IconExtensionWithTitle' + +import { styles } from '~/containers/add-resources/AddResources.styles' +import { getFormattedDate } from '~/utils/helper-functions' +import { AdditionalPropsInterface, Quiz, RemoveColumnRules } from '~/types' + +export const columns = [ + { + label: 'myResourcesPage.quizzes.title', + field: 'title', + calculatedCellValue: (quiz: Quiz) => ( + } + title={quiz.title} + /> + ) + }, + { + label: 'myResourcesPage.categories.category', + field: 'category', + calculatedCellValue: (quiz: Quiz, { t }: AdditionalPropsInterface) => + quiz.category ? ( + + {quiz.category.name} + + ) : ( + + {t('myResourcesPage.categories.noCategory')} + + ) + }, + { + label: 'myResourcesPage.quizzes.updated', + field: 'updatedAt', + calculatedCellValue: (quiz: Quiz) => + getFormattedDate({ date: quiz.updatedAt }) + } +] + +export const removeColumnRules: RemoveColumnRules = { + tablet: ['myResourcesPage.quizzes.updated'] +} diff --git a/src/containers/add-resources/AddResources.constants.tsx b/src/containers/add-resources/AddResources.constants.tsx new file mode 100644 index 000000000..bbe07626c --- /dev/null +++ b/src/containers/add-resources/AddResources.constants.tsx @@ -0,0 +1,3 @@ +import { SortEnum } from '~/types' + +export const initialSort = { order: SortEnum.Desc, orderBy: 'updatedAt' } diff --git a/src/containers/my-resources/add-questions/AddQuestions.styles.ts b/src/containers/add-resources/AddResources.styles.ts similarity index 100% rename from src/containers/my-resources/add-questions/AddQuestions.styles.ts rename to src/containers/add-resources/AddResources.styles.ts diff --git a/src/containers/my-resources/add-questions/AddQuestions.tsx b/src/containers/add-resources/AddResources.tsx similarity index 50% rename from src/containers/my-resources/add-questions/AddQuestions.tsx rename to src/containers/add-resources/AddResources.tsx index 7bd008589..ee340828b 100644 --- a/src/containers/my-resources/add-questions/AddQuestions.tsx +++ b/src/containers/add-resources/AddResources.tsx @@ -1,46 +1,61 @@ -import { FC, useCallback, useState } from 'react' +import { useCallback, useState } from 'react' -import { ResourceService } from '~/services/resource-service' import { useSnackBarContext } from '~/context/snackbar-context' import { useModalContext } from '~/context/modal-context' import useSelect from '~/hooks/table/use-select' import useSort from '~/hooks/table/use-sort' import useAxios from '~/hooks/use-axios' import useBreakpoints from '~/hooks/use-breakpoints' + import AddResourceModal from '~/containers/my-resources/add-resource-modal/AddResourceModal' -import { - columns, - initialSort, - removeColumnRules -} from '~/containers/my-resources/add-questions/AddQuestions.constants' -import { ajustColumns } from '~/utils/helper-functions' +import { initialSort } from '~/containers/add-resources/AddResources.constants' import { defaultResponses, snackbarVariants } from '~/constants' -import { Question, ErrorResponse, ItemsWithCount } from '~/types' -interface AddQuestionsProps { - questions: Question[] - onAddQuestions: (questions: Question[]) => void +import { ajustColumns } from '~/utils/helper-functions' +import { + ErrorResponse, + GetResourcesParams, + ItemsWithCount, + CourseResources, + TableColumn, + RemoveColumnRules, + Question, + ServiceFunction +} from '~/types' + +interface AddResourcesProps { + resources: T[] + onAddResources: (resource: T[]) => void + resourceType: string + requestService: ServiceFunction, GetResourcesParams> + columns: TableColumn[] + removeColumnRules: RemoveColumnRules } -const AddQuestions: FC = ({ - questions = [], - onAddQuestions -}) => { +const AddResources = ({ + resources = [], + onAddResources, + resourceType, + requestService, + columns, + removeColumnRules +}: AddResourcesProps) => { + const [selectedRows, setSelectedRows] = useState(resources) const { closeModal } = useModalContext() const { setAlert } = useSnackBarContext() const breakpoints = useBreakpoints() - const initialSelect = questions.map((question) => question._id) + const initialSelect = resources.map((resource) => resource._id) const select = useSelect({ initialSelect }) const sortOptions = useSort({ initialSort }) - const [selectedRows, setSelectedRows] = useState(questions) const { sort } = sortOptions const { handleSelectClick } = select - const columnsToShow = ajustColumns( - breakpoints, - columns, - removeColumnRules + const columnsToShow = ajustColumns(breakpoints, columns, removeColumnRules) + + const getMyResources = useCallback( + () => requestService({ sort }), + [sort, requestService] ) const onResponseError = useCallback( @@ -53,21 +68,16 @@ const AddQuestions: FC = ({ [setAlert] ) - const getMyQuestions = useCallback( - () => ResourceService.getQuestions({ sort }), - [sort] - ) - - const { loading, response } = useAxios>({ - service: getMyQuestions, + const { loading, response } = useAxios>({ + service: getMyResources, defaultResponse: defaultResponses.itemsWithCount, onResponseError }) - const onRowClick = (item: Question) => { - if (selectedRows.find((question) => question._id === item._id)) { + const onRowClick = (item: T) => { + if (selectedRows.find((resource) => resource._id === item._id)) { setSelectedRows((selectedRows) => - selectedRows.filter((question) => question._id !== item._id) + selectedRows.filter((resource) => resource._id !== item._id) ) } else { setSelectedRows((selectedRows) => [...selectedRows, item]) @@ -76,16 +86,26 @@ const AddQuestions: FC = ({ } const onAddItems = () => { - onAddQuestions(selectedRows) + onAddResources(selectedRows) closeModal() } const getItems = useCallback( - (title: string, selectedCategories: string[]) => { + (inputValue: string, selectedCategories: string[]) => { return response.items.filter((item) => { - const titleMatch = item.title - .toLocaleLowerCase() - .includes(title.toLocaleLowerCase()) + let titleMatch + if ('title' in item) { + titleMatch = item.title + .toLocaleLowerCase() + .includes(inputValue.toLocaleLowerCase()) + } else { + titleMatch = item.fileName + .toLocaleLowerCase() + .split('.') + .slice(0, -1) + .join('.') + .includes(inputValue.toLocaleLowerCase()) + } const categoryMatch = selectedCategories.length === 0 || selectedCategories.includes(String(item.category?.name)) @@ -105,10 +125,10 @@ const AddQuestions: FC = ({ onAddItems, data: { loading, getItems }, onRowClick, - resource: 'questions' + resource: resourceType } - return {...props} /> + return {...props} /> } -export default AddQuestions +export default AddResources diff --git a/src/containers/course-section/CourseSectionContainer.constants.tsx b/src/containers/course-section/CourseSectionContainer.constants.tsx index 4160a52b0..acdc0ab30 100644 --- a/src/containers/course-section/CourseSectionContainer.constants.tsx +++ b/src/containers/course-section/CourseSectionContainer.constants.tsx @@ -1,4 +1,25 @@ +import ListAltIcon from '@mui/icons-material/ListAlt' +import NoteAltOutlinedIcon from '@mui/icons-material/NoteAltOutlined' +import AttachFileIcon from '@mui/icons-material/AttachFile' + +import { ResourcesTabsEnum as ResourcesTypes } from '~/types' + export const menuTypes = { resourcesMenu: 'resources', sectionMenu: 'section' } + +export const resourcesData = { + lessons: { + resource: ResourcesTypes.Lessons, + icon: + }, + quizzes: { + resource: ResourcesTypes.Quizzes, + icon: + }, + attachments: { + resource: ResourcesTypes.Attachments, + icon: + } +} diff --git a/src/containers/course-section/CourseSectionContainer.styles.ts b/src/containers/course-section/CourseSectionContainer.styles.ts index 7385ce2f1..152ee0ec7 100644 --- a/src/containers/course-section/CourseSectionContainer.styles.ts +++ b/src/containers/course-section/CourseSectionContainer.styles.ts @@ -1,5 +1,12 @@ import { TypographyVariantEnum } from '~/types' +const menuItem = { + p: '8px', + display: 'flex', + alignItems: 'center', + typography: TypographyVariantEnum.Button +} + export const styles = { root: { backgroundColor: 'basic.white', @@ -54,9 +61,15 @@ export const styles = { shrink: false }, menuItem: { - minWidth: '300px', - pl: '30px', - py: '15px', - typography: TypographyVariantEnum.MidTitle + ...menuItem, + minWidth: '165px' + }, + deleteIconWrapper: { + ...menuItem, + color: 'error.700' + }, + menuIcon: { + fontSize: '18px', + mr: '10px' } } diff --git a/src/containers/course-section/CourseSectionContainer.tsx b/src/containers/course-section/CourseSectionContainer.tsx index 45e511616..37f83fac9 100644 --- a/src/containers/course-section/CourseSectionContainer.tsx +++ b/src/containers/course-section/CourseSectionContainer.tsx @@ -1,27 +1,59 @@ -import { useState, ChangeEvent, FC, Dispatch, SetStateAction } from 'react' +import { + useState, + ChangeEvent, + FC, + Dispatch, + SetStateAction, + useEffect +} from 'react' import { useTranslation } from 'react-i18next' import { MenuItem } from '@mui/material' import Box from '@mui/material/Box' -import DragIndicatorIcon from '@mui/icons-material/DragIndicator' import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp' import IconButton from '@mui/material/IconButton' import MoreVertIcon from '@mui/icons-material/MoreVert' import AddIcon from '@mui/icons-material/Add' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' import AppTextField from '~/components/app-text-field/AppTextField' import AppButton from '~/components/app-button/AppButton' +import ResourcesList from '~/containers/course-section/resources-list/ResourcesList' +import AddResources from '../add-resources/AddResources' + +import { ResourceService } from '~/services/resource-service' import useMenu from '~/hooks/use-menu' +import { useModalContext } from '~/context/modal-context' import { styles } from '~/containers/course-section/CourseSectionContainer.styles' -import { menuTypes } from '~/containers/course-section/CourseSectionContainer.constants' +import { + menuTypes, + resourcesData +} from '~/containers/course-section/CourseSectionContainer.constants' +import { + columns as attachmentColumns, + removeColumnRules as removeAttachmentColumnRules +} from '~/containers/add-resources/AddAttachments.constants' +import { + columns as lessonColumns, + removeColumnRules as removeLessontColumnRules +} from '~/containers/add-resources/AddLessons.constants' +import { + columns as quizColumns, + removeColumnRules as removeQuizColumnRules +} from '~/containers/add-resources/AddQuizzes.constants' import { TextFieldVariantEnum, SizeEnum, ColorEnum, ButtonVariantEnum, - CourseSection + CourseSection, + Lesson, + Quiz, + Attachment, + ResourcesTabsEnum as ResourcesTypes, + CourseResources } from '~/types' interface SectionProps { @@ -37,6 +69,7 @@ const CourseSectionContainer: FC = ({ }) => { const { t } = useTranslation() const { openMenu, renderMenu, closeMenu } = useMenu() + const { openModal } = useModalContext() const [activeMenu, setActiveMenu] = useState('') const [isVisible, setIsVisible] = useState(true) @@ -44,15 +77,67 @@ const CourseSectionContainer: FC = ({ const [descriptionInput, setDescriptionInput] = useState( sectionData.description ) + const [lessons, setLessons] = useState([]) + const [quizzes, setQuizzes] = useState([]) + const [attachments, setAttachments] = useState([]) + const [resources, setResources] = useState([]) + + useEffect(() => { + setResources((prevResources) => { + const allResourcesItems = [...lessons, ...quizzes, ...attachments] + const updatedResourcesItems = prevResources.filter((prevResource) => + allResourcesItems.some( + (currentResource) => currentResource._id === prevResource._id + ) + ) + for (const currentResource of allResourcesItems) { + if ( + !updatedResourcesItems.some( + (prevResource) => prevResource._id === currentResource._id + ) + ) { + updatedResourcesItems.push(currentResource) + } + } + return updatedResourcesItems + }) + }, [lessons, quizzes, attachments]) + + const deleteResource = (resource: CourseResources) => { + if (resource.resourceType === ResourcesTypes.Lessons) { + setLessons((prevLessons) => + prevLessons.filter((item) => item._id !== resource._id) + ) + } else if (resource.resourceType === ResourcesTypes.Quizzes) { + setQuizzes((prevQuizzes) => + prevQuizzes.filter((item) => item._id !== resource._id) + ) + } else if (resource.resourceType === ResourcesTypes.Attachments) { + setAttachments((prevAttachments) => + prevAttachments.filter((item) => item._id !== resource._id) + ) + } + } const onShowHide = () => { setIsVisible((isVisible) => !isVisible) } const onAction = (actionFunc: openModalFunc) => { + closeMenu() actionFunc() } + const onDeleteSection = () => { + setTitleInput('') + setDescriptionInput('') + setResources([]) + closeMenu() + setSectionsItems((prev) => + prev.filter((item) => item.id !== sectionData.id) + ) + } + const onTitleInputChange = (event: ChangeEvent) => { setTitleInput(event.target.value) } @@ -61,16 +146,83 @@ const CourseSectionContainer: FC = ({ setDescriptionInput(event.target.value) } - const onDeleteSection = () => { - setSectionsItems((prev) => { - return prev.filter((item) => item.id !== sectionData.id) + const handleAddResources = ( + resources: T[], + addResources: Dispatch>, + type: ResourcesTypes + ) => { + addResources( + resources.map((resource) => ({ + ...resource, + resourceType: type + })) + ) + } + + const handleOpenAddLessonsModal = () => { + openModal({ + component: ( + + columns={lessonColumns} + onAddResources={(resources) => + handleAddResources(resources, setLessons, ResourcesTypes.Lessons) + } + removeColumnRules={removeLessontColumnRules} + requestService={ResourceService.getUsersLessons} + resourceType={resourcesData.lessons.resource} + resources={lessons} + /> + ) + }) + } + + const handleOpenAddQuizzesModal = () => { + openModal({ + component: ( + + columns={quizColumns} + onAddResources={(resources) => + handleAddResources(resources, setQuizzes, ResourcesTypes.Quizzes) + } + removeColumnRules={removeQuizColumnRules} + requestService={ResourceService.getQuizzes} + resourceType={resourcesData.quizzes.resource} + resources={quizzes} + /> + ) + }) + } + + const handleOpenAddAttachmentsModal = () => { + openModal({ + component: ( + + columns={attachmentColumns} + onAddResources={(resources) => + handleAddResources( + resources, + setAttachments, + ResourcesTypes.Attachments + ) + } + removeColumnRules={removeAttachmentColumnRules} + requestService={ResourceService.getAttachments} + resourceType={resourcesData.attachments.resource} + resources={attachments} + /> + ) }) } const sectionActions = [ { id: 1, - label: {t('course.courseSection.sectionMenu.deleteSection')}, + label: ( + + + {t('course.courseSection.sectionMenu.deleteSection')} + + ), func: onDeleteSection } ] @@ -85,35 +237,43 @@ const CourseSectionContainer: FC = ({ { id: 1, label: ( - {t('course.courseSection.resourcesMenu.lessonMenuItem')} + + {resourcesData.lessons.icon} + {t('course.courseSection.resourcesMenu.lessonMenuItem')} + ), - func: closeMenu + func: handleOpenAddLessonsModal }, { id: 2, - label: {t('course.courseSection.resourcesMenu.quizMenuItem')}, - func: closeMenu + label: ( + + {resourcesData.quizzes.icon} + {t('course.courseSection.resourcesMenu.quizMenuItem')} + + ), + func: handleOpenAddQuizzesModal }, { id: 3, label: ( - {t('course.courseSection.resourcesMenu.attachmentMenuItem')} + + {resourcesData.attachments.icon} + {t('course.courseSection.resourcesMenu.attachmentMenuItem')} + ), - func: closeMenu + func: handleOpenAddAttachmentsModal } ] const resourcesMenuItems = addResourceActions.map(({ label, func, id }) => ( - onAction(func)} sx={styles.menuItem}> + onAction(func)}> {label} )) return ( - - - {isVisible ? ( @@ -164,6 +324,11 @@ const CourseSectionContainer: FC = ({ value={descriptionInput} variant={TextFieldVariantEnum.Standard} /> + } onClick={(event) => { diff --git a/src/containers/course-section/resource-item/ResourceItem.styles.ts b/src/containers/course-section/resource-item/ResourceItem.styles.ts new file mode 100644 index 000000000..94fd12451 --- /dev/null +++ b/src/containers/course-section/resource-item/ResourceItem.styles.ts @@ -0,0 +1,8 @@ +export const styles = { + container: { + p: '16px 24px', + display: 'flex', + justifyContent: 'space-between', + ml: '38px' + } +} diff --git a/src/containers/course-section/resource-item/ResourceItem.tsx b/src/containers/course-section/resource-item/ResourceItem.tsx new file mode 100644 index 000000000..27cf7aaf9 --- /dev/null +++ b/src/containers/course-section/resource-item/ResourceItem.tsx @@ -0,0 +1,43 @@ +import { FC, ReactElement } from 'react' +import Box from '@mui/material/Box' +import IconButton from '@mui/material/IconButton' +import CloseIcon from '@mui/icons-material/Close' + +import IconExtensionWithTitle from '~/components/icon-extension-with-title/IconExtensionWithTitle' + +import { resourcesData } from '~/containers/course-section/CourseSectionContainer.constants' +import { styles } from '~/containers/course-section/resource-item/ResourceItem.styles' +import { ResourcesTabsEnum as ResourcesTypes, CourseResources } from '~/types' + +interface ResourceItemProps { + resource: CourseResources + deleteResource: (resource: CourseResources) => void +} +const ResourceItem: FC = ({ resource, deleteResource }) => { + const onDeleteResource = () => { + deleteResource(resource) + } + + const setResourceIcon = (): ReactElement | undefined => { + if (resource.resourceType === ResourcesTypes.Lessons) { + return resourcesData.lessons.icon + } else if (resource.resourceType === ResourcesTypes.Quizzes) { + return resourcesData.quizzes.icon + } + } + + return ( + + + + + + + ) +} + +export default ResourceItem diff --git a/src/containers/course-section/resources-list/ResourcesList.styles.ts b/src/containers/course-section/resources-list/ResourcesList.styles.ts new file mode 100644 index 000000000..75cc74031 --- /dev/null +++ b/src/containers/course-section/resources-list/ResourcesList.styles.ts @@ -0,0 +1,29 @@ +import palette from '~/styles/app-theme/app.pallete' + +export const styles = { + root: { + position: 'relative', + mb: '32px' + }, + section: (isDragging: boolean) => ({ + position: 'relative', + backgroundColor: 'basic.white', + borderRadius: '6px', + ...(isDragging && { + boxShadow: `0px 3px 16px 2px ${palette.primary[300]}`, + border: `2px solid ${palette.primary[300]}`, + '& .dragIcon': { + color: 'primary.400' + } + }) + }), + dragIcon: { + left: '24px', + position: 'absolute', + top: '24px', + color: 'primary.700', + '&:hover': { + color: 'primary.400' + } + } +} diff --git a/src/containers/course-section/resources-list/ResourcesList.tsx b/src/containers/course-section/resources-list/ResourcesList.tsx new file mode 100644 index 000000000..13ed1db8d --- /dev/null +++ b/src/containers/course-section/resources-list/ResourcesList.tsx @@ -0,0 +1,94 @@ +import { FC, Dispatch, SetStateAction } from 'react' +import { + DragDropContext, + Droppable, + Draggable, + DraggableProvided, + DraggableStateSnapshot, + DropResult, + DroppableProvided +} from 'react-beautiful-dnd' +import Box from '@mui/material/Box' +import DragIndicatorIcon from '@mui/icons-material/DragIndicator' + +import ResourceItem from '~/containers/course-section/resource-item/ResourceItem' + +import { styles } from '~/containers/course-section/resources-list/ResourcesList.styles' + +import { CourseResources } from '~/types' + +interface ResourcesListProps { + items: CourseResources[] + setResources: Dispatch> + deleteResource: (resource: CourseResources) => void +} + +const ResourcesList: FC = ({ + items, + setResources, + deleteResource +}) => { + const reorder = ( + list: CourseResources[], + startIndex: number, + endIndex: number + ): CourseResources[] => { + const result = Array.from(list) + const [removed] = result.splice(startIndex, 1) + result.splice(endIndex, 0, removed) + + return result + } + + const onDragEnd = (result: DropResult) => { + if (!result.destination) { + return + } + + const reorderedItems = reorder( + items, + result.source.index, + result.destination.index + ) + + setResources(reorderedItems) + } + + const resourcesList = items.map((item, i) => ( + + {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => ( + + + + + + + )} + + )) + + return ( + + + + {(provided: DroppableProvided) => ( + + {resourcesList} + {provided.placeholder} + + )} + + + + ) +} + +export default ResourcesList diff --git a/src/containers/course-sections-list/CourseSectionsList.styles.ts b/src/containers/course-sections-list/CourseSectionsList.styles.ts index 8a7d89449..9719f6bac 100644 --- a/src/containers/course-sections-list/CourseSectionsList.styles.ts +++ b/src/containers/course-sections-list/CourseSectionsList.styles.ts @@ -12,5 +12,16 @@ export const styles = { boxShadow: `0px 3px 16px 2px ${palette.primary[300]}`, border: `2px solid ${palette.primary[300]}` }) - }) + }), + dragIconWrapper: { + paddingTop: '24px', + display: 'flex', + justifyContent: 'center' + }, + dragIcon: { + fontSize: '30px', + transform: 'rotate(90deg)', + color: 'primary.400', + cursor: 'pointer' + } } diff --git a/src/containers/course-sections-list/CourseSectionsList.tsx b/src/containers/course-sections-list/CourseSectionsList.tsx index c2a02d752..e576e8b20 100644 --- a/src/containers/course-sections-list/CourseSectionsList.tsx +++ b/src/containers/course-sections-list/CourseSectionsList.tsx @@ -9,6 +9,7 @@ import { DroppableProvided } from 'react-beautiful-dnd' import Box from '@mui/material/Box' +import DragIndicatorIcon from '@mui/icons-material/DragIndicator' import CourseSectionContainer from '~/containers/course-section/CourseSectionContainer' @@ -58,8 +59,10 @@ const CourseSectionsList: FC = ({ ref={provided.innerRef} sx={styles.section(snapshot.isDragging)} {...provided.draggableProps} - {...provided.dragHandleProps} > + + + { openModal({ component: ( - + + columns={columns} + onAddResources={onAddQuestions} + removeColumnRules={removeColumnRules} + requestService={ResourceService.getQuestions} + resourceType={ResourcesTabsEnum.Questions} + resources={questions} + /> ) }) } diff --git a/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx b/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx index 8a09b7ca7..5382f4f1b 100644 --- a/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx +++ b/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx @@ -9,7 +9,7 @@ import CloseIcon from '@mui/icons-material/Close' import IconButton from '@mui/material/IconButton' import Loader from '~/components/loader/Loader' -import AddAttachments from '~/containers/add-attachments/AddAttachments' +import AddResources from '~/containers/add-resources/AddResources' import IconExtensionWithTitle from '~/components/icon-extension-with-title/IconExtensionWithTitle' import { useModalContext } from '~/context/modal-context' @@ -29,6 +29,10 @@ import { myResourcesPath, validations } from '~/pages/create-or-edit-lesson/CreateOrEditLesson.constants' +import { + columns, + removeColumnRules +} from '~/containers/add-resources/AddAttachments.constants' import { styles } from '~/pages/create-or-edit-lesson/CreateOrEditLesson.styles' import { authRoutes } from '~/router/constants/authRoutes' import { @@ -40,7 +44,8 @@ import { LessonData, SizeEnum, TextFieldVariantEnum, - Attachment + Attachment, + ResourcesTabsEnum } from '~/types' const CreateOrEditLesson = () => { @@ -75,9 +80,13 @@ const CreateOrEditLesson = () => { const handleOpenAddAttachmentsModal = () => { openModal({ component: ( - + columns={columns} + onAddResources={handleAddAttachments} + removeColumnRules={removeColumnRules} + requestService={ResourceService.getAttachments} + resourceType={ResourcesTabsEnum.Attachments} + resources={data.attachments} /> ) }) diff --git a/src/types/attachment/interfaces/attachment.interface.ts b/src/types/attachment/interfaces/attachment.interface.ts index 677ebbc3b..585f1fd0a 100644 --- a/src/types/attachment/interfaces/attachment.interface.ts +++ b/src/types/attachment/interfaces/attachment.interface.ts @@ -1,7 +1,8 @@ import { CategoryNameInterface, CommonEntityFields, - UserResponse + UserResponse, + ResourcesTabsEnum as ResourcesTypes } from '~/types' export interface Attachment extends CommonEntityFields { @@ -11,6 +12,7 @@ export interface Attachment extends CommonEntityFields { link: string description?: string size: number + resourceType?: ResourcesTypes } export interface UpdateAttachmentParams { diff --git a/src/types/course/interfaces/course.interface.ts b/src/types/course/interfaces/course.interface.ts index a97483817..b098475c7 100644 --- a/src/types/course/interfaces/course.interface.ts +++ b/src/types/course/interfaces/course.interface.ts @@ -8,6 +8,8 @@ import { ProficiencyLevelEnum } from '~/types' +export type CourseResources = Lesson | Quiz | Attachment + export interface Course extends CommonEntityFields { title: string description: string @@ -21,5 +23,5 @@ export interface CourseSection { id: number title: string description: string - resources: (Lesson | Quiz | Attachment)[] + resources: CourseResources[] } diff --git a/src/types/my-resources/interfaces/myResources.interface.ts b/src/types/my-resources/interfaces/myResources.interface.ts index 9c14e5303..d1cbb0003 100644 --- a/src/types/my-resources/interfaces/myResources.interface.ts +++ b/src/types/my-resources/interfaces/myResources.interface.ts @@ -2,7 +2,8 @@ import { Attachment, Category, CommonEntityFields, - RequestParams + RequestParams, + ResourcesTabsEnum as ResourcesTypes } from '~/types' export interface Lesson extends CommonEntityFields { @@ -12,6 +13,7 @@ export interface Lesson extends CommonEntityFields { description: string attachments: Attachment[] category: Category | null + resourceType?: ResourcesTypes } export interface Categories extends CommonEntityFields { diff --git a/src/types/quizzes/interfaces/quizzes.interface.ts b/src/types/quizzes/interfaces/quizzes.interface.ts index b88fb12ca..e78926dbe 100644 --- a/src/types/quizzes/interfaces/quizzes.interface.ts +++ b/src/types/quizzes/interfaces/quizzes.interface.ts @@ -3,7 +3,8 @@ import { UserResponse, Answer, Category, - Question + Question, + ResourcesTabsEnum as ResourcesTypes } from '~/types' export interface QuestionWithAnswers { @@ -13,9 +14,11 @@ export interface QuestionWithAnswers { export interface Quiz extends CommonEntityFields { title: string + description: string items: QuestionWithAnswers[] author: Pick category: Category | null + resourceType?: ResourcesTypes } export interface CreateQuizParams { diff --git a/tests/unit/containers/add-attachments/AddAttachments.spec.jsx b/tests/unit/containers/add-attachments/AddAttachments.spec.jsx deleted file mode 100644 index d1310175b..000000000 --- a/tests/unit/containers/add-attachments/AddAttachments.spec.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { fireEvent, screen } from '@testing-library/react' -import AddAttachments from '~/containers/add-attachments/AddAttachments' -import { mockAxiosClient, renderWithProviders } from '~tests/test-utils' -import { URLs } from '~/constants/request' - -const attachmentDataMock = { - _id: '64cd12f1fad091e0ee719830', - author: '6494128829631adbaf5cf615', - fileName: 'spanish.pdf', - link: 'link', - description: 'Mock description for attachments', - size: 100, - createdAt: '2023-07-25T13:12:12.998Z', - updatedAt: '2023-07-25T13:12:12.998Z' -} - -const responseItemsMock = Array(10) - .fill() - .map((_, index) => ({ - ...attachmentDataMock, - _id: `${index}`, - fileName: index + attachmentDataMock.fileName - })) - -const attachmentMockData = { - count: 10, - items: responseItemsMock -} - -describe('AddAttachments', () => { - beforeEach(() => { - mockAxiosClient - .onGet(URLs.resources.attachments.get) - .reply(200, attachmentMockData) - renderWithProviders() - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('should render the component', () => { - const addAttachments = screen.getByText('myResourcesPage.attachments.add') - - expect(addAttachments).toBeInTheDocument() - }), - it('should filter attachments', async () => { - const placeholder = screen.getByPlaceholderText('common.search') - - expect(placeholder).toBeInTheDocument() - - fireEvent.click(placeholder) - fireEvent.change(placeholder, { target: { value: '2spanish' } }) - - const filteredAttachmentCount = screen.getAllByRole('row').length - 2 - - expect(filteredAttachmentCount).toBe(1) - }) -}) diff --git a/tests/unit/containers/add-resources/add-attachments/AddAttachments.spec.jsx b/tests/unit/containers/add-resources/add-attachments/AddAttachments.spec.jsx new file mode 100644 index 000000000..f95cc9e04 --- /dev/null +++ b/tests/unit/containers/add-resources/add-attachments/AddAttachments.spec.jsx @@ -0,0 +1,70 @@ +import { screen } from '@testing-library/react' +import AddResources from '~/containers/add-resources/AddResources' +import { mockAxiosClient, renderWithProviders } from '~tests/test-utils' +import { URLs } from '~/constants/request' +import { + columns, + removeColumnRules +} from '~/containers/add-resources/AddAttachments.constants' + +const attachmentDataMock = { + _id: '64cd12f1fad091e0ee719830', + author: '6494128829631adbaf5cf615', + fileName: 'spanish.pdf', + link: 'link', + category: { id: '64fb2c33eba89699411d22bb', name: 'History' }, + description: 'Mock description for attachments', + size: 100, + createdAt: '2023-07-25T13:12:12.998Z', + updatedAt: '2023-07-25T13:12:12.998Z' +} + +const responseItemsMock = Array(10) + .fill() + .map((_, index) => ({ + ...attachmentDataMock, + _id: `${index}`, + fileName: index + attachmentDataMock.fileName + })) + +const attachmentMockData = { + count: 10, + items: responseItemsMock +} + +const mockRequestService = vi.fn(() => + Promise.resolve({ + data: { items: responseItemsMock, count: responseItemsMock.length } + }) +) +const mockOnAddResources = () => {} + +describe('Tests for AddResources container', () => { + beforeEach(() => { + mockAxiosClient + .onGet(URLs.resources.attachments.get) + .reply(200, attachmentMockData) + + renderWithProviders( + + ) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('should display list of all attachments with category', async () => { + const displayedAttachments = screen.getAllByText( + attachmentDataMock.category.name + ).length + expect(displayedAttachments).toBe(10) + }) +}) diff --git a/tests/unit/containers/add-resources/add-lessons/AddLessons.spec.jsx b/tests/unit/containers/add-resources/add-lessons/AddLessons.spec.jsx new file mode 100644 index 000000000..3a20a8bfd --- /dev/null +++ b/tests/unit/containers/add-resources/add-lessons/AddLessons.spec.jsx @@ -0,0 +1,94 @@ +import { fireEvent, screen } from '@testing-library/react' +import AddResources from '~/containers/add-resources/AddResources' +import { URLs } from '~/constants/request' +import { + columns, + removeColumnRules +} from '~/containers/add-resources/AddLessons.constants' +import { mockAxiosClient, renderWithProviders } from '~tests/test-utils' + +const lessonDataMock = { + _id: '1', + title: 'Lesson', + author: 'some author', + content: 'Content', + description: 'Description', + attachments: [], + category: { id: '64fb2c33eba89699411d22bb', name: 'History' }, + createdAt: '2023-09-25T13:12:12.998Z', + updatedAt: '2023-09-25T13:12:12.998Z' +} + +const responseItemsMock = Array(10) + .fill() + .map((_, index) => ({ + ...lessonDataMock, + _id: `${index}`, + title: `${lessonDataMock.title}${index}` + })) + +const resourcesMockData = { + count: 10, + items: responseItemsMock +} + +const mockRequestService = vi.fn(() => + Promise.resolve({ + data: { items: responseItemsMock, count: responseItemsMock.length } + }) +) + +const mockOnAddResources = () => {} + +describe('Tests for AddResources container', () => { + beforeEach(() => { + mockAxiosClient + .onGet(URLs.resources.lessons.get) + .reply(200, resourcesMockData) + + renderWithProviders( + + ) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('should display list of all resources with category', async () => { + const displayedLessons = screen.getAllByText( + lessonDataMock.category.name + ).length + expect(displayedLessons).toBe(10) + }) + + it('should filter resources', () => { + const placeholder = screen.getByPlaceholderText('common.search') + + expect(placeholder).toBeInTheDocument() + + fireEvent.click(placeholder) + fireEvent.change(placeholder, { target: { value: 'Lesson2' } }) + + const filteredAttachmentCount = screen.getAllByRole('row').length - 2 + + expect(filteredAttachmentCount).toBe(1) + }) + + it('handles row click and selection', () => { + const resourceRow = screen.getByText('Lesson1') + + fireEvent.click(resourceRow) + + const rowCheckbox = screen.getAllByRole('row')[2] + + expect(rowCheckbox).toHaveClass('Mui-selected') + }) +}) diff --git a/tests/unit/containers/my-resources/AddQuestions.spec.jsx b/tests/unit/containers/add-resources/add-questions/AddQuestions.spec.jsx similarity index 55% rename from tests/unit/containers/my-resources/AddQuestions.spec.jsx rename to tests/unit/containers/add-resources/add-questions/AddQuestions.spec.jsx index ce5d20ddc..c7e3efabd 100644 --- a/tests/unit/containers/my-resources/AddQuestions.spec.jsx +++ b/tests/unit/containers/add-resources/add-questions/AddQuestions.spec.jsx @@ -1,9 +1,11 @@ -import { fireEvent, screen } from '@testing-library/react' - -import AddQuestions from '~/containers/my-resources/add-questions/AddQuestions' - +import { screen } from '@testing-library/react' +import AddResources from '~/containers/add-resources/AddResources' import { mockAxiosClient, renderWithProviders } from '~tests/test-utils' import { URLs } from '~/constants/request' +import { + columns, + removeColumnRules +} from '~/containers/add-resources/AddQuestions.constants' const questionMock = { _id: '64fb2c33eba89699411d22bb', @@ -31,35 +33,40 @@ const questionResponseMock = { items: responseItemsMock } +const mockRequestService = vi.fn(() => + Promise.resolve({ + data: { items: responseItemsMock, count: responseItemsMock.length } + }) +) + +const mockOnAddResources = () => {} + describe('AddQuestions', () => { beforeEach(() => { mockAxiosClient .onGet(URLs.resources.questions.get) .reply(200, questionResponseMock) - renderWithProviders() + renderWithProviders( + + ) }) afterEach(() => { vi.clearAllMocks() }) - it('should render title', () => { + it('should render title and question', () => { const title = screen.getByText('myResourcesPage.questions.add') - expect(title).toBeInTheDocument() - }), - it('should filter questions', async () => { - const placeholder = screen.getByPlaceholderText('common.search') - - expect(placeholder).toBeInTheDocument() - - fireEvent.click(placeholder) - fireEvent.change(placeholder, { - target: { value: responseItemsMock[1].title } - }) - const filteredQuestionsCount = screen.getAllByRole('row').length - 2 - - expect(filteredQuestionsCount).toBe(1) - }) + const questions = screen.getByText('1First Question') + expect(questions).toBeInTheDocument() + }) }) diff --git a/tests/unit/containers/add-resources/add-quizzes/AddQuizzes.spec.jsx b/tests/unit/containers/add-resources/add-quizzes/AddQuizzes.spec.jsx new file mode 100644 index 000000000..a9fa3d215 --- /dev/null +++ b/tests/unit/containers/add-resources/add-quizzes/AddQuizzes.spec.jsx @@ -0,0 +1,65 @@ +import { screen } from '@testing-library/react' +import AddResources from '~/containers/add-resources/AddResources' +import { mockAxiosClient, renderWithProviders } from '~tests/test-utils' +import { URLs } from '~/constants/request' +import { + columns, + removeColumnRules +} from '~/containers/add-resources/AddQuizzes.constants' + +const quizMock = { + _id: '64fb2c33eba89699411d22bb', + title: 'Quiz', + description: '', + items: [], + author: '648afee884936e09a37deaaa', + category: { id: '64fb2c33eba89699411d22bb', name: 'Music' }, + createdAt: '2023-09-08T14:14:11.373Z', + updatedAt: '2023-09-08T14:14:11.373Z' +} + +const responseItemsMock = Array(10) + .fill() + .map((_, index) => ({ + ...quizMock, + _id: `${index}`, + title: index + quizMock.title + })) + +const quizResponseMock = { + count: 10, + items: responseItemsMock +} + +const mockRequestService = vi.fn(() => + Promise.resolve({ + data: { items: responseItemsMock, count: responseItemsMock.length } + }) +) + +const mockOnAddResources = () => {} + +describe('AddQuestions', () => { + beforeEach(() => { + mockAxiosClient.onGet(URLs.quizzes.get).reply(200, quizResponseMock) + renderWithProviders( + + ) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('should render title and question', () => { + const displayedQuizzes = screen.getAllByText(quizMock.category.name).length + expect(displayedQuizzes).toBe(10) + }) +}) diff --git a/tests/unit/containers/course-section/CourseSectionContainer.spec.jsx b/tests/unit/containers/course-section/CourseSectionContainer.spec.jsx index 349635834..9e0e38ab0 100644 --- a/tests/unit/containers/course-section/CourseSectionContainer.spec.jsx +++ b/tests/unit/containers/course-section/CourseSectionContainer.spec.jsx @@ -4,24 +4,27 @@ import { screen, fireEvent } from '@testing-library/react' import CourseSectionContainer from '~/containers/course-section/CourseSectionContainer' const mockedSectionData = { - section_id: 1, - title: '', - description: '', + id: 1, + title: 'Title', + description: 'Description', resources: [] } +const mockedSetSectionItems = vi.fn() + describe('CourseSectionContainer tests', () => { beforeEach(() => { renderWithProviders( - + ) }) it('should render inputs for title and description', () => { - const titleInput = screen.getByText('course.courseSection.defaultNewTitle') - const labelInput = screen.getByText( - 'course.courseSection.defaultNewDescription' - ) + const titleInput = screen.getByDisplayValue(mockedSectionData.title) + const labelInput = screen.getByDisplayValue(mockedSectionData.description) expect(titleInput).toBeInTheDocument() expect(labelInput).toBeInTheDocument() @@ -53,9 +56,67 @@ describe('CourseSectionContainer tests', () => { 'course.courseSection.addResourceBtn' ) const hideBtn = screen.getAllByRole('button')[0] - fireEvent.click(hideBtn) expect(addResourcesBtn).not.toBeVisible() }) + + it('should delete section', () => { + const deleteMenu = screen.getByTestId('MoreVertIcon').parentElement + fireEvent.click(deleteMenu) + const deleteButton = screen.getByTestId('DeleteOutlineIcon').parentElement + fireEvent.click(deleteButton) + const titleInput = screen.getByText('course.courseSection.defaultNewTitle') + const descriptionInput = screen.getByText( + 'course.courseSection.defaultNewDescription' + ) + + expect(mockedSetSectionItems).toHaveBeenCalledTimes(1) + expect(titleInput).toBeInTheDocument() + expect(descriptionInput).toBeInTheDocument() + }) + + it('should show add lessons modal', () => { + const addResourcesBtn = screen.getByText( + 'course.courseSection.addResourceBtn' + ) + fireEvent.click(addResourcesBtn) + const addLessonBtn = screen.getByText( + 'course.courseSection.resourcesMenu.lessonMenuItem' + ).parentElement + fireEvent.click(addLessonBtn) + const addLessonModal = screen.getByText('myResourcesPage.lessons.add') + + expect(addLessonModal).toBeInTheDocument() + }) + + it('should show add quizzes modal', () => { + const addResourcesBtn = screen.getByText( + 'course.courseSection.addResourceBtn' + ) + fireEvent.click(addResourcesBtn) + const addQuizBtn = screen.getByText( + 'course.courseSection.resourcesMenu.quizMenuItem' + ).parentElement + fireEvent.click(addQuizBtn) + const addQuizModal = screen.getByText('myResourcesPage.quizzes.add') + + expect(addQuizModal).toBeInTheDocument() + }) + + it('should show attachments quizzes modal', () => { + const addResourcesBtn = screen.getByText( + 'course.courseSection.addResourceBtn' + ) + fireEvent.click(addResourcesBtn) + const addAttachmentBtn = screen.getByText( + 'course.courseSection.resourcesMenu.attachmentMenuItem' + ).parentElement + fireEvent.click(addAttachmentBtn) + const addAttachmentModal = screen.getByText( + 'myResourcesPage.attachments.add' + ) + + expect(addAttachmentModal).toBeInTheDocument() + }) }) diff --git a/tests/unit/containers/course-section/resource-item/ResourceItem.spec.jsx b/tests/unit/containers/course-section/resource-item/ResourceItem.spec.jsx new file mode 100644 index 000000000..322414e9c --- /dev/null +++ b/tests/unit/containers/course-section/resource-item/ResourceItem.spec.jsx @@ -0,0 +1,58 @@ +import { renderWithProviders } from '~tests/test-utils' +import { fireEvent, screen } from '@testing-library/react' + +import ResourceItem from '~/containers/course-section/resource-item/ResourceItem' +import { ResourcesTabsEnum as ResourcesTypes } from '~/types' + +const mockedLessonData = { + _id: '1', + title: 'Lesson1', + author: 'some author', + content: 'Content', + description: 'Description', + attachments: [], + category: null, + resourceType: ResourcesTypes.Lessons +} + +const mockedFunc = vi.fn() + +describe('new course section RescourceItem tests', () => { + beforeEach(() => { + renderWithProviders( + + ) + }) + + it('should render added resource', () => { + const resourceTitle = screen.getByText(mockedLessonData.title) + expect(resourceTitle).toBeInTheDocument() + }) + + it('should display lesson icon', () => { + const lessonIcon = screen.getByTestId('ListAltIcon') + expect(lessonIcon).toBeInTheDocument() + }) + + it('should call delete resource function', () => { + const deleteButton = screen.getByRole('button') + + fireEvent.click(deleteButton) + + expect(mockedFunc).toHaveBeenCalledTimes(1) + }) +}) + +describe('should render quiz component', () => { + it('should display quiz icon', () => { + mockedLessonData.resourceType = ResourcesTypes.Quizzes + + renderWithProviders( + + ) + + const quizIcon = screen.getByTestId('NoteAltOutlinedIcon') + + expect(quizIcon).toBeInTheDocument() + }) +}) diff --git a/tests/unit/containers/course-section/resources-list/ResourcesList.spec.jsx b/tests/unit/containers/course-section/resources-list/ResourcesList.spec.jsx new file mode 100644 index 000000000..20b6a98da --- /dev/null +++ b/tests/unit/containers/course-section/resources-list/ResourcesList.spec.jsx @@ -0,0 +1,49 @@ +import { renderWithProviders } from '~tests/test-utils' +import { screen } from '@testing-library/react' + +import ResourcesList from '~/containers/course-section/resources-list/ResourcesList' +import { ResourcesTabsEnum as ResourcesTypes } from '~/types' + +const mockedLessonData = [ + { + _id: '1', + title: 'Lesson1', + author: 'some author', + content: 'Content', + description: 'Description', + attachments: [], + category: null, + resourceType: ResourcesTypes.Lessons + }, + { + _id: '2', + title: 'Lesson2', + author: 'new author', + content: 'Content', + description: 'Description', + attachments: [], + category: null, + resourceType: ResourcesTypes.Lessons + } +] + +const mockedSetResources = vi.fn() + +describe('new course section RescourceItem tests', () => { + beforeEach(() => { + renderWithProviders( + + ) + }) + + it('should render resources list with gragBtn', () => { + const resourceTitle1 = screen.getByText(mockedLessonData[0].title) + const resourceTitle2 = screen.getByText(mockedLessonData[1].title) + + expect(resourceTitle1).toBeInTheDocument() + expect(resourceTitle2).toBeInTheDocument() + }) +}) diff --git a/tests/unit/pages/create-or-edit-lesson/CreateOrEditLesson.spec.jsx b/tests/unit/pages/create-or-edit-lesson/CreateOrEditLesson.spec.jsx index 218524b49..be36a56b6 100644 --- a/tests/unit/pages/create-or-edit-lesson/CreateOrEditLesson.spec.jsx +++ b/tests/unit/pages/create-or-edit-lesson/CreateOrEditLesson.spec.jsx @@ -33,7 +33,7 @@ describe('CreateOrEditLesson', () => { fireEvent.click(addedAttachment) - const title = screen.getByText('common.uploadNewFile') + const title = screen.getByText('myResourcesPage.attachments.add') expect(title).toBeInTheDocument() })