diff --git a/services/common/src/components/forms/FormWrapper.tsx b/services/common/src/components/forms/FormWrapper.tsx index 707f047d55..14eb1f03ba 100644 --- a/services/common/src/components/forms/FormWrapper.tsx +++ b/services/common/src/components/forms/FormWrapper.tsx @@ -56,7 +56,7 @@ NOTABLE OMISSIONS: SEE ALSO: - BaseInput.tsx */ -interface FormWrapperProps { +export interface FormWrapperProps { name: string; initialValues?: any; reduxFormConfig?: Partial; diff --git a/services/common/src/components/forms/RenderFileUpload.tsx b/services/common/src/components/forms/RenderFileUpload.tsx index 915f48a229..430dcf3eee 100644 --- a/services/common/src/components/forms/RenderFileUpload.tsx +++ b/services/common/src/components/forms/RenderFileUpload.tsx @@ -134,8 +134,11 @@ export const FileUpload = (props: FileUploadProps) => { const fileTypeList = listedFileTypes ?? Object.keys(acceptedFileTypesMap); const fileTypeDisplayString = fileTypeList.slice(0, -1).join(", ") + ", and " + fileTypeList.slice(-1); + const fileSize = props.maxFileSize + ? ` with max individual file size of ${props.maxFileSize}` + : ""; const secondLine = abbrevLabel - ? `
We accept most common ${fileTypeDisplayString} files
` + ? `
We accept most common ${fileTypeDisplayString} files${fileSize}.
` : `
Accepted filetypes: ${fileTypeDisplayString}
`; return `${labelInstruction}
${secondLine}`; }; diff --git a/services/common/src/components/forms/RenderSelect.tsx b/services/common/src/components/forms/RenderSelect.tsx index 3f503f8786..71b01a4c9c 100644 --- a/services/common/src/components/forms/RenderSelect.tsx +++ b/services/common/src/components/forms/RenderSelect.tsx @@ -61,6 +61,7 @@ export const RenderSelect: FC = ({ > + + + +
+ +
+ + + + +
+ Supporting Documents +
+
+ Upload any supporting document and draft of + + + Information Requirements Table (IRT) + + + following the official template here. It is required to upload your final IRT in the form provided to proceed to the final application. +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + File Name + + + + + + + + + + + +
+
+ Document Category + +
+ + Updated + + + + + + + + + + + +
+
+
+ + Updated By + + + + + + + + + + + +
+
+
+ No Data Yet +
+
+
+
+
+
+
+
+
+
+ + + + + +`; diff --git a/services/common/src/components/projectSummary/__snapshots__/ProjectDates.spec.tsx.snap b/services/common/src/components/projectSummary/__snapshots__/ProjectDates.spec.tsx.snap new file mode 100644 index 0000000000..f6e2e8ce34 --- /dev/null +++ b/services/common/src/components/projectSummary/__snapshots__/ProjectDates.spec.tsx.snap @@ -0,0 +1,404 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProjectDates renders properly 1`] = ` +
+
+

+ Project Dates +

+
+ These dates are for guidance and planning purposes only and do not reflect actual delivery dates. The + + + Major Mines Office + + + will work with you on a more definitive schedule. +
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
+
+
+ +
+
+
+ +
+`; diff --git a/services/common/src/models/documents/document.ts b/services/common/src/models/documents/document.ts index 9b85b6c842..4eb28c2337 100644 --- a/services/common/src/models/documents/document.ts +++ b/services/common/src/models/documents/document.ts @@ -163,10 +163,10 @@ export class MineDocument implements IMineDocument { _userRoles: string[] = [], is_latest_version: boolean = this.is_latest_version ) { - const canModify = is_latest_version && !this.is_archived; - if (!this.mine_document_guid) return []; + const canModify = is_latest_version && !this.is_archived && this.mine_document_guid; + const canView = this.file_type === ".pdf" && this.document_manager_guid; return [ - this.file_type === ".pdf" && FileOperations.View, + canView && FileOperations.View, FileOperations.Download, isFeatureEnabled(Feature.DOCUMENTS_REPLACE_FILE) && canModify && FileOperations.Replace, canModify && FileOperations.Archive, diff --git a/services/common/src/redux/selectors/projectSelectors.ts b/services/common/src/redux/selectors/projectSelectors.ts index 13a3473c5d..2d16c91a72 100644 --- a/services/common/src/redux/selectors/projectSelectors.ts +++ b/services/common/src/redux/selectors/projectSelectors.ts @@ -1,7 +1,7 @@ import { createSelector } from "reselect"; import { uniq } from "lodash"; import * as projectReducer from "../reducers/projectReducer"; -import { IParty, IProjectContact } from "../.."; +import { IParty, IProjectContact, IProjectSummaryDocument } from "../.."; import { getTransformedProjectSummaryAuthorizationTypes } from "./staticContentSelectors"; export const { @@ -26,6 +26,21 @@ const formatProjectSummaryParty = (party): IParty => { return { ...party, address: party.address[0] }; }; +const formatProjectSummaryDocuments = (documents = []): IProjectSummaryDocument[] => { + const allDocuments: any = { documents }; + const fieldNameMap = { + support_documents: "SPR", + spatial_documents: "SPT", + }; + Object.entries(fieldNameMap).forEach(([fieldName, docTypeCode]) => { + const matching = documents.filter( + (doc) => doc.project_summary_document_type_code === docTypeCode + ); + allDocuments[fieldName] = matching; + }); + return allDocuments; +}; + const formatProjectContact = (contacts): IProjectContact[] => { if (!contacts) { return contacts; @@ -78,6 +93,7 @@ export const getAmsAuthorizationTypes = createSelector( export const getFormattedProjectSummary = createSelector( [getProjectSummary, getProject, getAmsAuthorizationTypes], (summary, project, amsAuthTypes) => { + const documents = formatProjectSummaryDocuments(summary.documents); const contacts = formatProjectContact(project.contacts); const agent = formatProjectSummaryParty(summary.agent); const facility_operator = formatProjectSummaryParty(summary.facility_operator); @@ -90,6 +106,7 @@ export const getFormattedProjectSummary = createSelector( facility_operator, confirmation_of_submission, ...formatAuthorizations(amsAuthTypes, summary.status_code, summary.authorizations), + ...documents, }; formattedSummary.project_lead_party_guid = project.project_lead_party_guid; diff --git a/services/common/src/setupTests.ts b/services/common/src/setupTests.ts index b287af258b..8388132111 100644 --- a/services/common/src/setupTests.ts +++ b/services/common/src/setupTests.ts @@ -13,6 +13,14 @@ Enzyme.configure({ adapter: new Adapter() }); setTimeout(callback, 0); // eslint-disable-line @typescript-eslint/no-implied-eval }; +jest.mock("react", () => { + const original = jest.requireActual("react"); + return { + ...original, + useLayoutEffect: jest.fn(), + }; +}); + jest.mock("react-lottie", () => ({ __esModule: true, default: "lottie-mock", @@ -23,7 +31,7 @@ jest.mock("@mds/common/providers/featureFlags/useFeatureFlag", () => ({ isFeatureEnabled: () => true, }), })); - +window.scrollTo = jest.fn(); const location = JSON.stringify(window.location); delete window.location; diff --git a/services/core-api/app/api/projects/project_summary/models/project_summary.py b/services/core-api/app/api/projects/project_summary/models/project_summary.py index 2cc8e5bd39..5bedf84b40 100644 --- a/services/core-api/app/api/projects/project_summary/models/project_summary.py +++ b/services/core-api/app/api/projects/project_summary/models/project_summary.py @@ -186,11 +186,8 @@ def proponent_project_id(self): return None @classmethod - def find_by_project_summary_guid(cls, project_summary_guid, is_minespace_user=False): - if is_minespace_user: - return cls.query.filter_by( - project_summary_guid=project_summary_guid, deleted_ind=False).one_or_none() - return cls.query.filter(ProjectSummary.status_code.is_distinct_from("DFT")).filter_by( + def find_by_project_summary_guid(cls, project_summary_guid): + return cls.query.filter_by( project_summary_guid=project_summary_guid, deleted_ind=False).one_or_none() @classmethod @@ -198,11 +195,8 @@ def find_by_mine_guid(cls, mine_guid_to_search): return cls.query.filter(cls.mine_guid == mine_guid_to_search).all() @classmethod - def find_by_project_guid(cls, project_guid, is_minespace_user): - if is_minespace_user: - return cls.query.filter_by(project_guid=project_guid, deleted_ind=False).all() - return cls.query.filter(ProjectSummary.status_code.is_distinct_from("DFT")).filter_by( - project_guid=project_guid, deleted_ind=False).all() + def find_by_project_guid(cls, project_guid): + return cls.query.filter_by(project_guid=project_guid, deleted_ind=False).all() @classmethod def find_by_mine_document_guid(cls, mine_document_guid): diff --git a/services/core-api/app/api/projects/project_summary/resources/project_summary.py b/services/core-api/app/api/projects/project_summary/resources/project_summary.py index 6c275a85fd..f2cfd37e20 100644 --- a/services/core-api/app/api/projects/project_summary/resources/project_summary.py +++ b/services/core-api/app/api/projects/project_summary/resources/project_summary.py @@ -176,8 +176,7 @@ class ProjectSummaryResource(Resource, UserMixin): @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) @api.marshal_with(PROJECT_SUMMARY_MODEL, code=200) def get(self, project_guid, project_summary_guid): - project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid, - is_minespace_user()) + project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid) if project_summary is None: raise NotFound('Project Description not found') @@ -192,8 +191,7 @@ def get(self, project_guid, project_summary_guid): @requires_any_of([MINE_ADMIN, MINESPACE_PROPONENT, EDIT_PROJECT_SUMMARIES]) @api.marshal_with(PROJECT_SUMMARY_MODEL, code=200) def put(self, project_guid, project_summary_guid): - project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid, - is_minespace_user()) + project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid) project = Project.find_by_project_guid(project_guid) data = self.parser.parse_args() @@ -274,8 +272,7 @@ def put(self, project_guid, project_summary_guid): @requires_any_of([MINE_ADMIN, MINESPACE_PROPONENT, EDIT_PROJECT_SUMMARIES]) @api.response(204, 'Successfully deleted.') def delete(self, project_guid, project_summary_guid): - project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid, - is_minespace_user()) + project_summary = ProjectSummary.find_by_project_summary_guid(project_summary_guid) if project_summary is None: raise NotFound('Project Description not found') diff --git a/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py b/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py index 2177281b3c..2fe0698adc 100644 --- a/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py +++ b/services/core-api/app/api/projects/project_summary/resources/project_summary_list.py @@ -35,7 +35,7 @@ def get(self, project_guid): project_summaries = [] for project in projects: project_project_summaries = ProjectSummary.find_by_project_guid( - project.project_guid, is_minespace_user()) + project.project_guid) project_summaries = [*project_summaries, *project_project_summaries] return project_summaries diff --git a/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py b/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py index a79d746357..d951138098 100644 --- a/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py +++ b/services/core-api/tests/projects/project_summaries/models/test_project_summary_model.py @@ -6,14 +6,14 @@ def test_project_summary_find_by_project_summary_guid(db_session): project_summary = ProjectSummaryFactory() project_summary_guid = project_summary.project_summary_guid - project_summary = ProjectSummary.find_by_project_summary_guid(str(project_summary_guid), True) + project_summary = ProjectSummary.find_by_project_summary_guid(str(project_summary_guid)) assert project_summary.project_summary_guid == project_summary_guid def test_project_summary_find_by_project_guid(db_session): batch_size = 1 project = ProjectFactory.create_batch(size=batch_size) - project_summaries = ProjectSummary.find_by_project_guid(str(project[0].project_guid), True) + project_summaries = ProjectSummary.find_by_project_guid(str(project[0].project_guid)) assert len(project_summaries) == batch_size assert all(project_summary.project_guid == project[0].project_guid diff --git a/services/core-web/cypress/e2e/createproject.cy.ts b/services/core-web/cypress/e2e/createproject.cy.ts index 9285549ec1..93d93419e0 100644 --- a/services/core-web/cypress/e2e/createproject.cy.ts +++ b/services/core-web/cypress/e2e/createproject.cy.ts @@ -35,17 +35,142 @@ describe("Major Projects", () => { cy.get("#project_summary_description").type("This is just a Cypress test project description", { force: true, }); - cy.get("#contacts\\[0\\]\\.first_name").type("Cypress", { force: true }); - cy.get("#contacts\\[0\\]\\.last_name").type("Test", { force: true }); - cy.get("#contacts\\[0\\]\\.email").type("cypress@mds.com", { force: true }); - cy.get("#contacts\\[0\\]\\.phone_number").type("1234567890", { force: true }); + + // SAVE & CONTINUE - skip to Purpose & Authorization + cy.contains("Save & Continue").click({ force: true }); + cy.contains("Project Lead", { timeout: 10000 }); + cy.contains("Purpose and Authorization", { timeout: 10000 }).click({ force: true }); + cy.contains("Regulatory Approval Type", { timeout: 10000 }); + cy.get('[data-cy="checkbox-authorization-OTHER"]').click({ force: true }); cy.get( '[name="authorizations.OTHER[0].authorization_description"]' ).type("legislation description", { force: true }); + // SAVE & CONTINUE - direct to Project Contacts + cy.contains("Save & Continue").click({ force: true }); + cy.contains("First Name", { timeout: 10000 }); + + cy.get(`[name="contacts[0].first_name"]`).type("Cypress", { force: true }); + cy.get(`[name="contacts[0].last_name"]`).type("Test", { force: true }); + cy.get(`[name="contacts[0].email"]`).type("cypress@mds.com", { force: true }); + cy.get(`[name="contacts[0].phone_number"]`).type("1234567890", { force: true }); + cy.get(`[name="contacts[0].address.address_line_1"]`).type("123 Fake St", { force: true }); + cy.contains("Please select") + .first() + .click({ + force: true, + }); + cy.get('[title="Canada"]').click({ force: true }); + cy.get(`[name="contacts[0].address.city"]`).type("Cityville", { force: true }); + cy.contains("Please select") + .first() + .click({ + force: true, + }); + cy.get('[title="AB"]').click({ force: true }); + cy.get(`[name="contacts[0].address.post_code"]`).type("A0A0A0", { force: true }); + + // SAVE & CONTINUE - Applicant Information + cy.contains("Save & Continue").click({ force: true }); + cy.contains("Applicant Information", { timeout: 10000 }); + + cy.contains("Individual").click({ force: true }); + cy.get(`[name="applicant.first_name"]`).type("Cypress", { force: true }); + cy.get(`[name="applicant.party_name"]`).type("Test", { force: true }); + cy.get(`[name="applicant.phone_no"]`).type("1231231234", { force: true }); + cy.get(`[name="applicant.email"]`).type("email@email.com", { force: true }); + cy.get(`[name="applicant.address[0].address_line_1"]`).type("123 Fake St", { force: true }); + cy.get(`[data-cy="applicant.address[0].address_type_code"]`) + .contains("Please select") + .click({ + force: true, + }); + cy.get('[title="Canada"]').click({ force: true }); + cy.get(`[data-cy="applicant.address[0].sub_division_code"]`) + .contains("Please select") + .click({ + force: true, + }); + cy.get('[title="AB"]').click({ force: true }); + cy.get(`[name="applicant.address[0].post_code"]`).type("A0A0A0", { force: true }); + cy.get(`[name="applicant.address[0].city"]`).type("Cityville", { force: true }); + cy.contains("Same as mailing address") + .first() + .click({ force: true }); + cy.contains("Same as legal address") + .first() + .click({ force: true }); + + // SAVE & CONTINUE - Agent + cy.contains("Save & Continue").click({ force: true }); + cy.contains("Are you an agent applying on behalf of the applicant?", { timeout: 10000 }); + + cy.contains("No").click({ force: true }); + + // SAVE & CONTINUE - Location, Access and Land Use + cy.contains("Save & Continue").click({ force: true }); + cy.scrollTo(0, 0); + cy.get(`[name="is_legal_land_owner"]`, { timeout: 10000 }) + .first() + .scrollIntoView() + .click({ force: true }); // click yes + cy.get(`[name="facility_latitude"]`).type("48", { force: true }); + cy.get(`[name="facility_longitude"]`).type("-114", { force: true }); + + cy.get(`[data-cy="facility_coords_source"]`) + .contains("Please select") + .click({ force: true }); + cy.get('[title="GPS"]').click({ force: true }); + cy.get(`[data-cy="nearest_municipality"]`) + .contains("Please select") + .click({ force: true }); + cy.get('[title="Abbotsford"]').click({ force: true }); + + cy.get(`[name="facility_pid_pin_crown_file_no"]`).type("123", { force: true }); + cy.get(`[name="facility_lease_no"]`).type("456", { force: true }); + + // SAVE & CONTINUE - Mine Components and Offsite Infrastructure + cy.contains("Save & Continue").click({ force: true }); + cy.contains("Facility Type", { timeout: 10000 }); + cy.get(`[name="facility_type"]`).type("facility type", { force: true }); + cy.get(`[name="facility_desc"]`).type("facility description", { force: true }); + + cy.get(`[data-cy="regional_district_id"]`) + .contains("Please select") + .click({ force: true }); + cy.get('[title="Cariboo"]').click({ force: true }); + + cy.get(`[name="facility_operator.address.address_line_1"]`).type("123 Fake St", { + force: true, + }); + cy.get(`[name="facility_operator.address.city"]`).type("Cityville", { force: true }); + + cy.get(`[data-cy="facility_operator.address.sub_division_code"]`) + .contains("Please select") + .click({ + force: true, + }); + cy.get('[title="AB"]').click({ force: true }); + cy.get(`[name="zoning"]`, { timeout: 10000 }) + .first() + .click(); // click yes + + cy.get(`[name="facility_operator.first_name"]`).type("Firstname", { force: true }); + cy.get(`[name="facility_operator.party_name"]`).type("Lastname", { force: true }); + cy.get(`[name="facility_operator.phone_no"]`).type("1231231234", { force: true }); + + // SAVE & CONTINUE - skip to Declaration + cy.contains("Save & Continue").click({ force: true }); + cy.get("#expected_draft_irt_submission_date", { timeout: 10000 }); + cy.contains("Declaration", { timeout: 10000 }).click({ force: true }); + + cy.get("input#ADD_EDIT_PROJECT_SUMMARY_confirmation_of_submission", { timeout: 10000 }).click({ + force: true, + }); + // Submit the project - cy.get('[data-cy="project-summary-submit-button"]').click({ force: true }); + cy.contains("Submit").click({ force: true }); // wait for API to respond before navigating cy.wait(15000); // Navigate back to projects diff --git a/services/core-web/cypress/e2e/majorprojects.cy.ts b/services/core-web/cypress/e2e/majorprojects.cy.ts index 95d1dbba51..57a6c8581b 100644 --- a/services/core-web/cypress/e2e/majorprojects.cy.ts +++ b/services/core-web/cypress/e2e/majorprojects.cy.ts @@ -18,9 +18,9 @@ describe("Major Projects", () => { it("should upload and download a document successfully", () => { const fileName = "dummy.pdf"; - cy.get("#project-summary-submit").then(($button) => { - $button[0].click(); - }); + cy.contains("Document Upload").click(); + + cy.contains("Edit Project Description").click(); cy.fixture(fileName).then((fileContent) => { // Intercept the POST request and stub the response @@ -63,11 +63,13 @@ describe("Major Projects", () => { } ).as("statusRequest"); - cy.get('input[type="file"]').attachFile({ - fileContent: fileContent, - fileName: fileName, - mimeType: "application/pdf", - }); + cy.get('input[type="file"]') + .eq(1) + .attachFile({ + fileContent: fileContent, + fileName: fileName, + mimeType: "application/pdf", + }); // Wait for the upload request to complete(simulated) cy.wait("@uploadRequest").then((interception) => { diff --git a/services/core-web/src/components/Forms/projectSummaries/ProjectSummaryForm.tsx b/services/core-web/src/components/Forms/projectSummaries/ProjectSummaryForm.tsx deleted file mode 100644 index cb8ba551dd..0000000000 --- a/services/core-web/src/components/Forms/projectSummaries/ProjectSummaryForm.tsx +++ /dev/null @@ -1,551 +0,0 @@ -import React, { FC, useState } from "react"; -import { compose } from "redux"; -import { connect, useSelector } from "react-redux"; -import { useHistory, useParams, withRouter } from "react-router-dom"; -import { - FieldArray, - Field, - reduxForm, - formValueSelector, - InjectedFormProps, - change, -} from "redux-form"; -import { Alert, Button, Row, Col, Typography, Popconfirm, Form } from "antd"; -import { DeleteOutlined, PlusOutlined } from "@ant-design/icons"; -import { - maxLength, - phoneNumber, - required, - email, - dateNotBeforeOther, - dateNotAfterOther, -} from "@common/utils/Validate"; -import { - getDropdownProjectSummaryStatusCodes, - getProjectSummaryDocumentTypesHash, -} from "@mds/common/redux/selectors/staticContentSelectors"; -import AuthorizationsInvolved from "@mds/common/components/projectSummary/AuthorizationsInvolved"; -import { getDropdownProjectLeads } from "@mds/common/redux/selectors/partiesSelectors"; -import { getUserAccessData } from "@mds/common/redux/selectors/authenticationSelectors"; -import { Feature, IGroupedDropdownList, IProject, IProjectSummary, USER_ROLES } from "@mds/common"; -import { normalizePhone } from "@common/utils/helpers"; -import * as FORM from "@/constants/forms"; -import * as routes from "@/constants/routes"; -import { renderConfig } from "@/components/common/config"; -import LinkButton from "@/components/common/buttons/LinkButton"; -import { ProjectSummaryDocumentUpload } from "@/components/Forms/projectSummaries/ProjectSummaryDocumentUpload"; -import ArchivedDocumentsSection from "@common/components/documents/ArchivedDocumentsSection"; -import { MajorMineApplicationDocument } from "@mds/common/models/documents/document"; -import ProjectLinks from "@mds/common/components/projects/ProjectLinks"; -import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag"; - -interface ProjectSummaryFormProps { - project: IProject; - initialValues: Partial; - onSubmit: any; - handleSaveData: (message: string) => Promise; - removeDocument: (event, documentGuid: string) => Promise; - archivedDocuments: MajorMineApplicationDocument[]; - onArchivedDocuments: (mineGuid, projectSummaryGuid) => Promise; - isNewProject: boolean; -} - -const unassignedProjectLeadEntry = { - label: "Unassigned", - value: null, -}; - -const contactFields = ({ fields, isNewProject, isEditMode }) => { - return ( - <> - {fields.map((field, index) => { - return ( -
- {index === 0 ? ( -

Primary project contact

- ) : ( - <> - - -

Additional project contact #{index}

- - - {!isNewProject && ( - fields.remove(index)} - okText="Remove" - cancelText="Cancel" - > - - - )} - -
- - )} - - - - - - - - - - - - - - - - - - - - - - - - - {index === 0 &&

Additional project contacts (optional)

} -
- ); - })} - {(isNewProject || isEditMode) && ( - { - fields.push({ is_primary: false }); - }} - title="Add additional project contacts" - > - Add additional project contacts - - )} - - ); -}; - -const ProjectSummaryForm: FC & ProjectSummaryFormProps> = ( - props -) => { - const { isFeatureEnabled } = useFeatureFlag(); - const majorProjectsFeatureEnabled = isFeatureEnabled(Feature.MAJOR_PROJECT_LINK_PROJECTS); - const [isEditMode, setIsEditMode] = useState(false); - const projectLeads: IGroupedDropdownList = useSelector(getDropdownProjectLeads); - const userRoles: string[] = useSelector(getUserAccessData); - const formSelector = formValueSelector(FORM.ADD_EDIT_PROJECT_SUMMARY); - const expected_draft_irt_submission_date = useSelector((state) => - formSelector(state, "expected_draft_irt_submission_date") - ); - const expected_permit_application_date = useSelector((state) => - formSelector(state, "expected_permit_application_date") - ); - const expected_permit_receipt_date = useSelector((state) => - formSelector(state, "expected_permit_receipt_date") - ); - const documents = useSelector((state) => formSelector(state, "documents")); - - const projectSummaryStatusCodes = useSelector(getDropdownProjectSummaryStatusCodes); - const projectSummaryDocumentTypesHash = useSelector(getProjectSummaryDocumentTypesHash); - - const projectLeadData = [unassignedProjectLeadEntry, ...projectLeads[0]?.opt]; - const { mineGuid } = useParams<{ mineGuid: string }>(); - const history = useHistory(); - - const renderProjectDetails = () => { - const { - project: { project_lead_party_guid }, - } = props; - return ( -
- {!props.isNewProject && !project_lead_party_guid && ( - - Please assign a Project Lead to this project via the{" "} - Project contacts section. -

- } - type="warning" - showIcon - /> - )} -
- -
- Project details -
- - {props.initialValues?.status_code && ( - - - value !== "DFT")} - disabled={!isEditMode} - /> - - - )} - - - - - Proponent project tracking ID (optional) -
- - If your company uses a tracking number to identify projects, please provide it - here. - - - } - component={renderConfig.FIELD} - validate={[maxLength(20)]} - disabled={!props.isNewProject && !isEditMode} - /> - - Project Overview -
- - Provide a 2-3 paragraph high-level description of your proposed project. - - - } - component={renderConfig.AUTO_SIZE_FIELD} - minRows={10} - validate={[maxLength(4000), required]} - disabled={!props.isNewProject && !isEditMode} - /> - - {majorProjectsFeatureEnabled && ( - - - routes.PRE_APPLICATIONS.dynamicRoute(p.project_guid, p.project_summary_guid) - } - /> - - )} -
-
- ); - }; - - const renderContacts = () => { - return ( -
- Project contacts -

EMLI contacts

- - - Project Lead

} - component={renderConfig.SELECT} - data={projectLeadData} - disabled={!props.isNewProject && !isEditMode} - /> - -
-

Proponent contacts

- <> - - -
- ); - }; - - const renderProjectDates = () => { - return ( -
- Project dates - -
- - - - - - - - -
- ); - }; - - const renderDocuments = () => { - const canRemoveDocuments = - userRoles.includes(USER_ROLES.role_admin) || - userRoles.includes(USER_ROLES.role_edit_project_summaries); - return ( -
- -
- ); - }; - - const renderArchivedDocuments = () => { - return ; - }; - - const cancelEdit = () => { - props.reset(); - setIsEditMode(false); - }; - - const toggleEditMode = () => { - setIsEditMode(!isEditMode); - }; - - return ( -
{ - const message = props.isNewProject - ? "Successfully submitted a project description to the Province of British Columbia." - : "Successfully updated the project."; - props.handleSaveData(message); - }} - > -
- {!props.isNewProject && !isEditMode && ( - <> - - - )} - {(props.isNewProject || isEditMode) && ( - <> - { - if (props.isNewProject) { - const url = routes.MINE_PRE_APPLICATIONS.dynamicRoute(mineGuid); - history.push(url); - } else if (isEditMode) { - cancelEdit(); - } - }} - okText="Yes" - cancelText="No" - > - - - - - )} -
- {renderProjectDetails()} -
- -
- {renderProjectDates()} -
- {renderContacts()} -
- {renderDocuments()} -
- {renderArchivedDocuments()} -
- {(props.isNewProject || isEditMode) && ( - <> - { - if (props.isNewProject) { - const url = routes.MINE_PRE_APPLICATIONS.dynamicRoute(mineGuid); - history.push(url); - } else if (isEditMode) { - cancelEdit(); - } - }} - okText="Yes" - cancelText="No" - > - - - - - )} -
- - ); -}; - -const mapDispatchToProps = { - change, -}; - -export default (compose( - withRouter, - connect(null, mapDispatchToProps), - reduxForm({ - form: FORM.ADD_EDIT_PROJECT_SUMMARY, - enableReinitialize: true, - touchOnBlur: true, - touchOnChange: false, - }) -)(ProjectSummaryForm) as any) as FC; diff --git a/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js b/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js index 6d80d61a2b..b7e2914e5b 100644 --- a/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js +++ b/services/core-web/src/components/dashboard/majorProjectHomePage/MajorProjectTable.js @@ -86,7 +86,7 @@ export const MajorProjectTable = (props) => { sortField: "project_title", sorter: true, render: (text, record) => ( - + {text} ), @@ -130,7 +130,7 @@ export const MajorProjectTable = (props) => {
- + diff --git a/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js b/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js index d4ebddf927..fd3fd41413 100644 --- a/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js +++ b/services/core-web/src/components/mine/Projects/InformationRequirementsTableTab.js @@ -195,7 +195,7 @@ export class InformationRequirementsTableTab extends Component { - + Back to: {mine_name} Project overview diff --git a/services/core-web/src/components/mine/Projects/MineProjectTable.js b/services/core-web/src/components/mine/Projects/MineProjectTable.js index acba62ba23..9ba845221b 100644 --- a/services/core-web/src/components/mine/Projects/MineProjectTable.js +++ b/services/core-web/src/components/mine/Projects/MineProjectTable.js @@ -91,7 +91,7 @@ export const MineProjectTable = (props) => {
- + diff --git a/services/core-web/src/components/mine/Projects/Project.js b/services/core-web/src/components/mine/Projects/Project.js index 414f7cf411..fefdfa5c70 100644 --- a/services/core-web/src/components/mine/Projects/Project.js +++ b/services/core-web/src/components/mine/Projects/Project.js @@ -89,10 +89,10 @@ export class Project extends Component { project_guid, information_requirements_table: { irt_guid }, } = this.props.project; - let url = routes.PROJECTS.dynamicRoute(project_guid); + let url = routes.EDIT_PROJECT.dynamicRoute(project_guid); switch (activeTab) { case "overview": - url = routes.PROJECTS.dynamicRoute(project_guid); + url = routes.EDIT_PROJECT.dynamicRoute(project_guid); break; case "intro-project-overview": url = routes.INFORMATION_REQUIREMENTS_TABLE.dynamicRoute(project_guid, irt_guid); @@ -107,7 +107,7 @@ export class Project extends Component { url = routes.PROJECT_DECISION_PACKAGE.dynamicRoute(project_guid); break; default: - url = routes.PROJECTS.dynamicRoute(project_guid); + url = routes.EDIT_PROJECT.dynamicRoute(project_guid); } return this.props.history.replace(url); }; diff --git a/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js b/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js index f60d31eb38..e06ec554ce 100644 --- a/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js +++ b/services/core-web/src/components/mine/Projects/ProjectOverviewTab.js @@ -18,7 +18,7 @@ import CustomPropTypes from "@/customPropTypes"; import ProjectStagesTable from "./ProjectStagesTable"; import withFeatureFlag from "@mds/common/providers/featureFlags/withFeatureFlag"; import { Feature } from "@mds/common"; -import ProjectLinks from "@mds/common/components/projects/ProjectLinks"; +import ProjectLinks from "@mds/common/components/projectSummary/ProjectLinks"; const propTypes = { informationRequirementsTableStatusCodesHash: PropTypes.objectOf(PropTypes.string).isRequired, @@ -138,7 +138,7 @@ export class ProjectOverviewTab extends Component { link: ( + + +
+ + +
-
); diff --git a/services/core-web/src/components/navigation/NotificationDrawer.tsx b/services/core-web/src/components/navigation/NotificationDrawer.tsx index 1e28e312ed..be3a891bf1 100644 --- a/services/core-web/src/components/navigation/NotificationDrawer.tsx +++ b/services/core-web/src/components/navigation/NotificationDrawer.tsx @@ -16,8 +16,8 @@ import { INFORMATION_REQUIREMENTS_TABLE, MINE_TAILINGS_DETAILS, NOTICE_OF_DEPARTURE, - PRE_APPLICATIONS, - PROJECTS, + EDIT_PROJECT_SUMMARY, + EDIT_PROJECT, VIEW_MINE_INCIDENT, PROJECT_DOCUMENT_MANAGEMENT, REPORT_VIEW_EDIT, @@ -116,7 +116,7 @@ const NotificationDrawer: FC = (props) => { notification.notification_document.metadata.entity_guid ); case "ProjectSummary": - return PRE_APPLICATIONS.dynamicRoute( + return EDIT_PROJECT_SUMMARY.dynamicRoute( notification.notification_document.metadata.project.project_guid, notification.notification_document.metadata.entity_guid ); @@ -126,7 +126,7 @@ const NotificationDrawer: FC = (props) => { notification.notification_document.metadata.entity_guid ); case "MajorMineApplication": - return PROJECTS.dynamicRoute( + return EDIT_PROJECT.dynamicRoute( notification.notification_document.metadata.project.project_guid, "final-app" ); diff --git a/services/core-web/src/constants/routes.ts b/services/core-web/src/constants/routes.ts index 85816fa811..eb20817267 100644 --- a/services/core-web/src/constants/routes.ts +++ b/services/core-web/src/constants/routes.ts @@ -195,21 +195,27 @@ export const MINE_PRE_APPLICATIONS = { }; export const ADD_PROJECT_SUMMARY = { - route: "/mines/:mineGuid/project-description/new", - dynamicRoute: (mineGuid) => `/mines/${mineGuid}/project-description/new`, + route: "/mines/:mineGuid/project-description/new/:tab", + dynamicRoute: (mineGuid, tab = "basic-information") => + `/mines/${mineGuid}/project-description/new/${tab}`, component: ProjectSummary, }; -export const PRE_APPLICATIONS = { - route: "/pre-applications/:projectGuid/project-description/:projectSummaryGuid", - dynamicRoute: (projectGuid, projectSummaryGuid) => - `/pre-applications/${projectGuid}/project-description/${projectSummaryGuid}`, - hashRoute: (projectGuid, projectSummaryGuid, link) => - `/pre-applications/${projectGuid}/project-description/${projectSummaryGuid}/${link}`, +export const EDIT_PROJECT_SUMMARY = { + route: "/pre-applications/:projectGuid/project-description/:projectSummaryGuid/:mode/:tab", + dynamicRoute: ( + projectGuid, + projectSummaryGuid, + activeTab = "basic-information", + viewMode = true + ) => + `/pre-applications/${projectGuid}/project-description/${projectSummaryGuid}/${ + viewMode ? "view" : "edit" + }/${activeTab}`, component: ProjectSummary, }; -export const PROJECTS = { +export const EDIT_PROJECT = { route: "/pre-applications/:projectGuid/:tab", dynamicRoute: (projectGuid, tab = "overview") => `/pre-applications/${projectGuid}/${tab}`, component: Project, diff --git a/services/core-web/src/routes/MineDashboardRoutes.js b/services/core-web/src/routes/MineDashboardRoutes.js index ed4e880052..ddd5035c21 100644 --- a/services/core-web/src/routes/MineDashboardRoutes.js +++ b/services/core-web/src/routes/MineDashboardRoutes.js @@ -27,15 +27,15 @@ const MineDashboardRoutes = () => ( /> - + ({ }), })); +window.scrollTo = jest.fn(); const location = JSON.stringify(window.location); delete window.location; diff --git a/services/core-web/src/styles/components/SteppedForm.scss b/services/core-web/src/styles/components/SteppedForm.scss index 34d7506fc8..a0fd938756 100644 --- a/services/core-web/src/styles/components/SteppedForm.scss +++ b/services/core-web/src/styles/components/SteppedForm.scss @@ -15,7 +15,7 @@ } .stepped-form>.ant-menu-item-selected { - border-left: 4px solid $gov-blue; + border-left: 4px solid $violet; background-color: #FFFFFF !important; border-right: unset !important; } diff --git a/services/core-web/src/tests/components/Forms/projectSummaries/ProjectSummaryForm.spec.tsx b/services/core-web/src/tests/components/Forms/projectSummaries/ProjectSummaryForm.spec.tsx deleted file mode 100644 index 59414c6448..0000000000 --- a/services/core-web/src/tests/components/Forms/projectSummaries/ProjectSummaryForm.spec.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; -import ProjectSummaryForm from "@/components/Forms/projectSummaries/ProjectSummaryForm"; -import * as MOCK from "@/tests/mocks/dataMocks"; -import { store } from "@/App"; -import { Provider } from "react-redux"; -import { BrowserRouter } from "react-router-dom"; - -const props: any = {}; - -const setupProps = () => { - props.handleSubmit = jest.fn(); - props.submitting = false; - props.formValues = { contacts: [{}] }; - props.initialValues = { - projectSummaryAuthorizationTypes: [], - authorizations: [], - }; - props.projectSummary = { documents: [], contacts: [{}] }; - props.projectSummaryDocumentTypesHash = {}; - props.projectSummaryPermitTypesHash = {}; - props.projectSummaryAuthorizationTypesHash = {}; - props.project = MOCK.PROJECT; - props.projectLeads = [ - { groupName: "Active", opt: [] }, - { groupName: "Inactive", opt: [] }, - ]; - props.projectSummaryStatusCodes = []; - props.userRoles = []; -}; - -beforeEach(() => { - setupProps(); -}); - -describe("ProjectSummaryForm", () => { - it("renders properly", () => { - const { container } = render( - - - - - - ); - expect(container.firstChild).toMatchSnapshot(); - }); -}); diff --git a/services/core-web/src/tests/components/Forms/projectSummaries/__snapshots__/ProjectSummaryForm.spec.tsx.snap b/services/core-web/src/tests/components/Forms/projectSummaries/__snapshots__/ProjectSummaryForm.spec.tsx.snap deleted file mode 100644 index 0ea22be7d6..0000000000 --- a/services/core-web/src/tests/components/Forms/projectSummaries/__snapshots__/ProjectSummaryForm.spec.tsx.snap +++ /dev/null @@ -1,1509 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectSummaryForm renders properly 1`] = ` -
-
- -
-
- -
-
-

- Project details -

-
-
-
-
-
- -
-
-
- -
- - - -
-
-
-
-
-
-
- -
-
-
- -
- - - -
-
-
-
-
-
-
- -
-
-
- - +
+ + Maximum 4000 characters + + + 3982 / 4000 + +
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
`;