Skip to content

Commit

Permalink
feat: ProjectContactForm stages forms
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieu-foucault committed Mar 30, 2022
1 parent 14a5310 commit 4a0f9df
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 99 deletions.
109 changes: 91 additions & 18 deletions app/components/Form/ProjectContactForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import { Button } from "@button-inc/bcgov-theme";
import { mutation as addContactToRevisionMutation } from "mutations/Contact/addContactToRevision";
import { useUpdateFormChange } from "mutations/FormChange/updateFormChange";
import projectContactSchema from "data/jsonSchemaForm/projectContactSchema";
import { ValidatingFormProps } from "./Interfaces/FormValidationTypes";
import validateFormWithErrors from "lib/helpers/validateFormWithErrors";
import {
ProjectContactForm_projectRevision$key,
FormChangeOperation,
} from "__generated__/ProjectContactForm_projectRevision.graphql";
import useDiscardFormChange from "hooks/useDiscardFormChange";
import useMutationWithErrorMessage from "mutations/useMutationWithErrorMessage";
import SavingIndicator from "./SavingIndicator";

interface Props extends ValidatingFormProps {
interface Props {
query: ProjectContactForm_query$key;
onSubmit: () => void;
projectRevision: ProjectContactForm_projectRevision$key;
}

Expand Down Expand Up @@ -52,13 +52,16 @@ const ProjectContactForm: React.FC<Props> = (props) => {
id
newFormData
operation
changeStatus
updatedAt
}
}
}
}
`,
props.projectRevision
);
const { projectContactFormChanges } = projectRevision;

const { allContacts } = useFragment(
graphql`
Expand All @@ -76,6 +79,13 @@ const ProjectContactForm: React.FC<Props> = (props) => {
props.query
);

const lastEditedDate = useMemo(() => {
const mostRecentUpdate = projectContactFormChanges.edges
.map((e) => e.node.updatedAt)
.sort((a, b) => Date.parse(b) - Date.parse(a))[0];
return new Date(mostRecentUpdate);
}, [projectContactFormChanges]);

const contactSchema = useMemo(() => {
const schema = projectContactSchema;
schema.properties.contactId = {
Expand Down Expand Up @@ -112,20 +122,20 @@ const ProjectContactForm: React.FC<Props> = (props) => {

const allForms = useMemo(() => {
const contactForms = [
...projectRevision.projectContactFormChanges.edges
...projectContactFormChanges.edges
.filter(({ node }) => node.operation !== "ARCHIVE")
.map(({ node }) => node),
];
contactForms.sort(
(a, b) => a.newFormData.contactIndex - b.newFormData.contactIndex
);
return contactForms;
}, [projectRevision]);
}, [projectContactFormChanges]);

const [primaryContactForm, ...alternateContactForms] = allForms;
const [applyUpdateFormChangeMutation] = useUpdateFormChange();
const [applyUpdateFormChangeMutation, isUpdating] = useUpdateFormChange();
const [discardFormChange] = useDiscardFormChange(
projectRevision.projectContactFormChanges.__id
projectContactFormChanges.__id
);

const deleteContact = (
Expand All @@ -145,6 +155,7 @@ const ProjectContactForm: React.FC<Props> = (props) => {
readonly id: string;
readonly newFormData: any;
readonly operation: FormChangeOperation;
readonly changeStatus: string;
},
newFormData: any
) => {
Expand All @@ -154,6 +165,7 @@ const ProjectContactForm: React.FC<Props> = (props) => {
id: formChange.id,
formChangePatch: {
newFormData,
changeStatus: formChange.changeStatus,
},
},
},
Expand All @@ -162,29 +174,73 @@ const ProjectContactForm: React.FC<Props> = (props) => {
formChange: {
...formChange,
newFormData,
changeStatus: formChange.changeStatus,
},
},
},
debounceKey: formChange.id,
});
};

props.setValidatingForm({
selfValidate: () => {
return Object.keys(formRefs.current).reduce((agg, formId) => {
const formObject = formRefs.current[formId];
return [...agg, ...validateFormWithErrors(formObject)];
}, []);
},
});

const clearPrimaryContact = () => {
const { contactId, ...newFormData } = primaryContactForm.newFormData;
updateFormChange(primaryContactForm, newFormData);
};

const stageContactFormChanges = async () => {
const validationErrors = Object.keys(formRefs.current).reduce(
(agg, formId) => {
const formObject = formRefs.current[formId];
return [...agg, ...validateFormWithErrors(formObject)];
},
[]
);

if (validationErrors.length > 0) return;

const completedPromises: Promise<void>[] = [];

projectContactFormChanges.edges.forEach(({ node }) => {
if (node.changeStatus === "pending") {
const promise = new Promise<void>((resolve) => {
applyUpdateFormChangeMutation({
variables: {
input: {
id: node.id,
formChangePatch: {
changeStatus: "staged",
},
},
},
optimisticResponse: {
updateFormChange: {
formChange: {
changeStatus: "staged",
},
},
},
debounceKey: node.id,
onCompleted: () => {
resolve();
},
});
});
completedPromises.push(promise);
}
});

await Promise.all(completedPromises);

props.onSubmit();
};

