diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index a2d2ea718d..96d3518ee7 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -267,11 +267,11 @@ "makeActive": "Active", "noActionItems": "No Action Items", "options": "Options", - "preCompletionNotes": "Pre Completion Notes", + "preCompletionNotes": "Notes", "actionItemActive": "Active", "markCompletion": "Mark Completion", "actionItemStatus": "Action Item Status", - "postCompletionNotes": "Post Completion Notes", + "postCompletionNotes": "Completion Notes", "selectActionItemCategory": "Select an action item category", "selectAssignee": "Select an assignee", "status": "Status", @@ -944,8 +944,16 @@ "actionItemCategory": "Action Item Category", "selectActionItemCategory": "Select an action item category", "selectAssignee": "Select an assignee", - "preCompletionNotes": "Pre Completion Notes", - "postCompletionNotes": "Post Completion Notes", + "assignee": "Assignee", + "assigner": "Assigner", + "preCompletionNotes": "Notes", + "postCompletionNotes": "Completion Notes", + "assignmentDate": "Assignment Date", + "status": "Status", + "actionItemActive": "Active", + "actionItemStatus": "Action Item Status", + "actionItemCompleted": "Action Item Completed", + "markCompletion": "Mark Completion", "actionItemDetails": "Action Item Details", "dueDate": "Due Date", "completionDate": "Completion Date", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index d1683af4c8..1239b49158 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -951,8 +951,8 @@ "actionItemCategory": "Catégorie d'élément d'action", "selectActionItemCategory": "Sélectionnez une catégorie d'élément d'action", "selectAssignee": "Sélectionnez un responsable", - "preCompletionNotes": "Notes préalables à l'achèvement", - "postCompletionNotes": "Notes post-achèvement", + "preCompletionNotes": "Remarques", + "postCompletionNotes": "Notes d'achèvement", "actionItemDetails": "Détails de l'action", "dueDate": "Date d'échéance", "completionDate": "Date d'achèvement", @@ -963,6 +963,14 @@ "successfulCreation": "Élément d'action créé avec succès", "successfulUpdation": "Élément d'action mis à jour avec succès", "notes": "Remarques", + "assignee": "Cessionnaire", + "assigner": "Assigner", + "assignmentDate": "Date d'affectation", + "status": "Statut", + "actionItemActive": "Actif", + "actionItemStatus": "Statut de l'action", + "actionItemCompleted": "Élément d'action terminé", + "markCompletion": "Marquer l'achèvement", "save": "Sauvegarder" }, "checkIn": { diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 36e1dbefca..b78b7f17b2 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -951,8 +951,8 @@ "actionItemCategory": "कार्य आइटम श्रेणी", "selectActionItemCategory": "एक क्रिया आइटम श्रेणी का चयन करें", "selectAssignee": "एक समनुदेशिती का चयन करें", - "preCompletionNotes": "समापन पूर्व नोट्स", - "postCompletionNotes": "समापन के बाद के नोट्स", + "preCompletionNotes": "टिप्पणियाँ", + "postCompletionNotes": "समापन नोट्स", "actionItemDetails": "कार्रवाई मद विवरण", "dueDate": "नियत तारीख", "completionDate": "पूरा करने की तिथि", @@ -963,6 +963,14 @@ "successfulCreation": "कार्रवाई आइटम सफलतापूर्वक बनाया गया", "successfulUpdation": "कार्रवाई आइटम सफलतापूर्वक अपडेट किया गया", "notes": "टिप्पणियाँ", + "assignee": "संपत्ति-भागी", + "assigner": "असाइनर", + "assignmentDate": "असाइनमेंट तिथि", + "status": "स्थिति", + "actionItemActive": "सक्रिय", + "actionItemStatus": "कार्रवाई आइटम स्थिति", + "actionItemCompleted": "कार्रवाई आइटम पूर्ण हुआ", + "markCompletion": "पूर्णता को चिह्नित करें", "save": "बचाना" }, "checkIn": { diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 2016ed2e4c..854b60b19d 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -1191,8 +1191,8 @@ "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", + "preCompletionNotes": "Notas", + "postCompletionNotes": "Notas finales", "actionItemDetails": "Detalles del elemento de acción", "dueDate": "Fecha de vencimiento", "completionDate": "Fecha de finalización", @@ -1205,6 +1205,14 @@ "successfulCreation": "Elemento de acción creado exitosamente", "successfulUpdation": "Elemento de acción actualizado correctamente", "notes": "Notas", + "assignee": "Cesionario", + "assigner": "Asignador", + "assignmentDate": "Fecha de asignación", + "status": "Estado", + "actionItemActive": "Activo", + "actionItemStatus": "Estado del elemento de acción", + "actionItemCompleted": "Elemento de acción completado", + "markCompletion": "Marcar finalización", "save": "Guardar" }, "checkIn": { diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 3f44aeae9c..581701c9db 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -951,8 +951,8 @@ "actionItemCategory": "行动项目类别", "selectActionItemCategory": "选择操作项类别", "selectAssignee": "选择受托人", - "preCompletionNotes": "预完成注释", - "postCompletionNotes": "完成后注释", + "preCompletionNotes": "笔记", + "postCompletionNotes": "完成说明", "actionItemDetails": "行动项目详情", "dueDate": "到期日", "completionDate": "完成日期", @@ -963,6 +963,14 @@ "successfulCreation": "操作项创建成功", "successfulUpdation": "操作项已成功更新", "notes": "笔记", + "assignee": "受让人", + "assigner": "分配者", + "assignmentDate": "任务分配日期", + "status": "地位", + "actionItemActive": "积极的", + "actionItemStatus": "行动项目状态", + "actionItemCompleted": "行动项目已完成", + "markCompletion": "标记完成", "save": "节省" }, "checkIn": { diff --git a/src/assets/css/app.css b/src/assets/css/app.css index a96b3afabe..f58f1008ca 100644 --- a/src/assets/css/app.css +++ b/src/assets/css/app.css @@ -2401,8 +2401,8 @@ textarea.form-control-lg { .form-check-input { --bs-form-check-bg: #f2f2f2; - width: 1em; - height: 1em; + width: 1.3em; + height: 1.3em; margin-top: 0.25em; vertical-align: top; background-color: var(--bs-form-check-bg); diff --git a/src/components/ActionItems/ActionItemsContainer.test.tsx b/src/components/ActionItems/ActionItemsContainer.test.tsx index 21f1148d6a..7cb9c9e9bb 100644 --- a/src/components/ActionItems/ActionItemsContainer.test.tsx +++ b/src/components/ActionItems/ActionItemsContainer.test.tsx @@ -86,6 +86,35 @@ describe('Testing Action Item Categories Component', () => { screen.queryByText(translations.noActionItems), ).not.toBeInTheDocument(); }); + + expect(screen.getByText('#')).toBeInTheDocument(); + expect(screen.getByText(translations.assignee)).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemCategory), + ).toBeInTheDocument(); + expect( + screen.getByText(translations.preCompletionNotes), + ).toBeInTheDocument(); + expect( + screen.getByText(translations.postCompletionNotes), + ).toBeInTheDocument(); + + await wait(); + expect(screen.getAllByText('Harve Lance')[0]).toBeInTheDocument(); + + const asigneeAnchorElement = screen.getAllByText('Harve Lance')[0]; + expect(asigneeAnchorElement.tagName).toBe('A'); + expect(asigneeAnchorElement).toHaveAttribute('href', '/member/event1'); + + expect(screen.getAllByText('ActionItemCategory 1')[0]).toBeInTheDocument(); + const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); + const previewButtons = screen.getAllByTestId('previewActionItemModalBtn'); + const updateStatusButtons = screen.getAllByTestId( + 'actionItemStatusChangeCheckbox', + ); + expect(updateButtons[0]).toBeInTheDocument(); + expect(previewButtons[0]).toBeInTheDocument(); + expect(updateStatusButtons[0]).toBeInTheDocument(); }); test('component loads correctly with no action items', async () => { @@ -715,4 +744,34 @@ describe('Testing Action Item Categories Component', () => { ); }); }); + + test('Action Items loads with correct headers', async () => { + render( + + + + + + + + + , + ); + + await wait(); + + const actionItemHeaders = screen.getByTestId('actionItemsHeader'); + expect(actionItemHeaders).toBeInTheDocument(); + expect(screen.getByText(translations.assignee)).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemCategory), + ).toBeInTheDocument(); + expect( + screen.getByText(translations.preCompletionNotes), + ).toBeInTheDocument(); + expect( + screen.getByText(translations.postCompletionNotes), + ).toBeInTheDocument(); + expect(screen.getByText(translations.options)).toBeInTheDocument(); + }); }); diff --git a/src/components/ActionItems/ActionItemsContainer.tsx b/src/components/ActionItems/ActionItemsContainer.tsx index 0cb799c212..42e57f3822 100644 --- a/src/components/ActionItems/ActionItemsContainer.tsx +++ b/src/components/ActionItems/ActionItemsContainer.tsx @@ -27,6 +27,7 @@ import styles from './ActionItemsContainer.module.css'; import ActionItemUpdateModal from '../../screens/OrganizationActionItems/ActionItemUpdateModal'; import ActionItemPreviewModal from '../../screens/OrganizationActionItems/ActionItemPreviewModal'; import ActionItemDeleteModal from '../../screens/OrganizationActionItems/ActionItemDeleteModal'; +import { Link } from 'react-router-dom'; function actionItemsContainer({ actionItemsConnection, @@ -193,18 +194,28 @@ function actionItemsContainer({ > + +
{'#'}
+
{t('assignee')}
{t('preCompletionNotes')}
{t('postCompletionNotes')}
- +
{t('options')}
@@ -237,29 +253,44 @@ function actionItemsContainer({ {actionItemsData?.map((actionItem, index) => (
+ + {index + 1} + - {`${actionItem.assignee.firstName} ${actionItem.assignee.lastName}`} + + {`${actionItem.assignee.firstName} ${actionItem.assignee.lastName}`} + {actionItem.actionItemCategory.name} -
+
{actionItem.isCompleted ? ( @@ -311,7 +342,12 @@ function actionItemsContainer({ )}
- +
{ + const formData = { + assignee: 'Anna Bradley', + preCompletionNotes: 'pre completion notes edited', + dueDate: '02/14/2024', + completionDate: '02/21/2024', + }; test('Testing add new action item modal', async () => { window.location.assign('/event/111/123'); render( @@ -357,7 +519,9 @@ describe('Event Action Items Page', () => { userEvent.click(screen.getByTestId('createEventActionItemBtn')); await wait(); - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemDetails), + ).toBeInTheDocument(); const categoryDropdown = screen.getByTestId('formSelectActionItemCategory'); userEvent.selectOptions(categoryDropdown, 'Default'); @@ -369,17 +533,22 @@ describe('Event Action Items Page', () => { 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(translations.preCompletionNotes), + { + target: { value: 'task to be done with high priority' }, + }, ); + expect( + screen.getByPlaceholderText(translations.preCompletionNotes), + ).toHaveValue('task to be done with high priority'); - fireEvent.change(screen.getByLabelText('Due Date'), { + fireEvent.change(screen.getByLabelText(translations.dueDate), { target: { value: '04/05/2024' }, }); - expect(screen.getByLabelText('Due Date')).toHaveValue('04/05/2024'); + expect(screen.getByLabelText(translations.dueDate)).toHaveValue( + '04/05/2024', + ); userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); @@ -406,22 +575,51 @@ describe('Event Action Items Page', () => { 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(); + expect(screen.getByText(translations.assignee)).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemCategory), + ).toBeInTheDocument(); + expect( + screen.getByText(translations.preCompletionNotes), + ).toBeInTheDocument(); + expect( + screen.getByText(translations.postCompletionNotes), + ).toBeInTheDocument(); await wait(); + const asigneeAnchorElement = screen.getByText('Burton Sanders'); + expect(asigneeAnchorElement.tagName).toBe('A'); + expect(asigneeAnchorElement).toHaveAttribute('href', '/member/123'); + expect(screen.getByText('Burton Sanders')).toBeInTheDocument(); - expect(screen.getByText('Pre Completion Note')).toBeInTheDocument(); - const updateButtons = screen.getAllByText(/Manage Actions/i); + const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); + const previewButtons = screen.getAllByTestId('previewActionItemModalBtn'); + const updateStatusButtons = screen.getAllByTestId( + 'actionItemStatusChangeCheckbox', + ); expect(updateButtons[0]).toBeInTheDocument(); + expect(previewButtons[0]).toBeInTheDocument(); + expect(updateStatusButtons[0]).toBeInTheDocument(); + + // Truncate notes and long completion notes txt + expect( + screen.getAllByTestId('actionItemPreCompletionNotesOverlay')[1], + ).toHaveTextContent('Long Pre Completion Notes...'); + expect( + screen.getAllByTestId('actionItemPostCompletionNotesOverlay')[0], + ).toHaveTextContent('Long Post Completion Note...'); + expect( + screen.getAllByTestId('actionItemPostCompletionNotesOverlay')[1], + ).toHaveTextContent('Post Completion Text'); + expect( + screen.getAllByTestId('actionItemPreCompletionNotesOverlay')[2], + ).toHaveTextContent('Pre Completion Text'); }); - test('Testing update action item modal', async () => { + test('opens and closes the update and delete modals through the preview modal', async () => { window.location.assign('/event/111/123'); render( - + @@ -433,45 +631,235 @@ describe('Event Action Items Page', () => { , ); + await wait(); - const updateButtons = screen.getAllByText(/Manage Actions/i); - userEvent.click(updateButtons[0]); - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + expect( + screen.getAllByTestId('previewActionItemModalBtn')[0], + ).toBeInTheDocument(); + userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - const assigneeDropdown = screen.getByTestId('formUpdateAssignee'); - userEvent.selectOptions(assigneeDropdown, 'Teresa Bradley'); + await waitFor(() => { + return expect( + screen.findByTestId('previewActionItemModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); - expect(assigneeDropdown).toHaveValue('658930fd2caa9d8d6908745c'); - fireEvent.change(screen.getByPlaceholderText('Notes'), { - target: { value: 'task to be done with high priority' }, + await waitFor(() => { + expect( + screen.getByTestId('deleteActionItemPreviewModalBtn'), + ).toBeInTheDocument(); }); - expect(screen.getByPlaceholderText('Notes')).toHaveValue( - 'task to be done with high priority', + userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('actionItemDeleteModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('actionItemDeleteModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('actionItemDeleteModalCloseBtn'), ); - fireEvent.change(screen.getByPlaceholderText('Post Completion Notes'), { - target: { value: 'Done' }, + expect( + screen.getByTestId('editActionItemPreviewModalBtn'), + ).toBeInTheDocument(); + userEvent.click(screen.getByTestId('editActionItemPreviewModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('updateActionItemModalCloseBtn'), + ).resolves.toBeInTheDocument(); }); - expect(screen.getByPlaceholderText('Post Completion Notes')).toHaveValue( - 'Done', + userEvent.click(screen.getByTestId('updateActionItemModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('updateActionItemModalCloseBtn'), ); + }); + test('opens and closes the action item status change modal correctly', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); - fireEvent.change(screen.getByLabelText('Due Date'), { - target: { value: '04/05/2024' }, + await waitFor(() => { + expect( + screen.getAllByTestId('actionItemStatusChangeCheckbox')[0], + ).toBeInTheDocument(); }); - expect(screen.getByLabelText('Due Date')).toHaveValue('04/05/2024'); + userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[0]); - fireEvent.change(screen.getByLabelText('Completion Date'), { - target: { value: '04/05/2024' }, + await waitFor(() => { + return expect( + screen.findByTestId('actionItemStatusChangeModalCloseBtn'), + ).resolves.toBeInTheDocument(); }); - expect(screen.getByLabelText('Completion Date')).toHaveValue('04/05/2024'); + userEvent.click(screen.getByTestId('actionItemStatusChangeModalCloseBtn')); - userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); + await waitForElementToBeRemoved(() => + screen.queryByTestId('actionItemStatusChangeModalCloseBtn'), + ); + }); + + test('updates an action item status through the action item status change modal', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('actionItemStatusChangeCheckbox')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[0]); + + await waitFor(() => { + expect( + screen.getByTestId('actionItemsStatusChangeNotes'), + ).toBeInTheDocument(); + }); + + const postCompletionNotes = screen.getByTestId( + 'actionItemsStatusChangeNotes', + ); + fireEvent.change(postCompletionNotes, { target: { value: '' } }); + userEvent.type( + postCompletionNotes, + 'this action item has been completed successfully', + ); + + await waitFor(() => { + expect( + screen.getByTestId('actionItemStatusChangeSubmitBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('actionItemStatusChangeSubmitBtn')); + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.successfulUpdation); + }); + + await waitFor(() => { + expect( + screen.getAllByTestId('actionItemStatusChangeCheckbox')[1], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('actionItemStatusChangeCheckbox')[1]); + + await waitFor(() => { + expect( + screen.getByTestId('actionItemsStatusChangeNotes'), + ).toBeInTheDocument(); + }); + + const preCompletionNotes = screen.getByTestId( + 'actionItemsStatusChangeNotes', + ); + fireEvent.change(preCompletionNotes, { target: { value: '' } }); + userEvent.type( + preCompletionNotes, + 'this action item has been made active again', + ); + + await waitFor(() => { + expect( + screen.getByTestId('actionItemStatusChangeSubmitBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('actionItemStatusChangeSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.successfulUpdation); + }); + }); + + test('Testing update action item modal', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); await wait(); - expect(toast.success).toBeCalledWith(translations.successfulUpdation); + await waitFor(() => { + expect( + screen.getAllByTestId('editActionItemModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); + + await waitFor(() => { + expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); + }); + + userEvent.selectOptions( + screen.getByTestId('formUpdateAssignee'), + formData.assignee, + ); + + const preCompletionNotes = screen.getByPlaceholderText( + translations.preCompletionNotes, + ); + fireEvent.change(preCompletionNotes, { target: { value: '' } }); + userEvent.type(preCompletionNotes, formData.preCompletionNotes); + + const dueDatePicker = screen.getByLabelText(translations.dueDate); + fireEvent.change(dueDatePicker, { + target: { value: formData.dueDate }, + }); + + const completionDatePicker = screen.getByLabelText( + translations.completionDate, + ); + fireEvent.change(completionDatePicker, { + target: { value: formData.completionDate }, + }); + + await waitFor(() => { + expect( + screen.getByTestId('updateActionItemFormSubmitBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toBeCalledWith(translations.successfulUpdation); + }); }); test('Testing delete action item modal and delete the record', async () => { window.location.assign('/event/111/123'); @@ -489,16 +877,20 @@ describe('Event Action Items Page', () => { , ); await wait(); - const updateButtons = screen.getAllByText(/Manage Actions/i); - userEvent.click(updateButtons[0]); - - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + expect( + screen.getAllByTestId('previewActionItemModalBtn')[0], + ).toBeInTheDocument(); + userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - expect(screen.getByTestId('deleteActionItemBtn')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('deleteActionItemBtn')); + await waitFor(() => { + expect( + screen.getByTestId('deleteActionItemPreviewModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); await wait(); expect( - screen.getByText('Do you want to remove this action item?'), + screen.getByText(translations.deleteActionItemMsg), ).toBeInTheDocument(); userEvent.click(screen.getByText('Yes')); await wait(); @@ -522,20 +914,137 @@ describe('Event Action Items Page', () => { , ); await wait(); - const updateButtons = screen.getAllByText(/Manage Actions/i); - userEvent.click(updateButtons[0]); - - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + expect( + screen.getAllByTestId('previewActionItemModalBtn')[0], + ).toBeInTheDocument(); + userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); - expect(screen.getByTestId('deleteActionItemBtn')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('deleteActionItemBtn')); + await waitFor(() => { + expect( + screen.getByTestId('deleteActionItemPreviewModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); await wait(); expect( - screen.getByText('Do you want to remove this action item?'), + screen.getByText(translations.deleteActionItemMsg), ).toBeInTheDocument(); userEvent.click(screen.getByText('No')); await wait(); - expect(screen.getByText('Teresa Bradley')).toBeInTheDocument(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemDetails), + ).toBeInTheDocument(); + }); + + test('toasts error on unsuccessful deletion', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + + expect( + screen.getAllByTestId('previewActionItemModalBtn')[0], + ).toBeInTheDocument(); + userEvent.click(screen.getAllByTestId('previewActionItemModalBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('previewActionItemModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.getByTestId('deleteActionItemPreviewModalBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteActionItemPreviewModalBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('actionItemDeleteModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('deleteActionItemBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); + }); + + test('toasts error on unsuccessful updation', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + + await waitFor(() => { + expect( + screen.getAllByTestId('editActionItemModalBtn')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('editActionItemModalBtn')[0]); + + await waitFor(() => { + expect(screen.getByTestId('formUpdateAssignee')).toBeInTheDocument(); + }); + + userEvent.selectOptions( + screen.getByTestId('formUpdateAssignee'), + formData.assignee, + ); + + const preCompletionNotes = screen.getByPlaceholderText( + translations.preCompletionNotes, + ); + fireEvent.change(preCompletionNotes, { target: { value: '' } }); + userEvent.type(preCompletionNotes, formData.preCompletionNotes); + + const dueDatePicker = screen.getByLabelText(translations.dueDate); + fireEvent.change(dueDatePicker, { + target: { value: formData.dueDate }, + }); + + const completionDatePicker = screen.getByLabelText( + translations.completionDate, + ); + fireEvent.change(completionDatePicker, { + target: { value: formData.completionDate }, + }); + + await waitFor(() => { + expect( + screen.getByTestId('updateActionItemFormSubmitBtn'), + ).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalled(); + }); }); test('Raises an error when incorrect information is filled while creation', async () => { @@ -557,19 +1066,26 @@ describe('Event Action Items Page', () => { userEvent.click(screen.getByTestId('createEventActionItemBtn')); await wait(); - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemDetails), + ).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.getByPlaceholderText(translations.preCompletionNotes), + { + target: { value: 'task to be done with high priority' }, + }, ); + expect( + screen.getByPlaceholderText(translations.preCompletionNotes), + ).toHaveValue('task to be done with high priority'); - fireEvent.change(screen.getByLabelText('Due Date'), { + fireEvent.change(screen.getByLabelText(translations.dueDate), { target: { value: '04/05/2024' }, }); - expect(screen.getByLabelText('Due Date')).toHaveValue('04/05/2024'); + expect(screen.getByLabelText(translations.dueDate)).toHaveValue( + '04/05/2024', + ); userEvent.click(screen.getByTestId('createActionItemFormSubmitBtn')); await wait(); @@ -593,17 +1109,17 @@ describe('Event Action Items Page', () => { , ); await wait(); - const updateButtons = screen.getAllByText(/Manage Actions/i); + const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); userEvent.click(updateButtons[0]); - expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + expect( + screen.getByText(translations.actionItemDetails), + ).toBeInTheDocument(); userEvent.click(screen.getByTestId('updateActionItemFormSubmitBtn')); await wait(); - expect(toast.success).toBeCalledWith(translations.successfulUpdation); - expect(toast.error).toBeCalled(); }); @@ -625,4 +1141,39 @@ describe('Event Action Items Page', () => { await wait(); expect(screen.getByText('Nothing Found !!')).toBeInTheDocument(); }); + + test('Testing update action modal to have correct initial values', async () => { + window.location.assign('/event/111/123'); + render( + + + + + + {} + + + + + , + ); + await wait(); + const updateButtons = screen.getAllByTestId('editActionItemModalBtn'); + userEvent.click(updateButtons[0]); + + expect(screen.getByText('Action Item Details')).toBeInTheDocument(); + const assigneeDropdown = screen.getByTestId( + 'formUpdateAssignee', + ) as HTMLSelectElement; + expect(assigneeDropdown.value).toBe('658930fd2caa9d8d6908745c'); + expect(assigneeDropdown).toHaveTextContent('Teresa Bradley'); + + expect( + screen.getByPlaceholderText(translations.preCompletionNotes), + ).toHaveValue('Pre Completion Notes'); + const editActionItem = screen.getByRole('button', { + name: translations.editActionItem, + }); + expect(editActionItem).toBeInTheDocument(); + }); }); diff --git a/src/components/EventManagement/EventActionItems/EventActionItems.tsx b/src/components/EventManagement/EventActionItems/EventActionItems.tsx index 717269b3ce..57950a3a24 100644 --- a/src/components/EventManagement/EventActionItems/EventActionItems.tsx +++ b/src/components/EventManagement/EventActionItems/EventActionItems.tsx @@ -5,11 +5,10 @@ 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 type { GridCellParams } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import Modal from 'react-bootstrap/Modal'; import { @@ -19,6 +18,7 @@ import { } from 'GraphQl/Mutations/ActionItemMutations'; import type { InterfaceActionItemCategoryList, + InterfaceActionItemInfo, InterfaceMembersList, } from 'utils/interfaces'; import { DatePicker } from '@mui/x-date-pickers'; @@ -27,6 +27,9 @@ import { MEMBERS_LIST, } from 'GraphQl/Queries/Queries'; import { ACTION_ITEM_LIST_BY_EVENTS } from 'GraphQl/Queries/ActionItemQueries'; +import { useEventActionColumnConfig } from './useEventActionColumnConfig'; +import ActionItemPreviewModal from 'screens/OrganizationActionItems/ActionItemPreviewModal'; +import ActionItemDeleteModal from 'screens/OrganizationActionItems/ActionItemDeleteModal'; function eventActionItems(props: { eventId: string }): JSX.Element { const { eventId } = props; @@ -35,6 +38,11 @@ function eventActionItems(props: { eventId: string }): JSX.Element { }); const { t: tCommon } = useTranslation('common'); + const [actionItemPreviewModalIsOpen, setActionItemPreviewModalIsOpen] = + useState(false); + const [actionItemStatusModal, setActionItemStatusModal] = useState(false); + const [isActionItemCompleted, setIsActionItemCompleted] = useState(false); + const [assignmentDate, setAssignmentDate] = useState(new Date()); const [actionItemCreateModalIsOpen, setActionItemCreateModalIsOpen] = useState(false); const [actionItemUpdateModalIsOpen, setActionItemUpdateModalIsOpen] = @@ -74,6 +82,21 @@ function eventActionItems(props: { eventId: string }): JSX.Element { const toggleDeleteModal = (): void => { setActionItemDeleteModalIsOpen(!actionItemDeleteModalIsOpen); }; + const setActionItemState = (actionItem: InterfaceActionItemInfo): void => { + setFormState((prevState) => ({ + ...prevState, + assignee: `${actionItem.assignee.firstName} ${actionItem.assignee.lastName}`, + assigner: `${actionItem.assigner.firstName} ${actionItem.assigner.lastName}`, + assigneeId: actionItem.assignee._id, + preCompletionNotes: actionItem.preCompletionNotes, + postCompletionNotes: actionItem.postCompletionNotes, + isCompleted: actionItem.isCompleted, + })); + setActionItemId(actionItem._id); + setDueDate(actionItem.dueDate); + setAssignmentDate(actionItem.assignmentDate); + setCompletionDate(actionItem.completionDate); + }; const { data: actionItemCategoriesData, }: { @@ -164,6 +187,7 @@ function eventActionItems(props: { eventId: string }): JSX.Element { }); actionItemsRefetch(); hideUpdateModal(); + hideActionItemStatusModal(); toast.success(t('successfulUpdation')); } catch (error: unknown) { if (error instanceof Error) { @@ -173,116 +197,60 @@ function eventActionItems(props: { eventId: string }): JSX.Element { }; const [removeActionItem] = useMutation(DELETE_ACTION_ITEM_MUTATION); const deleteActionItemHandler = async (): Promise => { - await removeActionItem({ - variables: { - actionItemId, - }, - }); - actionItemsRefetch(); - toggleDeleteModal(); - hideUpdateModal(); - toast.success(t('successfulDeletion')); + try { + await removeActionItem({ + variables: { + actionItemId, + }, + }); + actionItemsRefetch(); + toggleDeleteModal(); + hidePreviewModal(); + toast.success(t('successfulDeletion')); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + console.log(error.message); + } + } }; - 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 ( - - ); - }, - }, - ]; + + const handleActionItemStatusChange = ( + actionItem: InterfaceActionItemInfo, + ): void => { + actionItem = { ...actionItem, isCompleted: !actionItem.isCompleted }; + setIsActionItemCompleted(!actionItem.isCompleted); + setActionItemState(actionItem); + setActionItemStatusModal(true); + }; + + const showPreviewModal = (actionItem: InterfaceActionItemInfo): void => { + setActionItemState(actionItem); + setActionItemPreviewModalIsOpen(true); + }; + + const handleEditClick = (actionItem: InterfaceActionItemInfo): void => { + setActionItemId(actionItem._id); + setActionItemState(actionItem); + showUpdateModal(); + }; + + const hidePreviewModal = (): void => { + setActionItemPreviewModalIsOpen(false); + }; + + const hideActionItemStatusModal = (): void => { + setActionItemStatusModal(false); + setActionItemUpdateModalIsOpen(false); + }; + + const { columns } = useEventActionColumnConfig({ + eventId, + handleActionItemStatusChange, + showPreviewModal, + handleEditClick, + }); + return ( <> +
{/* create action item modal */} Assignee setFormState({ ...formState, assigneeId: e.target.value }) } > - {membersData?.organizations[0].members.map((member, index) => { @@ -447,24 +417,6 @@ function eventActionItems(props: { eventId: string }): JSX.Element { }); }} /> - - { - setFormState({ - ...formState, - postCompletionNotes: e.target.value, - }); - }} - className="mb-2" - /> -

{ - if (date) { - setCompletionDate(date?.toDate()); + onChange={ + /* istanbul ignore next */ (date: Dayjs | null): void => { + /* istanbul ignore next */ + if (date) { + setCompletionDate(date?.toDate()); + } } - }} + } />
-

-
+
- -
- {/* delete modal */} + + {/* preview modal */} + + + {/* Delete Modal */} + + + {/* action item status change modal */} - - - {t('deleteActionItem')} - - - {t('deleteActionItemMsg')} - - + +

{t('actionItemStatus')}

-
+ + +
+ + {isActionItemCompleted + ? t('preCompletionNotes') + : t('postCompletionNotes')} + + { + if (isActionItemCompleted) { + setFormState({ + ...formState, + preCompletionNotes: e.target.value, + }); + } else { + setFormState({ + ...formState, + postCompletionNotes: e.target.value, + }); + } + }} + /> + + +
{actionItemsData && (
@@ -578,10 +574,14 @@ function eventActionItems(props: { eventId: string }): JSX.Element { '& .MuiDataGrid-row.Mui-hovered': { backgroundColor: 'transparent', }, + '& .MuiDataGrid-columnHeaderTitle': { + fontWeight: 700, + }, }} getRowClassName={() => `${styles.rowBackground}`} autoHeight - rowHeight={70} + rowHeight={50} + columnHeaderHeight={40} rows={actionItemsData?.actionItemsByEvent?.map( (item: object, index: number) => ({ ...item, diff --git a/src/components/EventManagement/EventActionItems/useEventActionColumnConfig.tsx b/src/components/EventManagement/EventActionItems/useEventActionColumnConfig.tsx new file mode 100644 index 0000000000..0db74323f6 --- /dev/null +++ b/src/components/EventManagement/EventActionItems/useEventActionColumnConfig.tsx @@ -0,0 +1,200 @@ +import React from 'react'; +import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; +import { Link } from 'react-router-dom'; +import { Button, OverlayTrigger, Popover } from 'react-bootstrap'; +import styles from './EventActionItems.module.css'; +import type { InterfaceActionItemInfo } from 'utils/interfaces'; +import { useTranslation } from 'react-i18next'; + +export type Props = { + eventId: string; + handleActionItemStatusChange: (actionItem: InterfaceActionItemInfo) => void; + showPreviewModal: (actionItem: InterfaceActionItemInfo) => void; + handleEditClick: (actionItem: InterfaceActionItemInfo) => void; +}; + +type ColumnConfig = { + columns: GridColDef[]; +}; + +const popover = ( + actionItemId: string, + actionItemNotes: string, +): JSX.Element => { + return ( + + {actionItemNotes} + + ); +}; + +export const useEventActionColumnConfig = ({ + eventId, + handleActionItemStatusChange, + showPreviewModal, + handleEditClick, +}: Props): ColumnConfig => { + const { t } = useTranslation('translation', { + keyPrefix: 'eventActionItems', + }); + 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) => { + const actionItem = params.row; + return ( + + + {actionItem.preCompletionNotes.length > 25 + ? `${actionItem.preCompletionNotes.substring(0, 25)}...` + : actionItem.preCompletionNotes} + + + ); + }, + }, + { + field: 'completionNotes', + headerName: 'Completion Notes', + minWidth: 150, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + flex: 2, + sortable: false, + renderCell: (params: GridCellParams) => { + const actionItem = params.row; + return actionItem.isCompleted ? ( + + + {actionItem.postCompletionNotes?.length > 25 + ? `${actionItem.postCompletionNotes.substring(0, 25)}...` + : actionItem.postCompletionNotes} + + + ) : ( + + {t('actionItemActive')} + + ); + }, + }, + { + field: 'options', + headerName: 'Options', + flex: 2, + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return ( +
+ handleActionItemStatusChange(params.row)} + /> + + +
+ ); + }, + }, + ]; + return { + columns, + }; +}; diff --git a/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx b/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx index 901101e58b..8b0510a4dc 100644 --- a/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx +++ b/src/screens/OrganizationActionItems/ActionItemUpdateModal.tsx @@ -75,11 +75,11 @@ const ActionItemUpdateModal: React.FC = ({ - {membersData?.map((member, index) => { + {membersData?.map((member: InterfaceMemberInfo) => { const currMemberName = `${member.firstName} ${member.lastName}`; if (currMemberName !== formState.assignee) { return ( - ); @@ -107,36 +107,33 @@ const ActionItemUpdateModal: React.FC = ({ />
-
- { - /* istanbul ignore next */ - if (date) { - setDueDate(date?.toDate()); - } + { + /* istanbul ignore next */ + if (date) { + setDueDate(date?.toDate()); } } - /> -
-
- { - /* istanbul ignore next */ - if (date) { - setCompletionDate(date?.toDate()); - } + } + /> +   + { + /* istanbul ignore next */ + if (date) { + setCompletionDate(date?.toDate()); } } - /> -
+ } + />