From e07886b402249ff5248815dc48954381cb8dc2df Mon Sep 17 00:00:00 2001 From: Maria Afonina <104988390+MariaAfonina@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:41:28 +0200 Subject: [PATCH] added category autocomplete to the lesson page --- .../translations/en/my-resources-page.json | 3 +- .../CreateOrEditLesson.constants.tsx | 3 +- .../CreateOrEditLesson.styles.ts | 13 ++ .../CreateOrEditLesson.tsx | 123 +++++++++++++++++- .../lesson/interfaces/lesson.interfaces.ts | 1 + .../CreateOrEditLesson.spec.jsx | 67 ++++++++-- .../CreateOrEditQuestion.spec.jsx | 1 - 7 files changed, 195 insertions(+), 16 deletions(-) diff --git a/src/constants/translations/en/my-resources-page.json b/src/constants/translations/en/my-resources-page.json index 2aeb6f6397..670758e2d5 100644 --- a/src/constants/translations/en/my-resources-page.json +++ b/src/constants/translations/en/my-resources-page.json @@ -71,7 +71,8 @@ "emptyItems": "You have no categories yet", "successCreation": "«{{category}}» category was created successfully", "successDeletion": "Category was deleted successfully", - "confirmDeletionTitle": "Do you confirm category deletion?" + "confirmDeletionTitle": "Do you confirm category deletion?", + "categoryDropdown": "Category..." }, "confirmDeletionMessage": "This action is permanent and will remove all related content. Please review your decision before proceeding." } \ No newline at end of file diff --git a/src/pages/create-or-edit-lesson/CreateOrEditLesson.constants.tsx b/src/pages/create-or-edit-lesson/CreateOrEditLesson.constants.tsx index ac598fdab9..5670f12569 100644 --- a/src/pages/create-or-edit-lesson/CreateOrEditLesson.constants.tsx +++ b/src/pages/create-or-edit-lesson/CreateOrEditLesson.constants.tsx @@ -11,7 +11,8 @@ export const initialValues = { title: '', description: '', content: '', - attachments: [] + attachments: [], + category: null } export const defaultResponse = { diff --git a/src/pages/create-or-edit-lesson/CreateOrEditLesson.styles.ts b/src/pages/create-or-edit-lesson/CreateOrEditLesson.styles.ts index 113c992f7f..ebc4588729 100644 --- a/src/pages/create-or-edit-lesson/CreateOrEditLesson.styles.ts +++ b/src/pages/create-or-edit-lesson/CreateOrEditLesson.styles.ts @@ -43,6 +43,19 @@ export const styles = { gap: { xs: '24px', sm: '30px' }, justifyContent: 'space-between' }, + addButton: { + justifyContent: 'flex-start', + pl: '10px', + gap: '5px' + }, + labelCategory: { + color: 'primary.600', + maxWidth: '464px', + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '4px' + }, attachmentList: { container: { background: palette.basic.grey, diff --git a/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx b/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx index 5382f4f1bc..ec5eb61d25 100644 --- a/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx +++ b/src/pages/create-or-edit-lesson/CreateOrEditLesson.tsx @@ -1,4 +1,10 @@ -import { useEffect } from 'react' +import { + HTMLAttributes, + SyntheticEvent, + useCallback, + useEffect, + useState +} from 'react' import { useTranslation } from 'react-i18next' import { Link, useNavigate, useParams } from 'react-router-dom' import { AxiosResponse } from 'axios' @@ -7,6 +13,7 @@ import Divider from '@mui/material/Divider' import AddIcon from '@mui/icons-material/Add' import CloseIcon from '@mui/icons-material/Close' import IconButton from '@mui/material/IconButton' +import Typography from '@mui/material/Typography' import Loader from '~/components/loader/Loader' import AddResources from '~/containers/add-resources/AddResources' @@ -17,6 +24,8 @@ import AppButton from '~/components/app-button/AppButton' import AppTextField from '~/components/app-text-field/AppTextField' import FileEditor from '~/components/file-editor/FileEditor' import PageWrapper from '~/components/page-wrapper/PageWrapper' +import AddCategoriesModal from '~/containers/my-resources/add-categories-modal/AddCategoriesModal' +import AsyncAutocomplete from '~/components/async-autocomlete/AsyncAutocomplete' import { useSnackBarContext } from '~/context/snackbar-context' import useAxios from '~/hooks/use-axios' import useForm from '~/hooks/use-form' @@ -45,16 +54,21 @@ import { SizeEnum, TextFieldVariantEnum, Attachment, - ResourcesTabsEnum + ResourcesTabsEnum, + CreateCategoriesParams, + CategoryNameInterface, + Categories, + TypographyVariantEnum } from '~/types' const CreateOrEditLesson = () => { const { t } = useTranslation() const { setAlert } = useSnackBarContext() - const { openModal } = useModalContext() + const { openModal, closeModal } = useModalContext() const navigate = useNavigate() const { id } = useParams() + const [isFetched, setIsFetched] = useState(false) const handleResponseError = (error: ErrorResponse) => { setAlert({ @@ -129,6 +143,59 @@ const CreateOrEditLesson = () => { onResponseError: handleResponseError }) + const createCategory = useCallback( + (params?: CreateCategoriesParams) => + ResourceService.createResourceCategory(params), + [] + ) + + const onResponseCategory = useCallback( + (response: Categories | null) => { + const categoryName = response ? response.name : '' + + setAlert({ + severity: snackbarVariants.success, + message: t('myResourcesPage.categories.successCreation', { + category: categoryName + }) + }) + + setIsFetched(false) + }, + [setAlert, t] + ) + + const { fetchData: handleCreateCategory } = useAxios({ + service: createCategory, + defaultResponse: null, + fetchOnMount: false, + onResponse: onResponseCategory, + onResponseError: handleResponseError + }) + + const onCreateCategory = () => { + openModal({ + component: ( + + ) + }) + } + + const getCategories = useCallback(() => { + setIsFetched(true) + return ResourceService.getResourcesCategoriesNames() + }, []) + + const onCategoryChange = ( + _: SyntheticEvent, + value: CategoryNameInterface | null + ) => { + handleNonInputValueChange('category', value?._id ?? null) + } + const { data, errors, @@ -154,7 +221,7 @@ const CreateOrEditLesson = () => { } const { loading: getLessonLoading, fetchData: fetchDataLesson } = useAxios< - Lesson, + LessonData, string >({ service: getLesson, @@ -187,6 +254,34 @@ const CreateOrEditLesson = () => { )) + const categoryOptionsList = ( + props: HTMLAttributes, + option: string, + index: number + ) => ( + + {index === 0 && ( + + + + {t('myResourcesPage.categories.addBtn')} + + + + )} + + {option} + + + ) + return ( { value={data.description} variant={TextFieldVariantEnum.Standard} /> + + + {t('questionPage.chooseCategory')} + + + fetchCondition={!isFetched} + fetchOnFocus + labelField='name' + onChange={onCategoryChange} + renderOption={(props, option, state) => + categoryOptionsList(props, option.name, state.index) + } + service={getCategories} + textFieldProps={{ + label: t('myResourcesPage.categories.categoryDropdown') + }} + value={data.category} + valueField='_id' + /> + ({ - ResourceService: { - addLesson: () => mockFetchData() - } -})) +describe('CreateOrEditLesson component test', () => { + mockAxiosClient + .onGet(URLs.resources.resourcesCategories.getNames) + .reply(200, categoriesNamesMock) -describe('CreateOrEditLesson', () => { beforeEach(() => { renderWithProviders() }) @@ -61,4 +63,51 @@ describe('CreateOrEditLesson', () => { expect(errorDescription).toBeInTheDocument() }) + + it('should choose the category from options list', async () => { + const autocomplete = screen.getByRole('combobox') + + expect(autocomplete).toBeInTheDocument() + expect(autocomplete.value).toBe('') + + fireEvent.click(autocomplete) + fireEvent.focus(autocomplete) + + fireEvent.change(autocomplete, { + target: { value: categoriesNamesMock[1].name } + }) + + fireEvent.keyDown(autocomplete, { key: 'ArrowDown' }) + fireEvent.keyDown(autocomplete, { key: 'Enter' }) + + await waitFor(() => { + expect(autocomplete.value).toBe(categoriesNamesMock[1].name) + }) + + fireEvent.keyDown(autocomplete, { key: 'ArrowDown' }) + fireEvent.keyDown(autocomplete, { key: 'Enter' }) + + expect(autocomplete.value).toBe(categoriesNamesMock[1].name) + }) + + it('should click on "add button" in options list', async () => { + const autocomplete = screen.getByRole('combobox') + + fireEvent.click(autocomplete) + fireEvent.focus(autocomplete) + + fireEvent.keyDown(autocomplete, { key: 'ArrowDown' }) + + await waitFor(() => { + const addButton = screen.queryByText('myResourcesPage.categories.addBtn') + + fireEvent.click(addButton) + }) + + await waitFor(() => { + const newCategory = screen.getByText('myResourcesPage.categories.name') + + expect(newCategory).toBeInTheDocument() + }) + }) }) diff --git a/tests/unit/pages/create-or-edit-question/CreateOrEditQuestion.spec.jsx b/tests/unit/pages/create-or-edit-question/CreateOrEditQuestion.spec.jsx index e369b1301e..b2aaaacb83 100644 --- a/tests/unit/pages/create-or-edit-question/CreateOrEditQuestion.spec.jsx +++ b/tests/unit/pages/create-or-edit-question/CreateOrEditQuestion.spec.jsx @@ -1,4 +1,3 @@ -import { describe } from 'vitest' import { screen, fireEvent, waitFor } from '@testing-library/react' import { renderWithProviders, mockAxiosClient } from '~tests/test-utils'