return (
<div>
<header>
<h2>Project Contacts</h2>
<SavingIndicator isSaved={!isUpdating} lastEdited={lastEditedDate} />
</header>

<Grid cols={10} align="center">
<Grid.Row>
<Grid.Col span={10}>
Expand All @@ -207,7 +263,10 @@ const ProjectContactForm: React.FC<Props> = (props) => {
ref={(el) => (formRefs.current[primaryContactForm.id] = el)}
formData={primaryContactForm.newFormData}
onChange={(change) => {
updateFormChange(primaryContactForm, change.formData);
updateFormChange(primaryContactForm, {
...change.formData,
changeStatus: "pending",
});
}}
schema={contactSchema}
uiSchema={uiSchema}
Expand Down Expand Up @@ -237,7 +296,10 @@ const ProjectContactForm: React.FC<Props> = (props) => {
ref={(el) => (formRefs.current[form.id] = el)}
formData={form.newFormData}
onChange={(change) => {
updateFormChange(form, change.formData);
updateFormChange(form, {
...change.formData,
changeStatus: "pending",
});
}}
schema={contactSchema}
uiSchema={uiSchema}
Expand Down Expand Up @@ -272,6 +334,17 @@ const ProjectContactForm: React.FC<Props> = (props) => {
</Button>
</Grid.Col>
</Grid.Row>

<Grid.Row>
<Button
size="medium"
variant="primary"
onClick={stageContactFormChanges}
disabled={isUpdating}
>
Submit Contacts
</Button>
</Grid.Row>
</FormBorder>
</Grid.Col>
</Grid.Row>
Expand Down
86 changes: 5 additions & 81 deletions app/pages/cif/project-revision/[projectRevision]/form/contacts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@ import { graphql, usePreloadedQuery } from "react-relay/hooks";
import { contactsFormQuery } from "__generated__/contactsFormQuery.graphql";
import withRelayOptions from "lib/relay/withRelayOptions";
import { useRouter } from "next/router";
import { Button } from "@button-inc/bcgov-theme";
import { useMemo, useRef } from "react";
import SavingIndicator from "components/Form/SavingIndicator";
import { mutation as updateProjectRevisionMutation } from "mutations/ProjectRevision/updateProjectRevision";
import { useMutation } from "react-relay";
import { getProjectsPageRoute } from "pageRoutes";
import { getProjectRevisionPageRoute } from "pageRoutes";
import ProjectContactForm from "components/Form/ProjectContactForm";
import { ISupportExternalValidation } from "components/Form/Interfaces/FormValidationTypes";
import TaskList from "components/TaskList";

const pageQuery = graphql`
Expand All @@ -22,7 +16,6 @@ const pageQuery = graphql`
}
projectRevision(id: $projectRevision) {
id
updatedAt
...ProjectContactForm_projectRevision
...TaskList_projectRevision
}
Expand All @@ -34,94 +27,25 @@ const pageQuery = graphql`
export function ProjectRevision({
preloadedQuery,
}: RelayProps<{}, contactsFormQuery>) {
const projectContactFormRef = useRef<ISupportExternalValidation>(null);

const router = useRouter();
const { query } = usePreloadedQuery(pageQuery, preloadedQuery);

const [updateProjectRevision, updatingProjectRevision] = useMutation(
updateProjectRevisionMutation
);

const lastEditedDate = useMemo(
() => new Date(query.projectRevision.updatedAt),
[query.projectRevision.updatedAt]
);
const { query } = usePreloadedQuery(pageQuery, preloadedQuery);

if (!query.projectRevision.id) return null;

/**
* Function: approve staged change, trigger an insert on the project
* table & redirect to the project page
*/
const commitProject = async () => {
const errors = [...projectContactFormRef.current.selfValidate()];

if (errors.length > 0) {
console.log("Could not submit a form with errors: ", errors);
return;
}

updateProjectRevision({
variables: {
input: {
id: query.projectRevision.id,
projectRevisionPatch: { changeStatus: "committed" },
},
},
// No need for an optimistic response
// Since we navigate away from the page after the mutation is complete
onCompleted: async () => {
await router.push(getProjectsPageRoute());
},
updater: (store) => {
// Invalidate the entire store,to make sure that we don't display any stale data after redirecting to the next page.
// This could be optimized to only invalidate the affected records.
store.invalidateStore();
},
});
const handleSubmit = () => {
router.push(getProjectRevisionPageRoute(query.projectRevision.id));
};

const taskList = <TaskList projectRevision={query.projectRevision} />;

return (
<DefaultLayout session={query.session} leftSideNav={taskList}>
<header>
<h2>Project Overview</h2>
<SavingIndicator
isSaved={!updatingProjectRevision}
lastEdited={lastEditedDate}
/>
</header>

<ProjectContactForm
query={query}
projectRevision={query.projectRevision}
setValidatingForm={(validator) =>
(projectContactFormRef.current = validator)
}
onSubmit={handleSubmit}
/>

<Button
size="medium"
variant="primary"
onClick={commitProject}
disabled={updatingProjectRevision}
>
Submit
</Button>

<style jsx>{`
header {
display: flex;
justify-content: space-between;
align-items: start;
}
:global(.pg-button) {
margin-right: 3em;
}
`}</style>
</DefaultLayout>
);
}
Expand Down

0 comments on commit 4a0f9df

Please sign in to comment.