diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 6b2e9b40bd..8ee4040568 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -279,6 +279,23 @@ "successfulDeletion": "Action Item deleted successfully", "title": "Action Items" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "Agenda Category Details", + "updateAgendaCategory": "Update Agenda Category", + "title": "Agenda Categories", + "name": "Category", + "description": "Description", + "createdBy": "Created By", + "options": "Options", + "createAgendaCategory": "Create Agenda Category", + "noAgendaCategories": "No Agenda Categories", + "update": "Update", + "agendaCategoryCreated": "Agenda Category created successfully", + "agendaCategoryUpdated": "Agenda Category updated successfully", + "agendaCategoryDeleted": "Agenda Category deleted successfully", + "deleteAgendaCategory": "Delete Agenda Category", + "deleteAgendaCategoryMsg": "Do you want to remove this agenda category?" + }, "eventListCard": { "deleteEvent": "Delete Event", "deleteEventMsg": "Do you want to remove this event?", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 88ca03c90d..cb0c7798d7 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -282,6 +282,23 @@ "successfulDeletion": "Élément d'action supprimé avec succès", "title": "Éléments d'action" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "Détails de la catégorie d'ordre du jour", + "updateAgendaCategory": "Mettre à jour la catégorie d'ordre du jour", + "title": "Catégories d'ordre du jour", + "name": "Catégorie", + "description": "Description", + "createdBy": "Créé par", + "options": "Options", + "createAgendaCategory": "Créer une catégorie d'ordre du jour", + "noAgendaCategories": "Aucune catégorie d'ordre du jour", + "update": "Mettre à jour", + "agendaCategoryCreated": "Catégorie d'ordre du jour créée avec succès", + "agendaCategoryUpdated": "Catégorie d'ordre du jour mise à jour avec succès", + "agendaCategoryDeleted": "Catégorie d'ordre du jour supprimée avec succès", + "deleteAgendaCategory": "Supprimer la catégorie d'ordre du jour", + "deleteAgendaCategoryMsg": "Souhaitez-vous supprimer cette catégorie d'ordre du jour ?" + }, "eventListCard": { "deleteEvent": "Supprimer l'événement", "deleteEventMsg": "Voulez-vous supprimer cet événement ?", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 5a3adc5fa4..69144d09ef 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -282,6 +282,23 @@ "successfulDeletion": "कार्रवाई आइटम सफलतापूर्वक हटा दिया गया", "title": "एक्शन आइटम्स" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "एजेंडा श्रेणी विवरण", + "updateAgendaCategory": "एजेंडा श्रेणी अपडेट करें", + "title": "एजेंडा श्रेणियाँ", + "name": "श्रेणी", + "description": "विवरण", + "createdBy": "द्वारा बनाया गया", + "options": "विकल्प", + "createAgendaCategory": "एजेंडा श्रेणी बनाएं", + "noAgendaCategories": "कोई एजेंडा श्रेणी नहीं", + "update": "अपडेट करें", + "agendaCategoryCreated": "एजेंडा श्रेणी सफलतापूर्वक बनाई गई", + "agendaCategoryUpdated": "एजेंडा श्रेणी सफलतापूर्वक अपडेट की गई", + "agendaCategoryDeleted": "एजेंडा श्रेणी सफलतापूर्वक हटा दी गई", + "deleteAgendaCategory": "एजेंडा श्रेणी हटाएं", + "deleteAgendaCategoryMsg": "क्या आप इस एजेंडा श्रेणी को हटाना चाहते हैं?" + }, "eventListCard": { "deleteEvent": "ईवेंट हटाएँ", "deleteEventMsg": "क्या आप इस ईवेंट को हटाना चाहते हैं?", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 27c21476be..c4ba57ab81 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -398,6 +398,23 @@ "title": "Ítems de acción", "yes": "Sí" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "Detalles de la categoría de la agenda", + "updateAgendaCategory": "Actualizar categoría de la agenda", + "title": "Categorías de la agenda", + "name": "Categoría", + "description": "Descripción", + "createdBy": "Creado por", + "options": "Opciones", + "createAgendaCategory": "Crear categoría de la agenda", + "noAgendaCategories": "No hay categorías de la agenda", + "update": "Actualizar", + "agendaCategoryCreated": "Categoría de la agenda creada exitosamente", + "agendaCategoryUpdated": "Categoría de la agenda actualizada exitosamente", + "agendaCategoryDeleted": "Categoría de la agenda eliminada exitosamente", + "deleteAgendaCategory": "Eliminar categoría de la agenda", + "deleteAgendaCategoryMsg": "¿Desea eliminar esta categoría de la agenda?" + }, "eventListCard": { "location": "Lugar del evento", "deleteEvent": "Eliminar evento", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 06bb5cb398..c29824067c 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -282,6 +282,23 @@ "successfulDeletion": "操作项已成功删除", "title": "行动项目" }, + "organizationAgendaCategory": { + "agendaCategoryDetails": "议程类别详情", + "updateAgendaCategory": "更新议程类别", + "title": "议程类别", + "name": "类别", + "description": "描述", + "createdBy": "创建人", + "options": "选项", + "createAgendaCategory": "创建议程类别", + "noAgendaCategories": "没有议程类别", + "update": "更新", + "agendaCategoryCreated": "议程类别创建成功", + "agendaCategoryUpdated": "议程类别更新成功", + "agendaCategoryDeleted": "议程类别删除成功", + "deleteAgendaCategory": "删除议程类别", + "deleteAgendaCategoryMsg": "是否要删除此议程类别?" + }, "eventListCard": { "deleteEvent": "删除事件", "deleteEventMsg": "您想删除此事件吗?", diff --git a/src/App.tsx b/src/App.tsx index 1d8787e7eb..d2e5d8306e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import OrgList from 'screens/OrgList/OrgList'; import OrgPost from 'screens/OrgPost/OrgPost'; import OrgSettings from 'screens/OrgSettings/OrgSettings'; import OrganizationActionItems from 'screens/OrganizationActionItems/OrganizationActionItems'; +import OrganizationAgendaCategory from 'screens/OrganizationAgendaCategory/OrganizationAgendaCategory'; import OrganizationDashboard from 'screens/OrganizationDashboard/OrganizationDashboard'; import OrganizationEvents from 'screens/OrganizationEvents/OrganizationEvents'; import OrganizaitionFundCampiagn from 'screens/OrganizationFundCampaign/OrganizationFundCampagins'; @@ -135,6 +136,10 @@ function app(): JSX.Element { path="/orgactionitems/:orgId" element={} /> + } + /> } /> checklist \ No newline at end of file diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.module.css b/src/components/AgendaCategory/AgendaCategoryContainer.module.css new file mode 100644 index 0000000000..7ad16b4c7c --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainer.module.css @@ -0,0 +1,20 @@ +.createModal { + margin-top: 20vh; + margin-left: 13vw; + max-width: 80vw; +} + +.titlemodal { + color: var(--bs-gray-600); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--bs-primary); + width: 65%; +} + +.agendaCategoryOptionsButton { + width: 24px; + height: 24px; +} diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx new file mode 100644 index 0000000000..5ddab82aec --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx @@ -0,0 +1,415 @@ +import React from 'react'; +import { + render, + screen, + waitFor, + act, + waitForElementToBeRemoved, + fireEvent, +} 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 { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import AgendaCategoryContainer from './AgendaCategoryContainer'; +import { props, props2 } from './AgendaCategoryContainerProps'; +import { MOCKS, MOCKS_ERROR_MUTATIONS } from './AgendaCategoryContainerMocks'; + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +async function wait(ms = 100): Promise { + await act(async () => { + return new Promise((resolve) => setTimeout(resolve, ms)); + }); +} + +const translations = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.organizationAgendaCategory, + ), +); + +describe('Testing Agenda Category Component', () => { + const formData = { + name: 'AgendaCategory 1 Edited', + description: 'AgendaCategory 1 Description Edited', + }; + + test('component loads correctly with categories', async () => { + render( + + + + + + + + + , + ); + await wait(); + + await waitFor(() => { + expect( + screen.queryByText(translations.noAgendaCategories), + ).not.toBeInTheDocument(); + }); + }); + + test('component loads correctly with no agenda Categories', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.queryByText(translations.noAgendaCategories), + ).toBeInTheDocument(); + }); + }); + + test('opens and closes the update modal correctly', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editAgendCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editAgendCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('updateAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('updateAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('updateAgendaCategoryModalCloseBtn'), + ); + }); + + test('opens and closes the preview modal correctly', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('previewAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('previewAgendaCategoryModalCloseBtn'), + ); + }); + + test('opens and closes the update and delete modals through the preview modal', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteAgendaCategoryModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('deleteAgendaCategoryCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('deleteAgendaCategoryCloseBtn'), + ); + + await waitFor(() => { + expect( + screen.getByTestId('editAgendaCategoryPreviewModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editAgendaCategoryPreviewModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('updateAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('updateAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('updateAgendaCategoryModalCloseBtn'), + ); + }); + + test('updates an agenda category and toasts success', async () => { + render( + + + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editAgendCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editAgendCategoryModalBtn')[0]); + + const name = screen.getByPlaceholderText(translations.name); + const description = screen.getByPlaceholderText(translations.description); + + fireEvent.change(name, { target: { value: '' } }); + userEvent.type(name, formData.name); + + fireEvent.change(description, { target: { value: '' } }); + userEvent.type(description, formData.description); + + await waitFor(() => { + expect(screen.getByTestId('editAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.agendaCategoryUpdated); + }); + }); + + test('toasts error on unsuccessful updation', async () => { + render( + + + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editAgendCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editAgendCategoryModalBtn')[0]); + + const nameInput = screen.getByLabelText(translations.name); + const descriptionInput = screen.getByLabelText(translations.description); + fireEvent.change(nameInput, { target: { value: '' } }); + fireEvent.change(descriptionInput, { + target: { value: '' }, + }); + userEvent.type(nameInput, formData.name); + userEvent.type(descriptionInput, formData.description); + + await waitFor(() => { + expect(screen.getByTestId('editAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); + + test('deletes the agenda category and toasts success', async () => { + render( + + + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteAgendaCategoryModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('deleteAgendaCategoryCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + userEvent.click(screen.getByTestId('deleteAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.agendaCategoryDeleted); + }); + }); + + test('toasts error on unsuccessful deletion', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('previewAgendaCategoryModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('previewAgendaCategoryModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteAgendaCategoryModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('deleteAgendaCategoryCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteAgendaCategoryBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.tsx new file mode 100644 index 0000000000..e803e77172 --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainer.tsx @@ -0,0 +1,272 @@ +import React, { useState } from 'react'; +import type { ChangeEvent } from 'react'; +import { Button, Col, Row } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { useMutation } from '@apollo/client'; + +import { + DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/mutations'; +import type { InterfaceAgendaItemCategoryInfo } from 'utils/interfaces'; +import styles from './AgendaCategoryContainer.module.css'; + +import AgendaCategoryDeleteModal from 'screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal'; +import AgendaCategoryPreviewModal from 'screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal'; +import AgendaCategoryUpdateModal from 'screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal'; + +function agendaCategoryContainer({ + agendaCategoryConnection, + agendaCategoryData, + agendaCategoryRefetch, +}: { + agendaCategoryConnection: 'Organization'; + agendaCategoryData: InterfaceAgendaItemCategoryInfo[] | undefined; + agendaCategoryRefetch: () => void; +}): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationAgendaCategory', + }); + const { t: tCommon } = useTranslation('common'); + const [ + agendaCategoryPreviewModalIsOpen, + setAgendaCategoryPreviewModalIsOpen, + ] = useState(false); + const [agendaCategoryUpdateModalIsOpen, setAgendaCategoryUpdateModalIsOpen] = + useState(false); + const [agendaCategoryDeleteModalIsOpen, setAgendaCategoryDeleteModalIsOpen] = + useState(false); + + const [agendaCategoryId, setAgendaCategoryId] = useState(''); + + const [formState, setFormState] = useState({ + name: '', + description: '', + createdBy: '', + }); + + const showPreviewModal = ( + agendaItemCategory: InterfaceAgendaItemCategoryInfo, + ): void => { + setAgendaCategoryState(agendaItemCategory); + setAgendaCategoryPreviewModalIsOpen(true); + }; + + const hidePreviewModal = (): void => { + setAgendaCategoryPreviewModalIsOpen(false); + }; + + const showUpdateModal = (): void => { + setAgendaCategoryUpdateModalIsOpen(!agendaCategoryUpdateModalIsOpen); + }; + + const hideUpdateModal = (): void => { + setAgendaCategoryUpdateModalIsOpen(!agendaCategoryUpdateModalIsOpen); + }; + + const toggleDeleteModal = (): void => { + setAgendaCategoryDeleteModalIsOpen(!agendaCategoryDeleteModalIsOpen); + }; + + const [updateAgendaCategory] = useMutation( + UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + ); + + const updateAgendaCategoryHandler = async ( + event: ChangeEvent, + ): Promise => { + event.preventDefault(); + try { + await updateAgendaCategory({ + variables: { + updateAgendaCategoryId: agendaCategoryId, + input: { + name: formState.name, + description: formState.description, + }, + }, + }); + + agendaCategoryRefetch(); + hideUpdateModal(); + toast.success(t('agendaCategoryUpdated')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(`Agenda Category Update Failed ${error.message}`); + } + } + }; + + const [deleteAgendaCategory] = useMutation( + DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + ); + + const deleteAgendaCategoryHandler = async (): Promise => { + try { + await deleteAgendaCategory({ + variables: { + deleteAgendaCategoryId: agendaCategoryId, + }, + }); + agendaCategoryRefetch(); + toggleDeleteModal(); + toast.success(t('agendaCategoryDeleted')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(`Agenda Category Delete Failed, ${error.message}`); + } + } + }; + + const handleEditClick = ( + agendaItemCategory: InterfaceAgendaItemCategoryInfo, + ): void => { + setAgendaCategoryState(agendaItemCategory); + showUpdateModal(); + }; + + const setAgendaCategoryState = ( + agendaItemCategory: InterfaceAgendaItemCategoryInfo, + ): void => { + setFormState({ + ...formState, + name: `${agendaItemCategory.name} `, + description: `${agendaItemCategory.description}`, + createdBy: `${agendaItemCategory.createdBy.firstName} ${agendaItemCategory.createdBy.lastName}`, + }); + setAgendaCategoryId(agendaItemCategory._id); + }; + + return ( + <> +
+
+ + +
{t('name')}
+ + + {t('description')} + + +
{t('createdBy')}
+ + +
{t('options')}
+ +
+
+
+ {agendaCategoryData?.map((agendaCategory, index) => ( +
+ + + {`${agendaCategory.name}`} + + + {agendaCategory.description} + + + {`${agendaCategory.createdBy.firstName} ${agendaCategory.createdBy.lastName}`} + + + +
+ + +
+ +
+ + {index !== agendaCategoryData.length - 1 && ( +
+ )} +
+ ))} + {agendaCategoryData?.length === 0 && ( +
+ {t('noAgendaCategories')} +
+ )} +
+
+ + {/* Preview model */} + + {/* Update model */} + + {/* Delete model */} + + + ); +} + +export default agendaCategoryContainer; diff --git a/src/components/AgendaCategory/AgendaCategoryContainerMocks.ts b/src/components/AgendaCategory/AgendaCategoryContainerMocks.ts new file mode 100644 index 0000000000..f5dee5cffd --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainerMocks.ts @@ -0,0 +1,104 @@ +import { + UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + DELETE_AGENDA_ITEM_CATEGORY_MUTATION, +} from 'GraphQl/Mutations/AgendaCategoryMutations'; + +export const MOCKS = [ + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory1', + input: { + name: 'AgendaCategory 1 Edited', + description: 'AgendaCategory 1 Description Edited', + }, + }, + }, + result: { + data: { + updateAgendaCategory: { + _id: 'agendaCategory1', + }, + }, + }, + }, + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory1', + input: { + name: 'AgendaCategory 1', + description: 'AgendaCategory 1 Description', + }, + }, + }, + result: { + data: { + updateAgendaCategory: { + _id: 'agendaCategory1', + }, + }, + }, + }, + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory2', + input: { + name: 'AgendaCategory 2 edited', + description: 'AgendaCategory 2 Description', + }, + }, + }, + result: { + data: { + updateAgendaCategory: { + _id: 'agendaCategory2', + }, + }, + }, + }, + { + request: { + query: DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + deleteAgendaCategoryId: 'agendaCategory1', + }, + }, + result: { + data: { + deleteAgendaCategory: { + _id: 'agendaCategory1', + }, + }, + }, + }, +]; + +export const MOCKS_ERROR_MUTATIONS = [ + { + request: { + query: UPDATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + updateAgendaCategoryId: 'agendaCategory1', + input: { + name: 'AgendaCategory 1 Edited', + description: 'AgendaCategory 1 Description Edited', + }, + }, + }, + error: new Error('Mock Graphql Error'), + }, + { + request: { + query: DELETE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + deleteAgendaCategoryId: 'agendaCategory1', + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/components/AgendaCategory/AgendaCategoryContainerProps.ts b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts new file mode 100644 index 0000000000..5181eec153 --- /dev/null +++ b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts @@ -0,0 +1,34 @@ +type AgendaCategoryConnectionType = 'Organization'; + +export const props = { + agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, + agendaCategoryData: [ + { + _id: 'agendaCategory1', + name: 'AgendaCategory 1', + description: 'AgendaCategory 1 Description', + createdBy: { + _id: 'user0', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + { + _id: 'agendaCategory2', + name: 'AgendaCategory 2', + description: 'AgendaCategory 2 Description', + createdBy: { + _id: 'user0', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + }, + ], + agendaCategoryRefetch: jest.fn(), +}; + +export const props2 = { + agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, + agendaCategoryData: [], + agendaCategoryRefetch: jest.fn(), +}; diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index d65982edef..17f9af8b32 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -1,5 +1,6 @@ import { QuestionMarkOutlined } from '@mui/icons-material'; import { ReactComponent as ActionItemIcon } from 'assets/svgs/actionItem.svg'; +import { ReactComponent as AgendaCategoryIcon } from 'assets/svgs/agenda-category-icon.svg'; import { ReactComponent as BlockUserIcon } from 'assets/svgs/blockUser.svg'; import { ReactComponent as CheckInRegistrantsIcon } from 'assets/svgs/checkInRegistrants.svg'; import { ReactComponent as DashboardIcon } from 'assets/svgs/dashboard.svg'; @@ -14,8 +15,8 @@ import { ReactComponent as PostsIcon } from 'assets/svgs/posts.svg'; import { ReactComponent as SettingsIcon } from 'assets/svgs/settings.svg'; import { ReactComponent as VenueIcon } from 'assets/svgs/venues.svg'; import { ReactComponent as RequestsIcon } from 'assets/svgs/requests.svg'; -import { ReactComponent as HomeIcon } from 'assets/svgs/home.svg'; -import { ReactComponent as DonateIcon } from 'assets/svgs/donate.svg'; +// import { ReactComponent as HomeIcon } from 'assets/svgs/home.svg'; +// import { ReactComponent as DonateIcon } from 'assets/svgs/donate.svg'; import React from 'react'; @@ -54,6 +55,13 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { data-testid="Icon-Component-ActionItemIcon" /> ); + case 'Agenda Items Category': + return ( + + ); case 'Posts': return ; case 'Block/Unblock': diff --git a/src/components/OrganizationScreen/OrganizationScreen.tsx b/src/components/OrganizationScreen/OrganizationScreen.tsx index 59b6f289c9..a8f9d16b9c 100644 --- a/src/components/OrganizationScreen/OrganizationScreen.tsx +++ b/src/components/OrganizationScreen/OrganizationScreen.tsx @@ -112,6 +112,7 @@ const map: InterfaceMapType = { member: 'memberDetail', orgevents: 'organizationEvents', orgactionitems: 'organizationActionItems', + orgagendacategory: 'organizationAgendaCategory', orgcontribution: 'orgContribution', orgpost: 'orgPost', orgfunds: 'funds', diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx new file mode 100644 index 0000000000..da92dfd201 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.test.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; + +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; + +const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', +}; +const mockHideCreateModal = jest.fn(); +const mockSetFormState = jest.fn(); +const mockCreateAgendaCategoryHandler = jest.fn(); +const mockT = (key: string): string => key; + +describe('AgendaCategoryCreateModal', () => { + test('renders modal correctly', () => { + render( + + + + + + + + + + + , + ); + + expect(screen.getByText('agendaCategoryDetails')).toBeInTheDocument(); + expect( + screen.getByTestId('createAgendaCategoryFormSubmitBtn'), + ).toBeInTheDocument(); + expect( + screen.getByTestId('createAgendaCategoryModalCloseBtn'), + ).toBeInTheDocument(); + }); + test('tests the condition for formState.name and formState.description', () => { + const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', + }; + render( + + + + + + + + + + + , + ); + const nameInput = screen.getByLabelText('name'); + fireEvent.change(nameInput, { + target: { value: 'New name' }, + }); + const descriptionInput = screen.getByLabelText('description'); + fireEvent.change(descriptionInput, { + target: { value: 'New description' }, + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + name: 'New name', + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + description: 'New description', + }); + }); + test('calls createAgendaCategoryHandler when form is submitted', () => { + render( + + + + + + + + + + + , + ); + + fireEvent.submit(screen.getByTestId('createAgendaCategoryFormSubmitBtn')); + expect(mockCreateAgendaCategoryHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx new file mode 100644 index 0000000000..9b260d877f --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryCreateModal.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import type { ChangeEvent } from 'react'; +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceFormStateType { + name: string; + description: string; + createdBy: string; +} + +interface InterfaceAgendaCategoryCreateModalProps { + agendaCategoryCreateModalIsOpen: boolean; + hideCreateModal: () => void; + formState: InterfaceFormStateType; + setFormState: (state: React.SetStateAction) => void; + createAgendaCategoryHandler: ( + e: ChangeEvent, + ) => Promise; + t: (key: string) => string; +} + +const AgendaCategoryCreateModal: React.FC< + InterfaceAgendaCategoryCreateModalProps +> = ({ + agendaCategoryCreateModalIsOpen, + hideCreateModal, + formState, + setFormState, + createAgendaCategoryHandler, + t, +}) => { + return ( + + +

{t('agendaCategoryDetails')}

+ +
+ +
+ + {t('name')} + + setFormState({ ...formState, name: e.target.value }) + } + /> + + + {t('description')} + + setFormState({ ...formState, description: e.target.value }) + } + /> + + +
+
+
+ ); +}; + +export default AgendaCategoryCreateModal; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx new file mode 100644 index 0000000000..f2edb8fc62 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryDeleteModal.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Modal, Button } from 'react-bootstrap'; +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceAgendaCategoryDeleteModalProps { + agendaCategoryDeleteModalIsOpen: boolean; + toggleDeleteModal: () => void; + deleteAgendaCategoryHandler: () => Promise; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +const AgendaCategoryDeleteModal: React.FC< + InterfaceAgendaCategoryDeleteModalProps +> = ({ + agendaCategoryDeleteModalIsOpen, + toggleDeleteModal, + deleteAgendaCategoryHandler, + t, + tCommon, +}) => { + return ( + + + + {t('deleteAgendaCategory')} + + + +

{t('deleteAgendaCategoryMsg')}

+
+ + + + +
+ ); +}; + +export default AgendaCategoryDeleteModal; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx new file mode 100644 index 0000000000..c9dc17e56f --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryPreviewModal.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; + +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceFormStateType { + name: string; + description: string; + createdBy: string; +} + +interface InterfaceAgendaCategoryPreviewModalProps { + agendaCategoryPreviewModalIsOpen: boolean; + hidePreviewModal: () => void; + showUpdateModal: () => void; + toggleDeleteModal: () => void; + formState: InterfaceFormStateType; + + t: (key: string) => string; +} + +const AgendaCategoryPreviewModal: React.FC< + InterfaceAgendaCategoryPreviewModalProps +> = ({ + agendaCategoryPreviewModalIsOpen, + hidePreviewModal, + showUpdateModal, + toggleDeleteModal, + formState, + t, +}) => { + return ( + + +

{t('agendaCategoryDetails')}

+ +
+ +
+
+

+ {t('name')} + {formState.name} +

+

+ {t('description')} + {formState.description} +

+

+ {t('createdBy')} + {formState.createdBy} +

+
+
+ + +
+
+
+
+ ); +}; + +export default AgendaCategoryPreviewModal; diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx new file mode 100644 index 0000000000..168b97abd3 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.test.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; + +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import AgendaCategoryUpdateModal from './AgendaCategoryUpdateModal'; + +const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', +}; +const mockHideUpdateModal = jest.fn(); +const mockSetFormState = jest.fn(); +const mockUpdateAgendaCategoryHandler = jest.fn(); +const mockT = (key: string): string => key; + +describe('AgendaCategoryUpdateModal', () => { + test('renders modal correctly', () => { + render( + + + + + + + + + + + , + ); + + expect(screen.getByText('updateAgendaCategory')).toBeInTheDocument(); + expect(screen.getByTestId('editAgendaCategoryBtn')).toBeInTheDocument(); + expect( + screen.getByTestId('updateAgendaCategoryModalCloseBtn'), + ).toBeInTheDocument(); + }); + + test('calls hideUpdateModal when close button is clicked', () => { + render( + + + + + + + + + + + , + ); + + userEvent.click(screen.getByTestId('updateAgendaCategoryModalCloseBtn')); + expect(mockHideUpdateModal).toHaveBeenCalledTimes(1); + }); + + test('tests the condition for formState.name and formState.description', () => { + const mockFormState = { + name: 'Test Name', + description: 'Test Description', + createdBy: 'Test User', + }; + render( + + + + + + + + + + + , + ); + const nameInput = screen.getByLabelText('name'); + fireEvent.change(nameInput, { + target: { value: 'New name' }, + }); + const descriptionInput = screen.getByLabelText('description'); + fireEvent.change(descriptionInput, { + target: { value: 'New description' }, + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + name: 'New name', + }); + expect(mockSetFormState).toHaveBeenCalledWith({ + ...mockFormState, + description: 'New description', + }); + }); + + test('calls updateAgendaCategoryHandler when form is submitted', () => { + render( + + + + + + + + + + + , + ); + + fireEvent.submit(screen.getByTestId('editAgendaCategoryBtn')); + expect(mockUpdateAgendaCategoryHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx new file mode 100644 index 0000000000..868205c339 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/AgendaCategoryUpdateModal.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import type { ChangeEvent } from 'react'; + +import styles from './OrganizationAgendaCategory.module.css'; + +interface InterfaceFormStateType { + name: string; + description: string; + createdBy: string; +} + +interface InterfaceAgendaCategoryUpdateModalProps { + agendaCategoryUpdateModalIsOpen: boolean; + hideUpdateModal: () => void; + formState: InterfaceFormStateType; + setFormState: (state: React.SetStateAction) => void; + updateAgendaCategoryHandler: ( + e: ChangeEvent, + ) => Promise; + t: (key: string) => string; +} + +const AgendaCategoryUpdateModal: React.FC< + InterfaceAgendaCategoryUpdateModalProps +> = ({ + agendaCategoryUpdateModalIsOpen, + hideUpdateModal, + formState, + setFormState, + updateAgendaCategoryHandler, + t, +}) => { + return ( + + +

{t('updateAgendaCategory')}

+ +
+ +
+ + {t('name')} + + setFormState({ ...formState, name: e.target.value }) + } + /> + + + {t('description')} + + setFormState({ ...formState, description: e.target.value }) + } + /> + + +
+
+
+ ); +}; + +export default AgendaCategoryUpdateModal; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css new file mode 100644 index 0000000000..13e187f8b5 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.module.css @@ -0,0 +1,171 @@ +.agendaCategoryContainer { + height: 90vh; +} + +.agendaCategoryModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.btnsContainer { + display: flex; + gap: 10px; +} + +.btnsContainer .btnsBlock { + display: flex; + gap: 10px; +} + +.btnsContainer button { + display: flex; + align-items: center; +} + +.container { + min-height: 100vh; +} + +.datediv { + display: flex; + flex-direction: row; +} + +.datebox { + width: 90%; + border-radius: 7px; + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.dropdown { + display: block; +} + +.dropdownToggle { + margin-bottom: 0; + display: flex; +} + +.dropdownModalToggle { + width: 50%; +} + +.errorIcon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.greenregbtn { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid var(--bs-gray-300); + box-shadow: 0 2px 2px var(--bs-gray-300); + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-primary); + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + width: 100%; +} + +h2 { + margin-top: 0.5rem; +} + +hr { + border: none; + height: 1px; + background-color: var(--bs-gray-500); + margin: 1rem; +} + +.iconContainer { + display: flex; + justify-content: flex-end; +} +.icon { + margin: 1px; +} + +.message { + margin-top: 25%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.organizationAgendaCategoryContainer h2 { + margin: 0.6rem 0; +} + +.preview { + display: flex; + flex-direction: row; + font-weight: 900; + font-size: 16px; + color: rgb(80, 80, 80); +} + +.removeFilterIcon { + cursor: pointer; +} + +.searchForm { + display: inline; +} + +.titlemodal { + color: var(--bs-gray-600); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--bs-primary); + width: 65%; +} + +.view { + margin-left: 2%; + font-weight: 600; + font-size: 16px; + color: var(--bs-gray-600); +} + +@media (max-width: 767px) { + .btnsContainer { + margin-bottom: 0; + display: flex; + flex-direction: column; + } + + .btnsContainer .btnsBlock .dropdownToggle { + flex-grow: 1; + } + + .btnsContainer button { + width: 100%; + } + + .createAgendaCategoryButton { + position: absolute; + top: 1rem; + right: 2rem; + } +} diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx new file mode 100644 index 0000000000..732db13a74 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.test.tsx @@ -0,0 +1,235 @@ +import React from 'react'; +import { + render, + screen, + waitFor, + act, + waitForElementToBeRemoved, +} 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 i18n from 'utils/i18nForTest'; +import { toast } from 'react-toastify'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import OrganizationAgendaCategory from './OrganizationAgendaCategory'; +import { + MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, + MOCKS_ERROR_MUTATION, +} from './OrganizationAgendaCategoryErrorMocks'; +import { MOCKS } from './OrganizationAgendaCategoryMocks'; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: '123' }), +})); + +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_AGENDA_ITEM_CATEGORY_LIST_QUERY, + true, +); +const link3 = new StaticMockLink(MOCKS_ERROR_MUTATION, true); + +const translations = { + ...JSON.parse( + JSON.stringify( + i18n.getDataByLanguage('en')?.translation.organizationAgendaCategory ?? + {}, + ), + ), +}; + +describe('Testing Agenda Categories Component', () => { + const formData = { + name: 'Category', + description: 'Test Description', + createdBy: 'Test User', + }; + test('Component loads correctly', async () => { + const { getByText } = render( + + + + + {} + + + + , + ); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.createAgendaCategory)).toBeInTheDocument(); + }); + }); + + test('render error component on unsuccessful agenda category list query', async () => { + const { queryByText } = render( + + + + + {} + + + + , + ); + + await wait(); + + await waitFor(() => { + expect( + queryByText(translations.createAgendaCategory), + ).not.toBeInTheDocument(); + }); + }); + + test('opens and closes the create agenda category modal', async () => { + render( + + + + + + {} + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('createAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createAgendaCategoryBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('createAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createAgendaCategoryModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('createAgendaCategoryModalCloseBtn'), + ); + }); + test('creates new agenda cagtegory', async () => { + render( + + + + + + {} + + + + + , + ); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('createAgendaCategoryBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createAgendaCategoryBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('createAgendaCategoryModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + userEvent.type( + screen.getByPlaceholderText(translations.name), + formData.name, + ); + + userEvent.type( + screen.getByPlaceholderText(translations.description), + formData.description, + ); + userEvent.click(screen.getByTestId('createAgendaCategoryFormSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.agendaCategoryCreated); + }); + }); + + // test('toasts error on unsuccessful creation', async () => { + // render( + // + // + // + // + // + // {} + // + // + // + // + // , + // ); + + // await wait(); + + // await waitFor(() => { + // expect(screen.getByTestId('createAgendaCategoryBtn')).toBeInTheDocument(); + // }); + // userEvent.click(screen.getByTestId('createAgendaCategoryBtn')); + + // await waitFor(() => { + // return expect( + // screen.findByTestId('createAgendaCategoryModalCloseBtn'), + // ).resolves.toBeInTheDocument(); + // }); + + // userEvent.type( + // screen.getByPlaceholderText(translations.name), + // formData.name, + // ); + + // userEvent.type( + // screen.getByPlaceholderText(translations.description), + // formData.description, + // ); + // userEvent.click(screen.getByTestId('createAgendaCategoryFormSubmitBtn')); + + // await waitFor(() => { + // expect(toast.error).toBeCalledWith(); + // }); + // }); +}); diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx new file mode 100644 index 0000000000..4216d309a2 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategory.tsx @@ -0,0 +1,157 @@ +import React, { useState } from 'react'; +import type { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button } from 'react-bootstrap'; +import { useParams } from 'react-router-dom'; + +import { WarningAmberRounded } from '@mui/icons-material'; +import { toast } from 'react-toastify'; + +import { useMutation, useQuery } from '@apollo/client'; +import { AGENDA_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/Queries'; +import { CREATE_AGENDA_ITEM_CATEGORY_MUTATION } from 'GraphQl/Mutations/mutations'; + +import type { InterfaceAgendaItemCategoryList } from 'utils/interfaces'; +import AgendaCategoryContainer from 'components/AgendaCategory/AgendaCategoryContainer'; +import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; +import styles from './OrganizationAgendaCategory.module.css'; +import Loader from 'components/Loader/Loader'; + +function organizationAgendaCategory(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationAgendaCategory', + }); + + const { orgId: currentUrl } = useParams(); + + const [agendaCategoryCreateModalIsOpen, setAgendaCategoryCreateModalIsOpen] = + useState(false); + + const [formState, setFormState] = useState({ + name: '', + description: '', + createdBy: '', + }); + + const { + data: agendaCategoryData, + loading: agendaCategoryLoading, + error: agendaCategoryError, + refetch: refetchAgendaCategory, + }: { + data: InterfaceAgendaItemCategoryList | undefined; + loading: boolean; + error?: unknown | undefined; + refetch: () => void; + } = useQuery(AGENDA_ITEM_CATEGORY_LIST, { + variables: { organizationId: currentUrl }, + notifyOnNetworkStatusChange: true, + }); + + const [createAgendaCategory] = useMutation( + CREATE_AGENDA_ITEM_CATEGORY_MUTATION, + ); + + const createAgendaCategoryHandler = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + await createAgendaCategory({ + variables: { + input: { + organizationId: currentUrl, + name: formState.name, + description: formState.description, + }, + }, + }); + toast.success(t('agendaCategoryCreated')); + setFormState({ name: '', description: '', createdBy: '' }); + refetchAgendaCategory(); + hideCreateModal(); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + const showCreateModal = (): void => { + setAgendaCategoryCreateModalIsOpen(!agendaCategoryCreateModalIsOpen); + }; + + const hideCreateModal = (): void => { + setAgendaCategoryCreateModalIsOpen(!agendaCategoryCreateModalIsOpen); + }; + + if (agendaCategoryLoading) return ; + + if (agendaCategoryError) { + return ( +
+
+ +
+ Error occured while loading{' '} + {agendaCategoryError && 'Agenda Categories'} + Data +
+ {agendaCategoryError && (agendaCategoryError as Error).message} +
+
+
+ ); + } + + return ( +
+
+
+
+
+ {/* setSearchValue(e.target.value)} + value={searchValue} + data-testid="searchAgendaCategories" + /> */} +
+ + +
+
+ +
+ + +
+ +
+ ); +} + +export default organizationAgendaCategory; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts new file mode 100644 index 0000000000..10809c3da8 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryErrorMocks.ts @@ -0,0 +1,51 @@ +import { CREATE_AGENDA_ITEM_CATEGORY_MUTATION } from 'GraphQl/Mutations/AgendaCategoryMutations'; + +import { AGENDA_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/AgendaCategoryQueries'; + +export const MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY = [ + { + request: { + query: AGENDA_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + error: new Error('Mock Graphql Error'), + }, +]; + +export const MOCKS_ERROR_MUTATION = [ + { + request: { + query: AGENDA_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + result: { + data: { + agendaItemCategoriesByOrganization: [ + { + _id: 'agendaItemCategory1', + name: 'Category', + description: 'Test Description', + createdBy: { + _id: 'user1', + firstName: 'Harve', + lastName: 'Lance', + }, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + input: { + organizationId: '123', + name: 'Category', + description: 'Test Description', + }, + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts new file mode 100644 index 0000000000..434b0dcad2 --- /dev/null +++ b/src/screens/OrganizationAgendaCategory/OrganizationAgendaCategoryMocks.ts @@ -0,0 +1,47 @@ +import { CREATE_AGENDA_ITEM_CATEGORY_MUTATION } from 'GraphQl/Mutations/AgendaCategoryMutations'; + +import { AGENDA_ITEM_CATEGORY_LIST } from 'GraphQl/Queries/AgendaCategoryQueries'; + +export const MOCKS = [ + { + request: { + query: AGENDA_ITEM_CATEGORY_LIST, + variables: { organizationId: '123' }, + }, + result: { + data: { + agendaItemCategoriesByOrganization: [ + { + _id: 'agendaItemCategory1', + name: 'Category', + description: 'Test Description', + createdBy: { + _id: 'user1', + firstName: 'Harve', + lastName: 'Lance', + }, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_AGENDA_ITEM_CATEGORY_MUTATION, + variables: { + input: { + organizationId: '123', + name: 'Category', + description: 'Test Description', + }, + }, + }, + result: { + data: { + createAgendaCategory: { + _id: 'agendaItemCategory1', + }, + }, + }, + }, +]; diff --git a/src/state/reducers/routesReducer.test.ts b/src/state/reducers/routesReducer.test.ts index 0a474df0c3..84427ee4d0 100644 --- a/src/state/reducers/routesReducer.test.ts +++ b/src/state/reducers/routesReducer.test.ts @@ -16,6 +16,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/undefined' }, { name: 'Venues', url: '/orgvenues/undefined' }, { name: 'Action Items', url: '/orgactionitems/undefined' }, + { name: 'Agenda Items Category', url: '/orgagendacategory/undefined' }, { name: 'Posts', url: '/orgpost/undefined' }, { name: 'Block/Unblock', @@ -63,6 +64,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { @@ -113,6 +119,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/orgId' }, { name: 'Venues', url: '/orgvenues/orgId' }, { name: 'Action Items', url: '/orgactionitems/orgId' }, + { name: 'Agenda Items Category', url: '/orgagendacategory/orgId' }, { name: 'Posts', url: '/orgpost/orgId' }, { name: 'Block/Unblock', url: '/blockuser/orgId' }, { name: 'Advertisement', url: '/orgads/orgId' }, @@ -157,6 +164,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { @@ -203,6 +215,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/undefined' }, { name: 'Venues', url: '/orgvenues/undefined' }, { name: 'Action Items', url: '/orgactionitems/undefined' }, + { name: 'Agenda Items Category', url: '/orgagendacategory/undefined' }, { name: 'Posts', url: '/orgpost/undefined' }, { name: 'Block/Unblock', @@ -253,6 +266,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { diff --git a/src/state/reducers/routesReducer.ts b/src/state/reducers/routesReducer.ts index 34cfdbbcca..bcda9f02d8 100644 --- a/src/state/reducers/routesReducer.ts +++ b/src/state/reducers/routesReducer.ts @@ -76,6 +76,11 @@ const components: ComponentType[] = [ comp_id: 'orgactionitems', component: 'OrganizationActionItems', }, + { + name: 'Agenda Items Category', + comp_id: 'orgagendacategory', + component: 'OrganizationAgendaCategory', + }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, { name: 'Advertisement', comp_id: 'orgads', component: 'Advertisements' }, diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index abaac2efcc..1d65b63189 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -456,3 +456,18 @@ export interface InterfaceQueryMembershipRequestsListItem { }[]; }[]; } + +export interface InterfaceAgendaItemCategoryInfo { + _id: string; + name: string; + description: string; + createdBy: { + _id: string; + firstName: string; + lastName: string; + }; +} + +export interface InterfaceAgendaItemCategoryList { + agendaItemCategoriesByOrganization: InterfaceAgendaItemCategoryInfo[]; +}