From 9889df11478307942d9859b84931b0185ee0eaae Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 16 May 2022 15:15:40 -0700 Subject: [PATCH] feat: add milestone-reports page --- .../Form/ProjectMilestoneReportForm.tsx | 297 ++++++++++++++++++ .../form/milestone-reports.tsx | 57 ++++ 2 files changed, 354 insertions(+) create mode 100644 app/components/Form/ProjectMilestoneReportForm.tsx create mode 100644 app/pages/cif/project-revision/[projectRevision]/form/milestone-reports.tsx diff --git a/app/components/Form/ProjectMilestoneReportForm.tsx b/app/components/Form/ProjectMilestoneReportForm.tsx new file mode 100644 index 0000000000..627e8540ba --- /dev/null +++ b/app/components/Form/ProjectMilestoneReportForm.tsx @@ -0,0 +1,297 @@ +import { Button } from "@button-inc/bcgov-theme"; +import { faPlusCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import projectReportingRequirementSchema from "data/jsonSchemaForm/projectReportingRequirementSchema"; +import useDiscardFormChange from "hooks/useDiscardFormChange"; +import { JSONSchema7 } from "json-schema"; +import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; +import FormBorder from "lib/theme/components/FormBorder"; +import EmptyObjectFieldTemplate from "lib/theme/EmptyObjectFieldTemplate"; +import { useAddReportingRequirementToRevision } from "mutations/ProjectReportingRequirement/addReportingRequirementToRevision.ts"; +import { useUpdateReportingRequirementFormChange } from "mutations/ProjectReportingRequirement/updateReportingRequirementFormChange"; +import { useMemo, useRef } from "react"; +import { graphql, useFragment } from "react-relay"; +import { FormChangeOperation } from "__generated__/ProjectContactForm_projectRevision.graphql"; +import { ProjectMilestoneReportForm_projectRevision$key } from "__generated__/ProjectMilestoneReportForm_projectRevision.graphql"; +import FormBase from "./FormBase"; +import SavingIndicator from "./SavingIndicator"; + +interface Props { + onSubmit: () => void; + projectRevision: ProjectMilestoneReportForm_projectRevision$key; +} + +const milestoneReportUiSchema = { + reportDueDate: { + "ui:col-md": 12, + "bcgov:size": "small", + "ui:widget": "date", + }, + submittedDate: { + "ui:col-md": 12, + "bcgov:size": "small", + "ui:widget": "date", + }, + comments: { + "ui:col-md": 12, + "bcgov:size": "small", + "ui:widget": "TextAreaWidget", + }, +}; + +const ProjectMilestoneReportForm: React.FC = (props) => { + const formRefs = useRef({}); + + const projectRevision = useFragment( + // The JSON string is tripping up eslint + // eslint-disable-next-line relay/graphql-syntax + graphql` + fragment ProjectMilestoneReportForm_projectRevision on ProjectRevision { + id + rowId + milestoneReportFormChanges: formChangesFor( + first: 1000 + formDataTableName: "reporting_requirement" + jsonMatcher: "{\"reportType\":\"Milestone\"}" + ) + @connection(key: "connection_milestoneReportFormChanges") { + __id + edges { + node { + rowId + id + newFormData + operation + changeStatus + formChangeByPreviousFormChangeId { + changeStatus + newFormData + } + } + } + } + projectFormChange { + formDataRecordId + } + } + `, + props.projectRevision + ); + const [addMilestoneReportMutation, isAdding] = + useAddReportingRequirementToRevision(); + + const addMilestoneReport = (reportIndex: number) => { + const formData = { + status: "on_track", + projectId: projectRevision.projectFormChange.formDataRecordId, + reportType: "Milestone", + reportingRequirementIndex: reportIndex, + }; + addMilestoneReportMutation({ + variables: { + projectRevisionId: projectRevision.rowId, + newFormData: formData, + connections: [projectRevision.milestoneFormChanges.__id], + }, + }); + }; + + const [applyUpdateFormChangeMutation, isUpdating] = + useUpdateReportingRequirementFormChange(); + + const updateFormChange = ( + formChange: { + readonly id: string; + readonly newFormData: any; + readonly operation: FormChangeOperation; + readonly changeStatus: string; + }, + newFormData: any + ) => { + applyUpdateFormChangeMutation({ + variables: { + input: { + id: formChange.id, + formChangePatch: { + newFormData, + changeStatus: formChange.changeStatus, + }, + }, + }, + debounceKey: formChange.id, + optimisticResponse: { + updateFormChange: { + formChange: { + id: formChange.id, + newFormData: newFormData, + changeStatus: formChange.changeStatus, + }, + }, + }, + }); + }; + + const [discardFormChange] = useDiscardFormChange( + projectRevision.milestoneFormChanges.__id + ); + + const deleteMilestoneReport = ( + formChangeId: string, + formChangeOperation: FormChangeOperation + ) => { + discardFormChange({ + formChange: { id: formChangeId, operation: formChangeOperation }, + onCompleted: () => { + delete formRefs.current[formChangeId]; + }, + }); + }; + + const stageMilestoneReportFormChanges = async () => { + const validationErrors = Object.keys(formRefs.current).reduce( + (agg, formId) => { + const formObject = formRefs.current[formId]; + return [...agg, ...validateFormWithErrors(formObject)]; + }, + [] + ); + + const completedPromises: Promise[] = []; + + projectRevision.milestoneFormChanges.edges.forEach(({ node }) => { + if (node.changeStatus === "pending") { + const promise = new Promise((resolve, reject) => { + applyUpdateFormChangeMutation({ + variables: { + input: { + id: node.id, + formChangePatch: { + changeStatus: "staged", + }, + }, + }, + debounceKey: node.id, + onCompleted: () => { + resolve(); + }, + onError: reject, + }); + }); + completedPromises.push(promise); + } + }); + try { + await Promise.all(completedPromises); + + if (validationErrors.length === 0) props.onSubmit(); + } catch (e) { + // the failing mutation will display an error message and send the error to sentry + } + }; + + const [sortedMilestoneReports, nextMilestoneReportIndex] = useMemo(() => { + const filteredReports = projectRevision.milestoneFormChanges.edges + .map(({ node }) => node) + .filter((report) => report.operation !== "ARCHIVE"); + + filteredReports.sort( + (a, b) => + a.newFormData.reportingRequirementIndex - + b.newFormData.reportingRequirementIndex + ); + const nextIndex = + filteredReports.length > 0 + ? filteredReports[filteredReports.length - 1].newFormData + .reportingRequirementIndex + 1 + : 1; + + return [filteredReports, nextIndex]; + }, [projectRevision.milestoneFormChanges]); + + return ( +
+
+

Milestone Reports

+ +
+ +
Milestone reports status here
+ + + + + {sortedMilestoneReports.map((milestoneReport, index) => { + return ( +
+
+

Milestone Report {index + 1}

+ +
+ (formRefs.current[milestoneReport.id] = el)} + formData={milestoneReport.newFormData} + onChange={(change) => { + updateFormChange( + { ...milestoneReport, changeStatus: "pending" }, + change.formData + ); + }} + schema={projectReportingRequirementSchema as JSONSchema7} + uiSchema={milestoneReportUiSchema} + ObjectFieldTemplate={EmptyObjectFieldTemplate} + /> +
+ ); + })} +
+ + + +
+ ); +}; + +export default ProjectMilestoneReportForm; diff --git a/app/pages/cif/project-revision/[projectRevision]/form/milestone-reports.tsx b/app/pages/cif/project-revision/[projectRevision]/form/milestone-reports.tsx new file mode 100644 index 0000000000..63c05508dd --- /dev/null +++ b/app/pages/cif/project-revision/[projectRevision]/form/milestone-reports.tsx @@ -0,0 +1,57 @@ +import DefaultLayout from "components/Layout/DefaultLayout"; +import { withRelay, RelayProps } from "relay-nextjs"; +import { graphql, usePreloadedQuery } from "react-relay/hooks"; +import withRelayOptions from "lib/relay/withRelayOptions"; +import { useRouter } from "next/router"; +import { getProjectRevisionPageRoute } from "pageRoutes"; +import TaskList from "components/TaskList"; +import useRedirectTo404IfFalsy from "hooks/useRedirectTo404IfFalsy"; + +import ProjectMilestoneReportForm from "components/Form/ProjectMilestoneReportForm"; +import { milestoneReportsFormQuery } from "__generated__/milestoneReportsFormQuery.graphql"; + +const pageQuery = graphql` + query milestoneReportsFormQuery($projectRevision: ID!) { + query { + session { + ...DefaultLayout_session + } + projectRevision(id: $projectRevision) { + id + ...ProjectMilestoneReportForm_projectRevision + ...TaskList_projectRevision + } + } + } +`; + +export function ProjectMilestoneReportsPage({ + preloadedQuery, +}: RelayProps<{}, milestoneReportsFormQuery>) { + const { query } = usePreloadedQuery(pageQuery, preloadedQuery); + const router = useRouter(); + + const isRedirecting = useRedirectTo404IfFalsy(query.projectRevision); + if (isRedirecting) return null; + + const taskList = ; + + const handleSubmit = () => { + router.push(getProjectRevisionPageRoute(query.projectRevision.id)); + }; + + return ( + + + + ); +} + +export default withRelay( + ProjectMilestoneReportsPage, + pageQuery, + withRelayOptions +);