From 558c4758125a4d11d503f0c9a29f381d6f31d9bd Mon Sep 17 00:00:00 2001 From: Meetul Rathore Date: Mon, 12 Feb 2024 01:24:16 +0530 Subject: [PATCH] Action item category (#1578) * add mutations and queries * add translations * add tests * add dropdown for toggling settings on small screens * minor corrections * minor change * minor change and refactor * Update documentation * Revert "Update documentation" This reverts commit 13a638b9b41af35127cea1f9a023c480d8dc677b. * Update documentation * Revert "Update documentation" This reverts commit 42936271a2ba048515dd23859a4976ff88070673. * Update documentation * Revert "Update documentation" This reverts commit bddc63d476642c95a6ba81ab0ea9fea1f96be628. * add wait to tests --- public/locales/en.json | 17 + public/locales/fr.json | 17 + public/locales/hi.json | 17 + public/locales/sp.json | 17 + public/locales/zh.json | 17 + src/GraphQl/Mutations/mutations.ts | 27 ++ src/GraphQl/Queries/Queries.ts | 12 + src/components/DeleteOrg/DeleteOrg.tsx | 2 +- .../OrgActionItemCategories.module.css | 33 ++ .../OrgActionItemCategories.test.tsx | 364 ++++++++++++++++++ .../OrgActionItemCategories.tsx | 284 ++++++++++++++ .../OrgActionItemCategoryMocks.ts | 174 +++++++++ .../OrgSettings/OrgSettings.module.css | 29 +- src/screens/OrgSettings/OrgSettings.test.tsx | 49 ++- src/screens/OrgSettings/OrgSettings.tsx | 162 ++++++-- src/utils/interfaces.ts | 10 + 16 files changed, 1187 insertions(+), 44 deletions(-) create mode 100644 src/components/OrgActionItemCategories/OrgActionItemCategories.module.css create mode 100644 src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx create mode 100644 src/components/OrgActionItemCategories/OrgActionItemCategories.tsx create mode 100644 src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts diff --git a/public/locales/en.json b/public/locales/en.json index d2d3c1366d..08b3933d74 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -446,6 +446,8 @@ "orgSettings": { "title": "Talawa Setting", "pageName": "Settings", + "general": "General", + "actionItemCategories": "Action Item Categories", "updateOrganization": "Update Organization", "seeRequest": "See Request", "settings": "Settings", @@ -742,5 +744,20 @@ "Remove Custom Field": "Remove Custom Field", "fieldSuccessMessage": "Field added successfully", "fieldRemovalSuccess": "Field removed successfully" + }, + "orgActionItemCategories": { + "createButton": "Create", + "editButton": "Edit", + "enableButton": "Enable", + "disableButton": "Disable", + "updateActionItemCategory": "Update", + "actionItemCategoryName": "Name", + "actionItemCategoryDetails": "Action Item Category Details", + "enterName": "Enter Name", + "successfulCreation": "Action Item Category created successfully", + "successfulUpdation": "Action Item Category updated successfully", + "sameNameConflict": "Please change the name to make an update", + "categoryEnabled": "Action Item Category Enabled", + "categoryDisabled": "Action Item Category Disabled" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index ba5f9537c4..504549e796 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -441,6 +441,8 @@ "orgSettings": { "title": "Paramètre Talawa", "pageName": "Paramètres", + "general": "Général", + "actionItemCategories": "Catégories d’éléments d’action", "updateYourDetails": "Mettre à jour vos informations", "updateYourPassword": "Mettez à jour votre mot de passe", "updateOrganization": "Mettre à jour l'organisation", @@ -727,5 +729,20 @@ "Supprimer le champ personnalisé": "Supprimer le champ personnalisé", "fieldSuccessMessage": "Champ ajouté avec succès", "fieldRemovalSuccess": "Champ supprimé avec succès" + }, + "orgActionItemCategories": { + "createButton": "Créez", + "editButton": "Éditez", + "enableButton": "Activez", + "disableButton": "Désactivez", + "updateActionItemCategory": "Mettre à jour", + "actionItemCategoryName": "Nom", + "actionItemCategoryDetails": "Détails de la catégorie d’élément d’action", + "enterName": "Entrez le nom", + "successfulCreation": "Catégorie d’élément d’action créée avec succès", + "successfulUpdation": "La catégorie d’élément d’action a été mise à jour avec succès", + "sameNameConflict": "Veuillez modifier le nom pour effectuer une mise à jour", + "categoryEnabled": "Catégorie d’action activée", + "categoryDisabled": "Catégorie d’action désactivée" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index a4828051a3..539a08873a 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -441,6 +441,8 @@ "orgSettings": { "title": "तलावा सेटिंग", "pageName": "सेटिंग्स", + "general": "सामान्य", + "actionItemCategories": "कार्रवाई आइटम श्रेणियाँ", "updateYourDetails": "अपना विवरण अपडेट करें", "updateYourPassword": "अपना पासवर्ड अपडेट करें", "updateOrganization": "अद्यतन संगठन", @@ -728,5 +730,20 @@ "Remove Custom Field": "कस्टम फ़ील्ड हटाएँ", "fieldSuccessMessage": "फ़ील्ड सफलतापूर्वक जोड़ा गया", "fieldRemovalSuccess": "फ़ील्ड सफलतापूर्वक हटा दिया गया" + }, + "orgActionItemCategories": { + "createButton": "बनाएं", + "editButton": "संपादित करें", + "enableButton": "सक्षम करें", + "disableButton": "अक्षम करें", + "updateActionItemCategory": "अद्यतन करें", + "actionItemCategoryName": "नाम", + "actionItemCategoryDetails": "कार्रवाई आइटम श्रेणी विवरण", + "enterName": "नाम दर्ज करें", + "successfulCreation": "कार्रवाई आइटम श्रेणी सफलतापूर्वक बनाई गई", + "successfulUpdation": "क्रिया आइटम श्रेणी सफलतापूर्वक अद्यतन की गई", + "sameNameConflict": "अपडेट करने के लिए कृपया नाम बदलें", + "categoryEnabled": "कार्रवाई आइटम श्रेणी सक्षम", + "categoryDisabled": "क्रिया आइटम श्रेणी अक्षम की गई" } } diff --git a/public/locales/sp.json b/public/locales/sp.json index 97661fc8b7..6cb0d4e6c9 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -440,6 +440,8 @@ "orgSettings": { "title": "Configuración Talawa", "pageName": "Configuración", + "general": "General", + "actionItemCategories": "Categorías de elementos de acción", "updateYourDetails": "Actualiza tus datos", "updateYourPassword": "Actualice su contraseña", "updateOrganization": "Actualizar Organización", @@ -727,5 +729,20 @@ "Remove Custom Field": "Eliminar Campo Personalizado", "fieldSuccessMessage": "Campo añadido exitosamente", "fieldRemovalSuccess": "Campo eliminado exitosamente" + }, + "orgActionItemCategories": { + "createButton": "Crear", + "editButton": "Editar", + "enableButton": "Habilitar", + "disableButton": "Inhabilitar", + "updateActionItemCategory": "Actualizar", + "actionItemCategoryName": "Nombre", + "actionItemCategoryDetails": "Detalles de la categoría de elemento de acción", + "enterName": "Introduzca el nombre", + "successfulCreation": "Categoría de elemento de acción creada correctamente", + "successfulUpdation": "Categoría de elemento de acción actualizada correctamente", + "sameNameConflict": "Cambie el nombre para realizar una actualización", + "categoryEnabled": "Categoría de elemento de acción habilitada", + "categoryDisabled": "Categoría de elemento de acción deshabilitada" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index 026d6364a8..875541af73 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -441,6 +441,8 @@ "orgSettings": { "title": "塔拉瓦設置", "pageName": "设置", + "general": "一般", + "actionItemCategories": "措施项类别", "updateYourDetails": "更新您的詳細信息", "updateYourPassword": "更新您的密碼", "updateOrganization": "更新組織", @@ -728,5 +730,20 @@ "删除自定义字段": "删除自定义字段", "fieldSuccessMessage": "字段添加成功", "fieldRemovalSuccess": "字段删除成功" + }, + "orgActionItemCategories": { + "createButton": "创建", + "editButton": "编辑", + "enableButton": "启用", + "disableButton": "禁用", + "updateActionItemCategory": "更新", + "actionItemCategoryName": "名称", + "actionItemCategoryDetails": "措施项类别详细信息", + "enterName": "输入名称", + "successfulCreation": "已成功创建措施项类别", + "successfulUpdation": "措施项类别已成功更新", + "sameNameConflict": "请更改名称以进行更新", + "categoryEnabled": "已启用措施项类别", + "categoryDisabled": "措施项类别已禁用" } } diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index 0d70d585eb..62e305c501 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -231,6 +231,33 @@ export const DELETE_ORGANIZATION_MUTATION = gql` } `; +// to create an action item category + +export const CREATE_ACTION_ITEM_CATEGORY_MUTATION = gql` + mutation CreateActionItemCategory($name: String!, $organizationId: ID!) { + createActionItemCategory(name: $name, organizationId: $organizationId) { + _id + } + } +`; + +// to update an action item category + +export const UPDATE_ACTION_ITEM_CATEGORY_MUTATION = gql` + mutation UpdateActionItemCategory( + $actionItemCategoryId: ID! + $name: String + $isDisabled: Boolean + ) { + updateActionItemCategory( + id: $actionItemCategoryId + data: { name: $name, isDisabled: $isDisabled } + ) { + _id + } + } +`; + // to create the event by any organization export const CREATE_EVENT_MUTATION = gql` diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 8c18ebf93b..6755927e66 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -90,6 +90,18 @@ export const ORGANIZATION_CONNECTION_LIST = gql` } `; +// Query to get the action item category list + +export const ACTION_ITEM_CATEGORY_LIST = gql` + query ActionItemCategoriesByOrganization($organizationId: ID!) { + actionItemCategoriesByOrganization(organizationId: $organizationId) { + _id + name + isDisabled + } + } +`; + // Query to take the User list export const USER_LIST = gql` query Users( diff --git a/src/components/DeleteOrg/DeleteOrg.tsx b/src/components/DeleteOrg/DeleteOrg.tsx index 87614bb121..4c3f2a2078 100644 --- a/src/components/DeleteOrg/DeleteOrg.tsx +++ b/src/components/DeleteOrg/DeleteOrg.tsx @@ -62,7 +62,7 @@ function deleteOrg(): JSX.Element { return ( <> {canDelete && ( - +
{t('deleteOrganization')}
diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategories.module.css b/src/components/OrgActionItemCategories/OrgActionItemCategories.module.css new file mode 100644 index 0000000000..ac9f4a5900 --- /dev/null +++ b/src/components/OrgActionItemCategories/OrgActionItemCategories.module.css @@ -0,0 +1,33 @@ +.addButton { + width: 7em; + position: absolute; + right: 1rem; + top: 1rem; +} + +.createModal { + margin-top: 20vh; + margin-left: 13vw; + max-width: 80vw; +} + +.icon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.message { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.titlemodal { + color: var(--bs-gray-600); + font-weight: 600; + font-size: 20px; + margin-top: 1rem; + width: 65%; +} diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx b/src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx new file mode 100644 index 0000000000..f1ab510369 --- /dev/null +++ b/src/components/OrgActionItemCategories/OrgActionItemCategories.test.tsx @@ -0,0 +1,364 @@ +import React from 'react'; +import { + render, + screen, + fireEvent, + waitFor, + act, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import 'jest-localstorage-mock'; +import { MockedProvider } from '@apollo/client/testing'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import i18nForTest from 'utils/i18nForTest'; +import { toast } from 'react-toastify'; + +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import OrgActionItemCategories from './OrgActionItemCategories'; +import { + MOCKS, + MOCKS_ERROR_QUERY, + MOCKS_ERROR_MUTATIONS, +} from './OrgActionItemCategoryMocks'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_ERROR_QUERY, true); +const link3 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); + +const translations = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.orgActionItemCategories + ) +); + +describe('Testing Action Item Categories Component', () => { + test('Component loads correctly', async () => { + window.location.assign('/orgsetting/id=123'); + const { getByText } = render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.createButton)).toBeInTheDocument(); + }); + }); + + test('render error component on unsuccessful query', async () => { + window.location.assign('/orgsetting/id=123'); + const { queryByText } = render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + expect(queryByText(translations.createButton)).not.toBeInTheDocument(); + }); + }); + + test('opens and closes create and update modals on button clicks', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click(screen.getByTestId('actionItemCategoryModalOpenBtn')); + userEvent.click(screen.getByTestId('actionItemCategoryModalCloseBtn')); + }); + + await waitFor(() => { + userEvent.click( + screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0] + ); + userEvent.click(screen.getByTestId('actionItemCategoryModalCloseBtn')); + }); + }); + + test('create a new action item category', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click(screen.getByTestId('actionItemCategoryModalOpenBtn')); + userEvent.type( + screen.getByPlaceholderText(translations.enterName), + 'ActionItemCategory 4' + ); + + userEvent.click(screen.getByTestId('formSubmitButton')); + }); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.successfulCreation); + }); + }); + + test('toast error on unsuccessful creation', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click(screen.getByTestId('actionItemCategoryModalOpenBtn')); + userEvent.type( + screen.getByPlaceholderText(translations.enterName), + 'ActionItemCategory 4' + ); + + userEvent.click(screen.getByTestId('formSubmitButton')); + }); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); + + test('update an action item category', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click( + screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0] + ); + + const name = screen.getByPlaceholderText(translations.enterName); + fireEvent.change(name, { target: { value: '' } }); + + userEvent.type( + screen.getByPlaceholderText(translations.enterName), + 'ActionItemCategory 1 updated' + ); + + userEvent.click(screen.getByTestId('formSubmitButton')); + }); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.successfulUpdation); + }); + }); + + test('toast error on unsuccessful updation', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click( + screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0] + ); + + const name = screen.getByPlaceholderText(translations.enterName); + fireEvent.change(name, { target: { value: '' } }); + + userEvent.type( + screen.getByPlaceholderText(translations.enterName), + 'ActionItemCategory 1 updated' + ); + + userEvent.click(screen.getByTestId('formSubmitButton')); + }); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); + + test('toast error on providing the same name on updation', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click( + screen.getAllByTestId('actionItemCategoryUpdateModalOpenBtn')[0] + ); + + const name = screen.getByPlaceholderText(translations.enterName); + fireEvent.change(name, { target: { value: '' } }); + + userEvent.type( + screen.getByPlaceholderText(translations.enterName), + 'ActionItemCategory 1' + ); + + userEvent.click(screen.getByTestId('formSubmitButton')); + }); + + await waitFor(() => { + expect(toast.error).toBeCalledWith(translations.sameNameConflict); + }); + }); + + test('toggle the disablity status of an action item category', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click(screen.getAllByTestId('disabilityStatusButton')[0]); + }); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.categoryDisabled); + }); + + await waitFor(() => { + userEvent.click(screen.getAllByTestId('disabilityStatusButton')[1]); + }); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.categoryEnabled); + }); + }); + + test('toast error on unsuccessful toggling of the disablity status', async () => { + window.location.assign('/orgsetting/id=123'); + render( + + + + + {} + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click(screen.getAllByTestId('disabilityStatusButton')[0]); + }); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + + await waitFor(() => { + userEvent.click(screen.getAllByTestId('disabilityStatusButton')[1]); + }); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategories.tsx b/src/components/OrgActionItemCategories/OrgActionItemCategories.tsx new file mode 100644 index 0000000000..3661fa0576 --- /dev/null +++ b/src/components/OrgActionItemCategories/OrgActionItemCategories.tsx @@ -0,0 +1,284 @@ +import type { ChangeEvent } from 'react'; +import React, { useState } from 'react'; +import { Button, Form, Modal } from 'react-bootstrap'; +import styles from './OrgActionItemCategories.module.css'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { WarningAmberRounded } from '@mui/icons-material'; + +import { useMutation, useQuery } from '@apollo/client'; +import { + CREATE_ACTION_ITEM_CATEGORY_MUTATION, + UPDATE_ACTION_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/mutations'; +import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; +import type { InterfaceActionItemCategoryList } from 'utils/interfaces'; +import Loader from 'components/Loader/Loader'; + +type ModalType = 'Create' | 'Update'; + +const OrgActionItemCategories = (): any => { + const { t } = useTranslation('translation', { + keyPrefix: 'orgActionItemCategories', + }); + + const [modalIsOpen, setModalIsOpen] = useState(false); + const [modalType, setModalType] = useState('Create'); + const [categoryId, setCategoryId] = useState(''); + + const [name, setName] = useState(''); + const [currName, setCurrName] = useState(''); + + const currentUrl = window.location.href.split('=')[1]; + + const { + data, + loading, + error, + refetch, + }: { + data: InterfaceActionItemCategoryList | undefined; + loading: boolean; + error?: Error | undefined; + refetch: any; + } = useQuery(ACTION_ITEM_CATEGORY_LIST, { + variables: { + organizationId: currentUrl, + }, + notifyOnNetworkStatusChange: true, + }); + + const [createActionItemCategory] = useMutation( + CREATE_ACTION_ITEM_CATEGORY_MUTATION + ); + + const [updateActionItemCategory] = useMutation( + UPDATE_ACTION_ITEM_CATEGORY_MUTATION + ); + + const handleCreate = async ( + e: ChangeEvent + ): Promise => { + e.preventDefault(); + try { + await createActionItemCategory({ + variables: { + name, + organizationId: currentUrl, + }, + }); + + setName(''); + refetch(); + + setModalIsOpen(false); + + toast.success(t('successfulCreation')); + } catch (error: any) { + toast.error(error.message); + console.log(error); + } + }; + + const handleEdit = async (e: ChangeEvent): Promise => { + e.preventDefault(); + if (name === currName) { + toast.error(t('sameNameConflict')); + } else { + try { + await updateActionItemCategory({ + variables: { + actionItemCategoryId: categoryId, + name, + }, + }); + + setName(''); + setCategoryId(''); + refetch(); + + setModalIsOpen(false); + + toast.success(t('successfulUpdation')); + } catch (error: any) { + toast.error(error.message); + console.log(error); + } + } + }; + + const handleStatusChange = async ( + id: string, + disabledStatus: boolean + ): Promise => { + try { + await updateActionItemCategory({ + variables: { + actionItemCategoryId: id, + isDisabled: !disabledStatus, + }, + }); + + refetch(); + + toast.success( + disabledStatus ? t('categoryEnabled') : t('categoryDisabled') + ); + } catch (error: any) { + toast.error(error.message); + console.log(error); + } + }; + + const showCreateModal = (): void => { + setModalType('Create'); + setModalIsOpen(true); + }; + + const showUpdateModal = (name: string, id: string): void => { + setCurrName(name); + setName(name); + setCategoryId(id); + setModalType('Update'); + setModalIsOpen(true); + }; + + const hideModal = (): void => { + setName(''); + setCategoryId(''); + setModalIsOpen(false); + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+ +
+ Error occured while loading Action Item Categories Data +
+ {`${error.message}`} +
+
+ ); + } + + const actionItemCategories = data?.actionItemCategoriesByOrganization; + + return ( + <> + + +
+ {actionItemCategories?.map((category, index) => { + return ( +
+
+
+ {category.name} +
+
+ + +
+
+ + {index !== actionItemCategories.length - 1 &&
} +
+ ); + })} +
+ + + +

+ {t('actionItemCategoryDetails')} +

+ +
+ +
+ + {t('actionItemCategoryName')} + + { + setName(e.target.value); + }} + /> + + +
+
+ + ); +}; + +export default OrgActionItemCategories; diff --git a/src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts b/src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts new file mode 100644 index 0000000000..b03e53c5f5 --- /dev/null +++ b/src/components/OrgActionItemCategories/OrgActionItemCategoryMocks.ts @@ -0,0 +1,174 @@ +import { + CREATE_ACTION_ITEM_CATEGORY_MUTATION, + UPDATE_ACTION_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/mutations'; + +import { ACTION_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; + +export const MOCKS = [ + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: '1', + name: 'ActionItemCategory 1', + isDisabled: false, + }, + { + _id: '2', + name: 'ActionItemCategory 2', + isDisabled: true, + }, + { + _id: '3', + name: 'ActionItemCategory 3', + isDisabled: false, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { name: 'ActionItemCategory 4', organizationId: '123' }, + }, + result: { + data: { + createActionItemCategory: { + _id: '4', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'ActionItemCategory 1 updated', + actionItemCategoryId: '1', + }, + }, + result: { + data: { + updateActionItemCategory: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + isDisabled: true, + actionItemCategoryId: '1', + }, + }, + result: { + data: { + updateActionItemCategory: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + isDisabled: false, + actionItemCategoryId: '2', + }, + }, + result: { + data: { + updateActionItemCategory: { + _id: '2', + }, + }, + }, + }, +]; + +export const MOCKS_ERROR_QUERY = [ + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + error: new Error('Mock Graphql Error'), + }, +]; + +export const MOCKS_ERROR_MUTATIONS = [ + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: '1', + name: 'ActionItemCategory 1', + isDisabled: false, + }, + { + _id: '2', + name: 'ActionItemCategory 2', + isDisabled: true, + }, + { + _id: '3', + name: 'ActionItemCategory 3', + isDisabled: false, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { name: 'ActionItemCategory 4', organizationId: '123' }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + name: 'ActionItemCategory 1 updated', + actionItemCategoryId: '1', + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + isDisabled: true, + actionItemCategoryId: '1', + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: UPDATE_ACTION_ITEM_CATEGORY_MUTATION, + variables: { + isDisabled: false, + actionItemCategoryId: '2', + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/screens/OrgSettings/OrgSettings.module.css b/src/screens/OrgSettings/OrgSettings.module.css index 2b15a2ac0c..6910ff49ad 100644 --- a/src/screens/OrgSettings/OrgSettings.module.css +++ b/src/screens/OrgSettings/OrgSettings.module.css @@ -1,5 +1,10 @@ +.settingsContainer { + min-height: 100vh; +} + .settingsBody { - margin: 2.5rem 0; + min-height: 100vh; + margin: 2.5rem 1rem; } .cardHeader { @@ -23,3 +28,25 @@ margin: 0 0 3rem 0; color: var(--bs-secondary); } + +hr { + border: none; + height: 1px; + background-color: var(--bs-gray-500); +} + +.settingsTabs { + display: none; +} + +@media (min-width: 577px) { + .settingsDropdown { + display: none; + } +} + +@media (min-width: 577px) { + .settingsTabs { + display: block; + } +} diff --git a/src/screens/OrgSettings/OrgSettings.test.tsx b/src/screens/OrgSettings/OrgSettings.test.tsx index 2436043060..245e7dfe17 100644 --- a/src/screens/OrgSettings/OrgSettings.test.tsx +++ b/src/screens/OrgSettings/OrgSettings.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; @@ -12,6 +12,7 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; import OrgSettings from './OrgSettings'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; +import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; const { setItem } = useLocalStorage(); @@ -83,6 +84,18 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const translations = JSON.parse( + JSON.stringify(i18nForTest.getDataByLanguage('en')?.translation.orgSettings) +); + afterEach(() => { localStorage.clear(); }); @@ -111,6 +124,9 @@ describe('Organisation Settings Page', () => { ); + + await wait(); + expect(screen.getAllByText(/Delete Organization/i)).toHaveLength(3); expect( screen.getByText( @@ -121,4 +137,35 @@ describe('Organisation Settings Page', () => { expect(screen.getByText(/Change Language/i)).toBeInTheDocument(); expect(window.location).toBeAt('/orgsetting/id=123'); }); + + test('should render appropriate settings based on the orgSetting state', async () => { + window.location.assign('/orgsetting/id=123'); + setItem('UserType', 'SUPERADMIN'); + + const { queryByText } = render( + + + + + + + + + + ); + + await wait(); + + await waitFor(() => { + userEvent.click(screen.getByTestId('actionItemCategoriesSettings')); + expect( + queryByText(translations.actionItemCategories) + ).toBeInTheDocument(); + }); + + await waitFor(() => { + userEvent.click(screen.getByTestId('generalSettings')); + expect(queryByText(translations.updateOrganization)).toBeInTheDocument(); + }); + }); }); diff --git a/src/screens/OrgSettings/OrgSettings.tsx b/src/screens/OrgSettings/OrgSettings.tsx index 9509719d5f..8dc3db1e22 100644 --- a/src/screens/OrgSettings/OrgSettings.tsx +++ b/src/screens/OrgSettings/OrgSettings.tsx @@ -1,68 +1,148 @@ -import React from 'react'; +import React, { useState } from 'react'; import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; import DeleteOrg from 'components/DeleteOrg/DeleteOrg'; import OrgUpdate from 'components/OrgUpdate/OrgUpdate'; import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; -import { Card, Form } from 'react-bootstrap'; +import { Button, Card, Dropdown, Form } from 'react-bootstrap'; import Col from 'react-bootstrap/Col'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; import styles from './OrgSettings.module.css'; import OrgProfileFieldSettings from 'components/OrgProfileFieldSettings/OrgProfileFieldSettings'; +import OrgActionItemCategories from 'components/OrgActionItemCategories/OrgActionItemCategories'; + +type SettingType = 'general' | 'actionItemCategories'; function orgSettings(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'orgSettings', }); + const orgSettings: SettingType[] = ['general', 'actionItemCategories']; + + const [orgSetting, setOrgSetting] = useState('general'); + document.title = t('title'); const orgId = window.location.href.split('=')[1]; return ( <> - - - -
-
- {t('updateOrganization')} -
-
- - {orgId && } - -
- - - - -
-
{t('otherSettings')}
+
+ + +
+ {orgSettings.map((setting, index) => ( + + ))}
- -
- - {t('changeLanguage')} - - -
-
- - - - -
-
- {t('manageCustomFields')} + + + + {t(orgSetting)} + + + {orgSettings.map((setting, index) => ( + setOrgSetting(setting) + } + className={orgSetting === setting ? 'text-secondary' : ''} + > + {t(setting)} + + ))} + + + + + +
+
+ + + {orgSetting === 'general' && ( + + + +
+
+ {t('updateOrganization')} +
+
+ + {orgId && } + +
+ + + + +
+
{t('otherSettings')}
+
+ +
+ + {t('changeLanguage')} + + +
+
+
+ + + +
+
+ {t('manageCustomFields')} +
+
+ + {orgId && } + +
+ +
+ )} + + {orgSetting === 'actionItemCategories' && ( + +
+
+ {t('actionItemCategories')}
- - {orgId && } - - - -
+
+ {orgId && } +
+ + )} +
); diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index efcca55ca8..8ab012c281 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -13,6 +13,16 @@ export interface InterfaceUserType { }; } +export interface InterfaceActionItemCategoryInfo { + _id: string; + name: string; + isDisabled: boolean; +} + +export interface InterfaceActionItemCategoryList { + actionItemCategoriesByOrganization: InterfaceActionItemCategoryInfo[]; +} + export interface InterfaceOrgConnectionInfoType { _id: string; image: string | null;