Skip to content

Commit

Permalink
Merge pull request #3641 from bcgov/NDT-515-Add-Unsaved-Changes-Warni…
Browse files Browse the repository at this point in the history
…ng-Modal

feat: add unsaved changes warning modal to forms
  • Loading branch information
rafasdc authored Nov 7, 2024
2 parents 94ed766 + 8fb717a commit 82fd8ef
Show file tree
Hide file tree
Showing 29 changed files with 668 additions and 121 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# [1.206.0](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.205.0...v1.206.0) (2024-11-07)

### Features

- add unsaved changes warning modal to forms ([ccc86e6](https://github.com/bcgov/CONN-CCBC-portal/commit/ccc86e664ef9fcc9acbd812e78b1dabbf49b5911))

# [1.205.0](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.204.0...v1.205.0) (2024-11-05)

### Features
Expand Down
78 changes: 31 additions & 47 deletions app/components/Admin/AddAnalyst.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useState } from 'react';
import styled from 'styled-components';
import Button from '@button-inc/bcgov-theme/Button';
import Input from '@button-inc/bcgov-theme/Input';
import { useCreateAnalystMutation } from 'schema/mutations/analyst/createAnalyst';
import { InputLabel } from '@mui/material';
import { FormBase } from 'components/Form';
import { IChangeEvent } from '@rjsf/core';
import analyst from 'formSchema/admin/analyst';
import analystUiSchema from 'formSchema/uiSchema/admin/analystUiSchema';

interface Props {
relayConnectionId: string;
Expand All @@ -25,19 +27,11 @@ const StyledButtons = styled.div`
}
`;

const StyledInputs = styled.div`
display: flex;
margin-bottom: 16px;
& div:first-child,
& div:nth-child(2) {
margin-right: 16px;
}
`;

const StyledTransition = styled.div<TransistionProps>`
overflow: ${(props) => (props.show ? 'visible' : 'hidden')};
transition: max-height 0.5s, opacity 0.3s ease-in-out;
transition:
max-height 0.5s,
opacity 0.3s ease-in-out;
transition-delay: 0.1s;
opacity: ${(props) => (props.show ? 1 : 0)};
Expand All @@ -51,21 +45,24 @@ const StyledSection = styled.form`

const AddAnalyst: React.FC<Props> = ({ relayConnectionId }) => {
const [showInputs, setShowInputs] = useState(false);
const [familyName, setFamilyName] = useState('');
const [givenName, setGivenName] = useState('');
const [email, setEmail] = useState('');
const [formData, setFormData] = useState({});
const [createAnalyst] = useCreateAnalystMutation();

const resetForm = () => {
setFormData({});
};

const handleSubmit = (e) => {
e.preventDefault();
if (showInputs) {
createAnalyst({
variables: {
connections: [relayConnectionId],
input: { analyst: { familyName, givenName, email } },
input: { analyst: formData },
},
onCompleted: () => {
setShowInputs(false);
resetForm();
},
});
} else {
Expand All @@ -77,35 +74,16 @@ const AddAnalyst: React.FC<Props> = ({ relayConnectionId }) => {
<StyledSection>
<StyledTransition show={showInputs}>
<h4>New analyst</h4>
<StyledInputs>
<div>
<InputLabel htmlFor="givenName">Given Name</InputLabel>
<Input
name="givenName"
type="text"
value={givenName}
onChange={(e) => setGivenName(e.target.value)}
/>
</div>
<div>
<InputLabel htmlFor="familyName">Family Name</InputLabel>
<Input
name="familyName"
type="text"
value={familyName}
onChange={(e) => setFamilyName(e.target.value)}
/>
</div>
<div>
<InputLabel htmlFor="email">Email</InputLabel>
<Input
name="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</StyledInputs>
<FormBase
formData={formData}
onChange={(e: IChangeEvent) => setFormData({ ...e.formData })}
schema={analyst}
uiSchema={analystUiSchema as any}
onSubmit={handleSubmit}
// Pass children to hide submit button
// eslint-disable-next-line react/no-children-prop
children
/>
</StyledTransition>
<StyledButtons>
<Button
Expand All @@ -115,7 +93,13 @@ const AddAnalyst: React.FC<Props> = ({ relayConnectionId }) => {
{showInputs ? 'Add' : 'Add analyst'}
</Button>
{showInputs && (
<Button variant="secondary" onClick={() => setShowInputs(false)}>
<Button
variant="secondary"
onClick={() => {
setShowInputs(false);
resetForm();
}}
>
Cancel
</Button>
)}
Expand Down
6 changes: 5 additions & 1 deletion app/components/Analyst/Assessments/AssessmentsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useRef, useState } from 'react';
import styled from 'styled-components';
import { FormBase } from 'components/Form';
import Button from '@button-inc/bcgov-theme/Button';
Expand All @@ -10,6 +10,7 @@ import assessmentsUiSchema from 'formSchema/uiSchema/analyst/assessmentsUiSchema
import { RJSFSchema } from '@rjsf/utils';
import * as Sentry from '@sentry/nextjs';
import { useToast } from 'components/AppProvider';
import { FormBaseRef } from 'components/Form/FormBase';

interface Props {
addedContext?: any;
Expand Down Expand Up @@ -67,6 +68,7 @@ const AssessmentsForm: React.FC<Props> = ({
const [emailStatus, setEmailStatus] = useState<
'idle' | 'inProgress' | 'sent'
>('idle');
const formRef = useRef<FormBaseRef>(null);

const handleSubmit = async (e: IChangeEvent<any>) => {
if (!isFormSaved) {
Expand All @@ -81,6 +83,7 @@ const AssessmentsForm: React.FC<Props> = ({
},
onCompleted: () => {
setIsFormSaved(true);
formRef.current?.resetFormState(e.formData);
},
optimisticResponse: {
jsonData: e.formData,
Expand Down Expand Up @@ -135,6 +138,7 @@ const AssessmentsForm: React.FC<Props> = ({

return (
<StyledFormBase
ref={formRef}
schema={schema}
uiSchema={uiSchema || assessmentsUiSchema}
noValidate
Expand Down
12 changes: 10 additions & 2 deletions app/components/Analyst/Project/ProjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Accordion from 'components/Accordion';
import { FormBase } from 'components/Form';
import CircularProgress from '@mui/material/CircularProgress';
import { Collapse } from '@mui/material';
import { FormBaseRef } from 'components/Form/FormBase';
import { useRef } from 'react';
import ProjectTheme from './ProjectTheme';
import { ProjectFormProps } from './ProjectFormProps';

Expand Down Expand Up @@ -89,6 +91,7 @@ const ProjectForm: React.FC<ProjectFormProps> = ({
...rest
}) => {
const stopPropagation = (e) => e.stopPropagation();
const formRef = useRef<FormBaseRef>(null);

return (
<Accordion
Expand All @@ -102,7 +105,10 @@ const ProjectForm: React.FC<ProjectFormProps> = ({
id={`${title.toLowerCase().split(' ').join('-')}-save-button`}
size="small"
disabled={saveBtnDisabled}
onClick={onSubmit}
onClick={(e) => {
onSubmit(e);
formRef.current?.resetFormState(e.formData);
}}
>
{saveBtnText || 'Save'}
</StyledBtn>
Expand All @@ -111,8 +117,9 @@ const ProjectForm: React.FC<ProjectFormProps> = ({
variant="secondary"
disabled={cancelBtnDisabled}
onClick={() => {
setFormData();
setFormData({});
setIsFormEditMode(false);
formRef.current?.resetFormState({});
}}
>
Cancel
Expand Down Expand Up @@ -158,6 +165,7 @@ const ProjectForm: React.FC<ProjectFormProps> = ({
{formHeader}
<FormBase
// setting a key here will reset the form
ref={formRef}
key={isFormEditMode ? 'edit' : 'view'}
schema={schema}
uiSchema={uiSchema}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,7 @@ const ProjectInformationForm: React.FC<Props> = ({
handleChange={(e) => {
setHasFormSaved(false);
if (!isChangeRequest && !e.formData.hasFundingAgreementBeenSigned) {
setFormData({
hasFundingAgreementBeenSigned: false,
});
setFormData({});
} else {
setFormData({ ...e.formData });
}
Expand Down
13 changes: 8 additions & 5 deletions app/components/Analyst/RFI/RfiForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { graphql, useFragment } from 'react-relay';
import { RfiForm_RfiData$key } from '__generated__/RfiForm_RfiData.graphql';
import { useUpdateWithTrackingRfiMutation } from 'schema/mutations/application/updateWithTrackingRfiMutation';
import removeFalseyValuesFromObject from 'utils/removeFalseValuesFromObject';
import { useState } from 'react';
import RfiTheme from './RfiTheme';

const StyledCancel = styled(Button)`
Expand Down Expand Up @@ -44,9 +45,10 @@ const RfiForm = ({ rfiDataKey }: RfiFormProps) => {
const rfiUrl = `/analyst/application/${applicationId}/rfi`;
const [createRfi] = useCreateRfiMutation();
const [updateRfi] = useUpdateWithTrackingRfiMutation();
const [formData, setFormData] = useState(rfiFormData?.jsonData ?? {});

const handleSubmit = (e: IChangeEvent<any>) => {
const formData = {
const newFormData = {
...e.formData,
rfiAdditionalFiles: {
// Remove fields with false values from object to prevent unintended bugs when
Expand All @@ -60,7 +62,7 @@ const RfiForm = ({ rfiDataKey }: RfiFormProps) => {
variables: {
input: {
applicationRowId: parseInt(applicationId as string, 10),
jsonData: formData,
jsonData: newFormData,
},
},
onCompleted: () => {
Expand All @@ -75,7 +77,7 @@ const RfiForm = ({ rfiDataKey }: RfiFormProps) => {
updateRfi({
variables: {
input: {
jsonData: formData,
jsonData: newFormData,
rfiRowId: parseInt(rfiId as string, 10),
},
},
Expand All @@ -98,12 +100,13 @@ const RfiForm = ({ rfiDataKey }: RfiFormProps) => {
schema={rfiSchema}
uiSchema={rfiUiSchema}
omitExtraData={false}
formData={rfiFormData?.jsonData ?? {}}
formData={formData}
onChange={(e) => setFormData(e.formData)}
onSubmit={handleSubmit}
noValidate
>
<Button>Save</Button>
<Link href={rfiUrl} passHref>
<Link href={rfiUrl} passHref data-skip-unsaved-warning>
<StyledCancel variant="secondary">Cancel</StyledCancel>
</Link>
</FormBase>
Expand Down
15 changes: 9 additions & 6 deletions app/components/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, {
ReactNode,
} from 'react';
import Toast, { ToastType } from 'components/Toast';
import UnsavedChangesProvider from 'components/UnsavedChangesProvider';

type AppContextType = {
showToast?: (message: ReactNode, type?: ToastType, timeout?: number) => void;
Expand Down Expand Up @@ -52,12 +53,14 @@ export const AppProvider = ({ children }) => {

return (
<AppContext.Provider value={contextValue}>
{children}
{toast?.visible && (
<Toast type={toast?.type} onClose={hideToast} timeout={toast.timeout}>
{toast.message}
</Toast>
)}
<UnsavedChangesProvider>
{children}
{toast?.visible && (
<Toast type={toast?.type} onClose={hideToast} timeout={toast.timeout}>
{toast.message}
</Toast>
)}
</UnsavedChangesProvider>
</AppContext.Provider>
);
};
10 changes: 8 additions & 2 deletions app/components/ButtonLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ type Props = {
disabled?: boolean;
};

const ButtonLink = ({ children, href, onClick, disabled = false }: Props) => (
<Link href={href} passHref>
const ButtonLink = ({
children,
href,
onClick,
disabled = false,
...rest
}: Props) => (
<Link href={href} passHref {...rest}>
<Button onClick={onClick} disabled={disabled}>
{children}
</Button>
Expand Down
1 change: 1 addition & 0 deletions app/components/Form/ApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ const ApplicationForm: React.FC<Props> = ({
isProjectAreaSelected,
intakeNumber,
isRollingIntake,
skipUnsavedWarning: true,
};
}, [
openIntake,
Expand Down
Loading

0 comments on commit 82fd8ef

Please sign in to comment.