From 2c36281878ca6281b9181cdd9dfbd198cc8ba055 Mon Sep 17 00:00:00 2001 From: Anvita Mahajan <78889572+Anvita0305@users.noreply.github.com> Date: Fri, 12 Apr 2024 22:29:12 +0530 Subject: [PATCH] ADMIN REDESIGN: redesign the Event Management's Action Items tab for the talawa Admin portal (#1895) * redesign action items and added tests * added missing files * added missing files * added missing files * Correct changed files * fixed spelling mistake * udated delete modal and added tests * fixed linting errors * tests * fixed linting * Changed headings and added notes field * fixed linting --- public/locales/en.json | 22 + public/locales/fr.json | 22 + public/locales/hi.json | 22 + public/locales/sp.json | 22 + public/locales/zh.json | 22 + src/GraphQl/Queries/ActionItemQueries.ts | 37 ++ .../EventActionItems.module.css | 172 +++++ .../EventActionItems.test.tsx | 628 ++++++++++++++++++ .../EventActionItems/EventActionItems.tsx | 598 +++++++++++++++++ .../EventManagement/EventManagement.tsx | 3 +- 10 files changed, 1547 insertions(+), 1 deletion(-) create mode 100644 src/components/EventManagement/EventActionItems/EventActionItems.module.css create mode 100644 src/components/EventManagement/EventActionItems/EventActionItems.test.tsx create mode 100644 src/components/EventManagement/EventActionItems/EventActionItems.tsx diff --git a/public/locales/en.json b/public/locales/en.json index 6d2eb3f99c..206a819425 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -1060,5 +1060,27 @@ "passwordNotMatch": "Passwords do not match.", "user": "User", "addMember": "Add Member" + }, + "eventActionItems": { + "title": "Action Items", + "createActionItem": "Create Action Items", + "actionItemCategory": "Action Item Category", + "selectActionItemCategory": "Select an action item category", + "selectAssignee": "Select an assignee", + "preCompletionNotes": "Pre Completion Notes", + "postCompletionNotes": "Post Completion Notes", + "actionItemDetails": "Action Item Details", + "dueDate": "Due Date", + "completionDate": "Completion Date", + "editActionItem": "Edit Action Item", + "deleteActionItem": "Delete Action Item", + "deleteActionItemMsg": "Do you want to remove this action item?", + "yes": "Yes", + "no": "No", + "successfulDeletion": "Action Item deleted successfully", + "successfulCreation": "Action Item created successfully", + "successfulUpdation": "Action Item updated successfully", + "notes": "Notes", + "save": "Save" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index a4678096ab..e79928e4f9 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -1049,5 +1049,27 @@ "passwordNotMatch": "Les mots de passe ne correspondent pas.", "user": "Utilisateur", "addMember": "Ajouter un membre" + }, + "eventActionItems": { + "title": "Éléments d'action", + "createActionItem": "Créer des éléments d'action", + "actionItemCategory": "Catégorie d'éléments d'action", + "selectActionItemCategory": "Sélectionnez une catégorie d'élément d'action", + "selectAssignee": "Sélectionner un cessionnaire", + "preCompletionNotes": "Notes de pré-achèvement", + "postCompletionNotes": "Notes post-achèvement", + "actionItemDetails": "Détails de l'élément d'action", + "dueDate": "Date d'échéance", + "completetionDate": "Date d'achèvement", + "editActionItem": "Modifier l'élément d'action", + "deleteActionItem": "Supprimer l'élément d'action", + "deleteActionItemMsg": "Voulez-vous supprimer cette action ?", + "yes": "Oui", + "no": "non", + "successfulDeletion": "Élément d'action supprimé avec succès", + "successfulCreation": "Élément d'action créé avec succès", + "successfulUpdation": "Élément d'action mis à jour avec succès", + "notes": "Remarques", + "save": "Enregistrer" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 6d33c0aed9..33ebb88122 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -1054,5 +1054,27 @@ "passwordNotMatch": "पासवर्ड मेल नहीं खाते।", "user": "उपयोगकर्ता", "addMember": "सदस्य जोड़ें" + }, + "eventActionItems": { + "title": "कार्रवाई आइटम", + "createActionItem": "क्रिया आइटम बनाएँ", + "actionItemCategory": "एक्शन आइटम श्रेणी", + "selectActionItemCategory": "एक क्रिया आइटम श्रेणी चुनें", + "selectAssignee": "एक असाइनी का चयन करें", + "preCompletionNotes": "पूर्व समापन नोट्स", + "postCompletionNotes": "पोस्टकंप्लीशननोट्स", + "actionItemDetails": "एक्शन आइटम विवरण", + "dueDate": "नियत तिथि", + "completionDate": "समापन तिथि", + "editActionItem": "एक्शन आइटम संपादित करें", + "deleteActionItem": "क्रिया आइटम हटाएं", + "deleteActionItemMsg": "क्या आप इस क्रिया आइटम को हटाना चाहते हैं?", + "yes": "हां", + "no": "नहीं", + "successfulDeletion": "कार्रवाई आइटम सफलतापूर्वक हटा दिया गया", + "successfulCreation": "क्रिया आइटम सफलतापूर्वक बनाया गया", + "successfulUpdation": "कार्रवाई आइटम सफलतापूर्वक अद्यतन किया गया", + "notes": "नोट्स", + "save": "सहेजें" } } diff --git a/public/locales/sp.json b/public/locales/sp.json index 340439bffd..d0e125d562 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -1051,5 +1051,27 @@ "passwordNotMatch": "Las contraseñas no coinciden.", "user": "Usuario", "addMember": "Agregar miembro" + }, + "eventActionItems": { + "title": "Elementos de acción", + "createActionItem": "Crear elementos de acción", + "actionItemCategory": "Categoría de elemento de acción", + "selectActionItemCategory": "Seleccione una categoría de elemento de acción", + "selectAssignee": "Seleccione un asignado", + "preCompletionNotes": "Notas previas a la finalización", + "postCompletionNotes": "Publicar notas de finalización", + "actionItemDetails": "Detalles del elemento de acción", + "dueDate": "Fecha de vencimiento", + "completionDate": "Fecha de finalización", + "editActionItem": "Editar elemento de acción", + "deleteActionItem": "Eliminar elemento de acción", + "deleteActionItemMsg": "¿Quieres eliminar este elemento de acción?", + "yes": "Sí", + "no": "no", + "successfulDeletion": "Elemento de acción eliminado exitosamente", + "successfulCreation": "Elemento de acción creado exitosamente", + "successfulUpdation": "Elemento de acción actualizado correctamente", + "notes": "Notas", + "save": "Guardar" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index 39be3e4b14..1cd604e3e3 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -1052,5 +1052,27 @@ "passwordNotMatch": "密码不匹配。", "user": "用户", "addMember": "添加成员" + }, + "eventActionItems": { + "title": "行动项目", + "createActionItem": "创建操作项", + "actionItemCategory": "操作项类别", + "selectActionItemCategory": "选择操作项类别", + "selectAssignee": "选择受让人", + "preCompletionNotes": "预完成注释", + "postCompletionNotes": "完成后注释", + "actionItemDetails": "操作项详细信息", + "dueDate": "截止日期", + "completionDate": "完成日期", + "editActionItem": "编辑操作项", + "deleteActionItem": "删除操作项", + "deleteActionItemMsg": "您要删除此操作项吗?", + "yes": "是的", + "不": "不", + "successfulDeletion": "操作项删除成功", + "successfulCreation": "操作项创建成功", + "successfulUpdation": "操作项更新成功", + "notes": "注释", + "save": "保存" } } diff --git a/src/GraphQl/Queries/ActionItemQueries.ts b/src/GraphQl/Queries/ActionItemQueries.ts index 1c925af346..0ce9f1acab 100644 --- a/src/GraphQl/Queries/ActionItemQueries.ts +++ b/src/GraphQl/Queries/ActionItemQueries.ts @@ -64,3 +64,40 @@ export const ACTION_ITEM_LIST = gql` } } `; + +export const ACTION_ITEM_LIST_BY_EVENTS = gql` + query actionItemsByEvent($eventId: ID!) { + actionItemsByEvent(eventId: $eventId) { + _id + assignee { + _id + firstName + lastName + } + assigner { + _id + firstName + lastName + } + actionItemCategory { + _id + name + } + preCompletionNotes + postCompletionNotes + assignmentDate + dueDate + completionDate + isCompleted + event { + _id + title + } + creator { + _id + firstName + lastName + } + } + } +`; diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.module.css b/src/components/EventManagement/EventActionItems/EventActionItems.module.css new file mode 100644 index 0000000000..a73fd82113 --- /dev/null +++ b/src/components/EventManagement/EventActionItems/EventActionItems.module.css @@ -0,0 +1,172 @@ +@media screen and (max-width: 575.5px) { + .mainpageright { + width: 98%; + } +} +.modalContent { + width: 670px; + max-width: 680px; +} +.dropdown { + background-color: white; + border: 1px solid #31bb6b; + position: relative; + display: inline-block; + margin-top: 10px; + margin-bottom: 10px; + color: #31bb6b; +} +.input { + flex: 1; + position: relative; +} + +.btnsContainer { + display: flex; + margin: 2.5rem 0 2.5rem 0; +} + +.btnsContainer .btnsBlock { + display: flex; +} + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.btnsContainer .input { + flex: 1; + position: relative; +} + +input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainer .input button { + width: 52px; +} + +.inputField { + margin-top: 10px; + margin-bottom: 10px; + background-color: white; + box-shadow: 0 1px 1px #31bb6b; +} +.inputFieldModal { + margin-bottom: 10px; + background-color: white; + box-shadow: 0 1px 1px #31bb6b; +} +.inputField > button { + padding-top: 10px; + padding-bottom: 10px; +} +.TableImage { + object-fit: cover; + width: 50px !important; + height: 50px !important; + border-radius: 100% !important; +} +.tableHead { + background-color: #31bb6b !important; + color: white; + border-radius: 20px !important; + padding: 20px; + margin-top: 20px; +} + +.tableHead :nth-first-child() { + border-top-left-radius: 20px; +} + +.mainpageright > hr { + margin-top: 10px; + width: 100%; + margin-left: -15px; + margin-right: -15px; + margin-bottom: 20px; +} +.rowBackground { + background-color: var(--bs-white); +} +.tableHeader { + background-color: var(--bs-primary); + color: var(--bs-white); + font-size: 16px; +} +.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%; +} + +.editDelBtns { + display: flex; + justify-content: space-around; +} + +.greenregbtn { + margin-bottom: 20px; + margin-left: 85%; +} + +.datatable { + margin-top: 5rem; +} + +.datediv { + display: flex; +} + +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.test.tsx b/src/components/EventManagement/EventActionItems/EventActionItems.test.tsx new file mode 100644 index 0000000000..ef69796da4 --- /dev/null +++ b/src/components/EventManagement/EventActionItems/EventActionItems.test.tsx @@ -0,0 +1,628 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import userEvent from '@testing-library/user-event'; +import { I18nextProvider } from 'react-i18next'; +import EventActionItems from './EventActionItems'; +import { store } from 'state/store'; +import 'jest-location-mock'; +import { toast } from 'react-toastify'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { + CREATE_ACTION_ITEM_MUTATION, + UPDATE_ACTION_ITEM_MUTATION, + DELETE_ACTION_ITEM_MUTATION, +} from 'GraphQl/Mutations/ActionItemMutations'; +import { + ACTION_ITEM_CATEGORY_LIST, + MEMBERS_LIST, +} from 'GraphQl/Queries/Queries'; +import { ACTION_ITEM_LIST_BY_EVENTS } from 'GraphQl/Queries/ActionItemQueries'; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const MOCKS = [ + { + request: { + query: CREATE_ACTION_ITEM_MUTATION, + variables: { + assigneeId: '658930fd2caa9d8d6908745c', + actionItemCategoryId: '65f069a53b63ad266db32b3f', + eventId: '123', + preCompletionNotes: 'task to be done with high priority', + dueDate: '2024-04-05', + }, + }, + result: { + data: { + createActionItem: { + _id: 'newly_created_action_item_id', + }, + }, + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: '_6613ef741677gygwuyu', + assigneeId: '658930fd2caa9d8d6908745c', + preCompletionNotes: 'task to be done with high priority', + postCompletionNotes: 'Done', + dueDate: '2024-04-05', + completionDate: '2024-04-05', + isCompleted: false, + }, + }, + result: { + data: { + updateActionItem: { + _id: '_6613ef741677gygwuyu', + __typename: 'ActionItem', + }, + }, + }, + }, + { + request: { + query: DELETE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: '_6613ef741677gygwuyu', + }, + }, + result: { + data: { + removeActionItem: { + _id: '_6613ef741677gygwuyu', + __typename: 'ActionItem', + }, + }, + }, + }, + { + request: { + query: ACTION_ITEM_CATEGORY_LIST, + variables: { + organizationId: '111', + }, + }, + result: { + data: { + actionItemCategoriesByOrganization: [ + { + _id: '65f069a53b63ad266db32b3f', + name: 'Default', + isDisabled: false, + __typename: 'ActionItemCategory', + }, + ], + }, + }, + }, + { + request: { + query: MEMBERS_LIST, + variables: { + id: '111', + }, + }, + result: { + data: { + organizations: [ + { + _id: '111', + members: [ + { + createdAt: '2023-04-13T04:53:17.742Z', + email: 'testuser4@example.com', + firstName: 'Teresa', + image: null, + lastName: 'Bradley', + organizationsBlockedBy: [], + __typename: 'User', + _id: '658930fd2caa9d8d6908745c', + }, + { + createdAt: '2024-04-13T04:53:17.742Z', + email: 'testuser2@example.com', + firstName: 'Anna', + image: null, + lastName: 'Bradley', + organizationsBlockedBy: [], + __typename: 'User', + _id: '658930fd2caa9d8d690sfhgush', + }, + ], + }, + ], + }, + }, + }, + { + request: { + query: ACTION_ITEM_LIST_BY_EVENTS, + variables: { + eventId: '123', + }, + }, + result: { + data: { + actionItemsByEvent: [ + { + _id: '_6613ef741677gygwuyu', + actionItemCategory: { + __typename: 'ActionItemCategory', + _id: '65f069a53b63ad266db32b3j', + name: 'Default', + }, + assignee: { + __typename: 'User', + _id: '6589387e2caa9d8d69087485', + firstName: 'Burton', + lastName: 'Sanders', + }, + assigner: { + __typename: 'User', + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + assignmentDate: '2024-04-08', + completionDate: '2024-04-08', + creator: { + __typename: 'User', + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + dueDate: '2024-04-08', + event: { + __typename: 'Event', + _id: '123', + title: 'Adult Painting Lessons', + }, + isCompleted: false, + postCompletionNotes: 'Post Completion Note', + preCompletionNotes: 'Pre Completion Note', + }, + ], + }, + refetch: jest.fn(), + }, + }, +]; + +const CREATE_ACTION_ITEM_ERROR_MOCK = [ + { + request: { + query: CREATE_ACTION_ITEM_MUTATION, + variables: { + assigneeId: '658930fd2caa9d8d6908745c', + actionItemCategoryId: '65f069a53b63ad266db32b3f', + eventId: '123', + preCompletionNotes: 'task to be done with high priority', + dueDate: '2024-04-05', + }, + }, + result: { + data: { + createActionItem: { + _id: undefined, + }, + }, + }, + }, +]; + +const UPDATE_ACTION_ITEM_ERROR_MOCK = [ + { + request: { + query: ACTION_ITEM_LIST_BY_EVENTS, + variables: { + eventId: '123', + }, + }, + result: { + data: { + actionItemsByEvent: [ + { + _id: '_6613ef741677gygwuyu', + actionItemCategory: { + __typename: 'ActionItemCategory', + _id: '65f069a53b63ad266db32b3j', + name: 'Default', + }, + assignee: { + __typename: 'User', + _id: '6589387e2caa9d8d69087485', + firstName: 'Burton', + lastName: 'Sanders', + }, + assigner: { + __typename: 'User', + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + assignmentDate: '2024-04-08', + completionDate: '2024-04-08', + creator: { + __typename: 'User', + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + }, + dueDate: '2024-04-08', + event: { + __typename: 'Event', + _id: '123', + title: 'Adult Painting Lessons', + }, + isCompleted: false, + postCompletionNotes: 'Post Completion Note', + preCompletionNotes: 'Pre Completion Note', + }, + ], + }, + refetch: jest.fn(), + }, + }, + { + request: { + query: UPDATE_ACTION_ITEM_MUTATION, + variables: { + actionItemId: '_6613ef741677gygwuyu', + assigneeId: '658930fd2caa9d8d6908745c', + preCompletionNotes: 'task to be done with high priority', + postCompletionNotes: 'Done', + dueDate: '2024-04-05', + completionDate: '2024-04-05', + isCompleted: false, + }, + }, + result: { + data: { + updateActionItem: { + _id: undefined, + __typename: 'ActionItem', + }, + }, + }, + }, +]; + +const NO_ACTION_ITEMs_ERROR_MOCK = [ + { + request: { + query: ACTION_ITEM_LIST_BY_EVENTS, + variables: { + eventId: '123', + }, + }, + result: { + data: { + actionItemsByEvent: [], + }, + refetch: jest.fn(), + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(CREATE_ACTION_ITEM_ERROR_MOCK, true); +const link3 = new StaticMockLink(UPDATE_ACTION_ITEM_ERROR_MOCK, true); +const link4 = new StaticMockLink(NO_ACTION_ITEMs_ERROR_MOCK, true); + +const translations = JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.eventActionItems, + ), +); + +describe('Event Action Items Page', () => { + test('Testing add new action item modal', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + userEvent.click(screen.getByTestId('createEventActionItemBtn')); + + await wait(); + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + + const categoryDropdown = screen.getByTestId('formSelectActionItemCategory'); + userEvent.selectOptions(categoryDropdown, 'Default'); + + expect(categoryDropdown).toHaveValue('65f069a53b63ad266db32b3f'); + + const assigneeDropdown = screen.getByTestId('formSelectAssignee'); + userEvent.selectOptions(assigneeDropdown, 'Teresa Bradley'); + + expect(assigneeDropdown).toHaveValue('658930fd2caa9d8d6908745c'); + + fireEvent.change(screen.getByPlaceholderText('Notes'), { + target: { value: 'task to be done with high priority' }, + }); + expect(screen.getByPlaceholderText('Notes')).toHaveValue( + 'task to be done with high priority', + ); + + fireEvent.change(screen.getByLabelText('Due Date'), { + target: { value: '04/05/2024' }, + }); + expect(screen.getByLabelText('Due Date')).toHaveValue('04/05/2024'); + + userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); + + await wait(); + + expect(toast.success).toBeCalledWith(translations.successfulCreation); + }); + + test('Display all the action items', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + + expect(screen.getByText('#')).toBeInTheDocument(); + expect(screen.getByText('Assignee')).toBeInTheDocument(); + expect(screen.getByText('Action Item Category')).toBeInTheDocument(); + expect(screen.getByText('Notes')).toBeInTheDocument(); + expect(screen.getByText('Completion Notes')).toBeInTheDocument(); + + await wait(); + expect(screen.getByText('Burton Sanders')).toBeInTheDocument(); + expect(screen.getByText('Pre Completion Note')).toBeInTheDocument(); + const updateButtons = screen.getAllByText(/Manage Actions/i); + expect(updateButtons[0]).toBeInTheDocument(); + }); + + test('Testing update action item modal', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + const updateButtons = screen.getAllByText(/Manage Actions/i); + userEvent.click(updateButtons[0]); + + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + + const assigneeDropdown = screen.getByTestId('formUpdateAssignee'); + userEvent.selectOptions(assigneeDropdown, 'Teresa Bradley'); + + expect(assigneeDropdown).toHaveValue('658930fd2caa9d8d6908745c'); + fireEvent.change(screen.getByPlaceholderText('Notes'), { + target: { value: 'task to be done with high priority' }, + }); + expect(screen.getByPlaceholderText('Notes')).toHaveValue( + 'task to be done with high priority', + ); + + fireEvent.change(screen.getByPlaceholderText('Post Completion Notes'), { + target: { value: 'Done' }, + }); + expect(screen.getByPlaceholderText('Post Completion Notes')).toHaveValue( + 'Done', + ); + + fireEvent.change(screen.getByLabelText('Due Date'), { + target: { value: '04/05/2024' }, + }); + expect(screen.getByLabelText('Due Date')).toHaveValue('04/05/2024'); + + fireEvent.change(screen.getByLabelText('Completion Date'), { + target: { value: '04/05/2024' }, + }); + expect(screen.getByLabelText('Completion Date')).toHaveValue('04/05/2024'); + + userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); + + await wait(); + + expect(toast.success).toBeCalledWith(translations.successfulUpdation); + }); + test('Testing delete action item modal and delete the record', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + const updateButtons = screen.getAllByText(/Manage Actions/i); + userEvent.click(updateButtons[0]); + + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + + expect(screen.getByTestId('deleteActionItemBtn')).toBeInTheDocument(); + userEvent.click(screen.getByTestId('deleteActionItemBtn')); + await wait(); + expect( + screen.getByText('Do you want to remove this action item?'), + ).toBeInTheDocument(); + userEvent.click(screen.getByText('Yes')); + await wait(); + + expect(toast.success).toBeCalledWith(translations.successfulDeletion); + }); + + test('Testing delete action item modal and does not delete the record', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + const updateButtons = screen.getAllByText(/Manage Actions/i); + userEvent.click(updateButtons[0]); + + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + + expect(screen.getByTestId('deleteActionItemBtn')).toBeInTheDocument(); + userEvent.click(screen.getByTestId('deleteActionItemBtn')); + await wait(); + expect( + screen.getByText('Do you want to remove this action item?'), + ).toBeInTheDocument(); + userEvent.click(screen.getByText('No')); + await wait(); + expect(screen.getByText('Teresa Bradley')).toBeInTheDocument(); + }); + + test('Raises an error when incorrect information is filled while creation', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + userEvent.click(screen.getByTestId('createEventActionItemBtn')); + + await wait(); + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + + fireEvent.change(screen.getByPlaceholderText('Notes'), { + target: { value: 'task to be done with high priority' }, + }); + expect(screen.getByPlaceholderText('Notes')).toHaveValue( + 'task to be done with high priority', + ); + + fireEvent.change(screen.getByLabelText('Due Date'), { + target: { value: '04/05/2024' }, + }); + expect(screen.getByLabelText('Due Date')).toHaveValue('04/05/2024'); + + userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); + await wait(); + + expect(toast.error).toBeCalled(); + }); + + test('Raises an error when incorrect information is filled while updation', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + const updateButtons = screen.getAllByText(/Manage Actions/i); + userEvent.click(updateButtons[0]); + + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + + userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); + + await wait(); + + expect(toast.success).toBeCalledWith(translations.successfulUpdation); + + expect(toast.error).toBeCalled(); + }); + + test('Displays message when no data is available', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + expect(screen.getByText('Nothing Found !!')).toBeInTheDocument(); + }); +}); diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.tsx b/src/components/EventManagement/EventActionItems/EventActionItems.tsx new file mode 100644 index 0000000000..d23cfd3bee --- /dev/null +++ b/src/components/EventManagement/EventActionItems/EventActionItems.tsx @@ -0,0 +1,598 @@ +import { useMutation, useQuery } from '@apollo/client'; +import type { Dayjs } from 'dayjs'; +import dayjs from 'dayjs'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Button, Form } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import styles from './EventActionItems.module.css'; +import { DataGrid } from '@mui/x-data-grid'; +import type { GridColDef, GridCellParams } from '@mui/x-data-grid'; +import { Stack } from '@mui/material'; +import Modal from 'react-bootstrap/Modal'; +import { + CREATE_ACTION_ITEM_MUTATION, + DELETE_ACTION_ITEM_MUTATION, + UPDATE_ACTION_ITEM_MUTATION, +} from 'GraphQl/Mutations/ActionItemMutations'; +import type { + InterfaceActionItemCategoryList, + InterfaceMembersList, +} from 'utils/interfaces'; +import { DatePicker } from '@mui/x-date-pickers'; +import { + ACTION_ITEM_CATEGORY_LIST, + MEMBERS_LIST, +} from 'GraphQl/Queries/Queries'; +import { ACTION_ITEM_LIST_BY_EVENTS } from 'GraphQl/Queries/ActionItemQueries'; + +function eventActionItems(props: { eventId: string }): JSX.Element { + const { eventId } = props; + const { t } = useTranslation('translation', { + keyPrefix: 'eventActionItems', + }); + + const [actionItemCreateModalIsOpen, setActionItemCreateModalIsOpen] = + useState(false); + const [actionItemUpdateModalIsOpen, setActionItemUpdateModalIsOpen] = + useState(false); + const [actionItemDeleteModalIsOpen, setActionItemDeleteModalIsOpen] = + useState(false); + const [dueDate, setDueDate] = useState(new Date()); + const [completionDate, setCompletionDate] = useState(new Date()); + const [actionItemId, setActionItemId] = useState(''); + document.title = t('title'); + const url: string = window.location.href; + const startIdx: number = url.indexOf('/event/') + '/event/'.length; + const orgId: string = url.slice(startIdx, url.indexOf('/', startIdx)); + const [formState, setFormState] = useState({ + actionItemCategoryId: '', + assignee: '', + assigner: '', + assigneeId: '', + preCompletionNotes: '', + postCompletionNotes: '', + isCompleted: false, + }); + const showCreateModal = (): void => { + const newState = !actionItemCreateModalIsOpen; + setActionItemCreateModalIsOpen(newState); + }; + const hideCreateModal = (): void => { + setActionItemCreateModalIsOpen(!actionItemCreateModalIsOpen); + }; + const showUpdateModal = (): void => { + setActionItemUpdateModalIsOpen(!actionItemUpdateModalIsOpen); + }; + const hideUpdateModal = (): void => { + setActionItemId(''); + setActionItemUpdateModalIsOpen(!actionItemUpdateModalIsOpen); + }; + const toggleDeleteModal = (): void => { + setActionItemDeleteModalIsOpen(!actionItemDeleteModalIsOpen); + }; + const { + data: actionItemCategoriesData, + }: { + data: InterfaceActionItemCategoryList | undefined; + loading: boolean; + error?: Error | undefined; + } = useQuery(ACTION_ITEM_CATEGORY_LIST, { + variables: { + organizationId: orgId, + }, + }); + const actionItemCategories = + actionItemCategoriesData?.actionItemCategoriesByOrganization.filter( + (category) => !category.isDisabled, + ); + const { data: actionItemsData, refetch: actionItemsRefetch } = useQuery( + ACTION_ITEM_LIST_BY_EVENTS, + { + variables: { + eventId, + }, + }, + ); + const { + data: membersData, + }: { + data: InterfaceMembersList | undefined; + loading: boolean; + error?: Error | undefined; + } = useQuery(MEMBERS_LIST, { + variables: { id: orgId }, + }); + const [createActionItem] = useMutation(CREATE_ACTION_ITEM_MUTATION); + const createActionItemHandler = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + await createActionItem({ + variables: { + assigneeId: formState.assigneeId, + actionItemCategoryId: formState.actionItemCategoryId, + eventId, + preCompletionNotes: formState.preCompletionNotes, + dueDate: dayjs(dueDate).format('YYYY-MM-DD'), + }, + }); + setFormState({ + actionItemCategoryId: '', + assignee: '', + assigner: '', + assigneeId: '', + preCompletionNotes: '', + postCompletionNotes: '', + isCompleted: false, + }); + setDueDate(new Date()); + actionItemsRefetch(); + hideCreateModal(); + toast.success(t('successfulCreation')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + useEffect(() => { + actionItemsRefetch({ + eventId, + }); + }, []); + const [updateActionItem] = useMutation(UPDATE_ACTION_ITEM_MUTATION); + const updateActionItemHandler = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + try { + await updateActionItem({ + variables: { + actionItemId, + assigneeId: formState.assigneeId, + preCompletionNotes: formState.preCompletionNotes, + postCompletionNotes: formState.postCompletionNotes, + dueDate: dayjs(dueDate).format('YYYY-MM-DD'), + completionDate: dayjs(completionDate).format('YYYY-MM-DD'), + isCompleted: formState.isCompleted, + }, + }); + actionItemsRefetch(); + hideUpdateModal(); + toast.success(t('successfulUpdation')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + const [removeActionItem] = useMutation(DELETE_ACTION_ITEM_MUTATION); + const deleteActionItemHandler = async (): Promise => { + await removeActionItem({ + variables: { + actionItemId, + }, + }); + actionItemsRefetch(); + toggleDeleteModal(); + hideUpdateModal(); + toast.success(t('successfulDeletion')); + }; + const columns: GridColDef[] = [ + { + field: 'serialNo', + headerName: '#', + flex: 1, + minWidth: 50, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return params.row?.index; + }, + }, + { + field: 'assignee', + headerName: 'Assignee', + flex: 2, + minWidth: 150, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return ( + + {params.row?.assignee.firstName + + ' ' + + params.row?.assignee.lastName} + + ); + }, + }, + { + field: 'actionItemCategory', + headerName: 'Action Item Category', + flex: 2, + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return params.row.actionItemCategory.name; + }, + }, + { + field: 'notes', + headerName: 'Notes', + minWidth: 150, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + flex: 2, + sortable: false, + renderCell: (params: GridCellParams) => { + return params.row.preCompletionNotes; + }, + }, + { + field: 'completionNotes', + headerName: 'Completion Notes', + minWidth: 150, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + flex: 2, + sortable: false, + renderCell: (params: GridCellParams) => { + return params.row.postCompletionNotes; + }, + }, + { + field: 'options', + headerName: 'Options', + flex: 2, + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return ( + + ); + }, + }, + ]; + return ( + <> + + {/* create action item modal */} + + +

