Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) O3-3165: Add modal confirmation modal when deleting repeated question #267

Merged
merged 12 commits into from
May 24, 2024
Merged
Prev Previous commit
Next Next commit
(refactor): add confirmation modal handler as prop
NethmiRodrigo committed May 24, 2024
commit cad48a9e1d3b9bfdba74552ccf9d617fa087052b
29 changes: 16 additions & 13 deletions src/components/repeat/repeat.component.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { FormGroup } from '@carbon/react';
import { useFormikContext } from 'formik';
import { useTranslation } from 'react-i18next';
import { type FormField, type FormFieldProps, type RenderType } from '../../types';
import type { HandleConfirmDeletionFunctionStore, FormField, FormFieldProps, RenderType } from '../../types';
import { evaluateAsyncExpression, evaluateExpression } from '../../utils/expression-runner';
import { isEmpty } from '../../validators/form-validator';
import styles from './repeat.scss';
@@ -11,7 +11,7 @@ import { FormContext } from '../../form-context';
import { getFieldControlWithFallback } from '../section/helpers';
import { clearSubmission } from '../../utils/common-utils';
import RepeatControls from './repeat-controls.component';
import { showModal } from '@openmrs/esm-framework';
import { getGlobalStore } from '@openmrs/esm-framework';

const renderingByTypeMap: Record<string, RenderType> = {
obsGroup: 'group',
@@ -27,6 +27,9 @@ const Repeat: React.FC<FormFieldProps> = ({ question, onChange, handler }) => {
const [rows, setRows] = useState([]);
const [fieldComponent, setFieldComponent] = useState(null);

const functionStore = getGlobalStore<HandleConfirmDeletionFunctionStore>('functionStore');
const { handleConfirmQuestionDeletion } = functionStore.getState();

useEffect(() => {
const repeatedFields = allFormFields.filter(
(field) =>
@@ -106,17 +109,17 @@ const Repeat: React.FC<FormFieldProps> = ({ question, onChange, handler }) => {
setRows(rows.filter((q) => q.id !== question.id));
};

const onClickDeleteQuestion = (question: FormField) => {
try {
const dispose = showModal('delete-question-confirm-modal', {
onCancel: () => dispose(),
onConfirm: () => {
removeNthRow(question);
dispose();
},
});
if (dispose === null) throw Error('Cannot find modal');
} catch (error) {
const onClickDeleteQuestion = (question: Readonly<FormField>) => {
if (handleConfirmQuestionDeletion && typeof handleConfirmQuestionDeletion === 'function') {
const result = handleConfirmQuestionDeletion(question);
if (result && typeof result.then === 'function' && typeof result.catch === 'function') {
result.then(() => removeNthRow(question)).catch(() => console.error('Modal has being cancelled'));
} else if (typeof result === 'boolean') {
result && removeNthRow(question);
} else {
removeNthRow(question);
}
} else {
removeNthRow(question);
}
};
17 changes: 15 additions & 2 deletions src/form-engine.component.tsx
Original file line number Diff line number Diff line change
@@ -4,9 +4,15 @@ import { Form, Formik } from 'formik';
import { Button, ButtonSet, InlineLoading } from '@carbon/react';
import { I18nextProvider, useTranslation } from 'react-i18next';
import * as Yup from 'yup';
import { showSnackbar, useSession, type Visit } from '@openmrs/esm-framework';
import { createGlobalStore, showSnackbar, useSession, type Visit } from '@openmrs/esm-framework';
import { init, teardown } from './lifecycle';
import type { FormPage as FormPageProps, FormSchema, SessionMode } from './types';
import type {
FormField,
FormPage as FormPageProps,
FormSchema,
HandleConfirmDeletionFunctionStore,
SessionMode,
} from './types';
import { extractErrorMessagesFromResponse, reportError } from './utils/error-utils';
import { useFormJson } from './hooks/useFormJson';
import { usePostSubmissionAction } from './hooks/usePostSubmissionAction';
@@ -33,6 +39,7 @@ interface FormProps {
onSubmit?: () => void;
onCancel?: () => void;
handleClose?: () => void;
handleConfirmQuestionDeletion?: (question: Readonly<FormField>) => Promise<void>;
mode?: SessionMode;
meta?: {
/**
@@ -69,6 +76,10 @@ export interface FormSubmissionHandler {
validate: (values) => boolean;
}

const functionStore = createGlobalStore<HandleConfirmDeletionFunctionStore | null>('functionStore', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think rather than using a global store for this, it makes more sense to use a React context. Context's are local to a React tree, so we have fewer issues around naming conflicts, etc. Global stores are for things we need to be shared across different React trees, but here I don't see a reason for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Ian. I've updated to using context instead, as suggested.
Could you please review the context part? I hope I've implemented it right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I might rename the context ExternalFunctionContext or something so it's reusable for other similar functions (since I imagine we'll need something like this for other uses). Nice job!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I changed the context name and pulled it out of the components/repeat nested folder into the root of src, because it looked wrong to have it nested there when made generic.

handleConfirmQuestionDeletion: null,
});

const FormEngine: React.FC<FormProps> = ({
formJson,
formUUID,
@@ -79,6 +90,7 @@ const FormEngine: React.FC<FormProps> = ({
onSubmit,
onCancel,
handleClose,
handleConfirmQuestionDeletion,
formSessionIntent,
meta,
encounterUuid,
@@ -94,6 +106,7 @@ const FormEngine: React.FC<FormProps> = ({
isLoading: isLoadingFormJson,
formError,
} = useFormJson(formUUID, formJson, encounterUUID || encounterUuid, formSessionIntent);
if (handleConfirmQuestionDeletion) functionStore.setState({ handleConfirmQuestionDeletion });

const { t } = useTranslation();
const formSessionDate = useMemo(() => new Date(), []);
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -468,3 +468,9 @@ export interface PatientProgramPayload {
endDate?: string;
}>;
}

export interface HandleConfirmDeletionFunctionStore {
handleConfirmQuestionDeletion: {
(question: Readonly<FormField>): Promise<void>;
};
}