{t('actionItemDetails')}

+ +
+ +
+ + {t('actionItemCategory')} + + setFormState({ + ...formState, + actionItemCategoryId: e.target.value, + }) + } + > + + {actionItemCategories?.map((category, index) => ( + + ))} + + + + Assignee + + setFormState({ ...formState, assigneeId: e.target.value }) + } + > + + {membersData?.organizations[0].members?.map((member, index) => ( + + ))} + + + + { + setFormState({ + ...formState, + preCompletionNotes: e.target.value, + }); + }} + /> +
+ { + if (date) { + setDueDate(date?.toDate()); + } + }} + /> +
+ + +
+
+ {/* update action items modal */} + + +

{t('actionItemDetails')}

+ +
+ +
+ + Assignee + + setFormState({ ...formState, assigneeId: e.target.value }) + } + > + + {membersData?.organizations[0].members.map((member, index) => { + const currMemberName = `${member.firstName} ${member.lastName}`; + if (currMemberName !== formState.assignee) { + return ( + + ); + } + })} + + + + { + setFormState({ + ...formState, + preCompletionNotes: e.target.value, + }); + }} + /> + + { + setFormState({ + ...formState, + postCompletionNotes: e.target.value, + }); + }} + className="mb-2" + /> +

+
+ { + if (date) { + setDueDate(date?.toDate()); + } + }} + /> +   + { + if (date) { + setCompletionDate(date?.toDate()); + } + }} + /> +
+

+
+ + + +
+ +
+
+ {/* delete modal */} + + + + {t('deleteActionItem')} + + + {t('deleteActionItemMsg')} + + + + + + {actionItemsData && ( +
+ row._id} + components={{ + NoRowsOverlay: () => ( + + Nothing Found !! + + ), + }} + sx={{ + '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { + outline: 'none !important', + }, + '&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus-within': { + outline: 'none', + }, + '& .MuiDataGrid-row:hover': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-row.Mui-hovered': { + backgroundColor: 'transparent', + }, + }} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={70} + rows={actionItemsData?.actionItemsByEvent?.map( + (item: object, index: number) => ({ + ...item, + index: index + 1, + }), + )} + columns={columns} + isRowSelectable={() => false} + /> +
+ )} + + ); +} +export default eventActionItems; diff --git a/src/screens/EventManagement/EventManagement.tsx b/src/screens/EventManagement/EventManagement.tsx index b0e09a7780..af934fc798 100644 --- a/src/screens/EventManagement/EventManagement.tsx +++ b/src/screens/EventManagement/EventManagement.tsx @@ -11,6 +11,7 @@ import { ReactComponent as EventStatisticsIcon } from 'assets/svgs/eventStats.sv import { useTranslation } from 'react-i18next'; import { Button } from 'react-bootstrap'; import EventDashboard from 'components/EventManagement/Dashboard/EventDashboard'; +import EventActionItems from 'components/EventManagement/EventActionItems/EventActionItems'; const eventDashboardTabs: { value: TabOptions; @@ -119,7 +120,7 @@ const EventManagement = (): JSX.Element => { case 'eventActions': return (
-

Event Actions

+
); case 'eventStats':