From 45bd8ca654d4ff2331734dd0a2a33a77c54ac778 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Thu, 23 May 2024 15:43:58 -0600 Subject: [PATCH 01/13] Add regional selection to FacilityOperator and refactor ProjectSummaryPage Added a new region selector field in the FacilityOperator component for facility's regional location. Refactored ProjectSummaryPage, changing to use react-redux useDispatch and useSelector hooks instead of connect method and got rid of unused imports. Made the code more readable and manageable. --- .../projectSummary/FacilityOperator.tsx | 43 +++- services/common/src/constants/API.ts | 3 + .../pages/Project/ProjectSummaryPage.tsx | 187 +++++++----------- 3 files changed, 119 insertions(+), 114 deletions(-) diff --git a/services/common/src/components/projectSummary/FacilityOperator.tsx b/services/common/src/components/projectSummary/FacilityOperator.tsx index 4fca5574fb..2c03b68209 100644 --- a/services/common/src/components/projectSummary/FacilityOperator.tsx +++ b/services/common/src/components/projectSummary/FacilityOperator.tsx @@ -17,10 +17,39 @@ import { getDropdownProvinceOptions } from "@mds/common/redux/selectors/staticCo import RenderRadioButtons from "../forms/RenderRadioButtons"; import RenderAutoSizeField from "../forms/RenderAutoSizeField"; import { normalizePhone } from "@mds/common/redux/utils/helpers"; +import { getRegionOptions } from "@mds/common/redux/slices/regionsSlice"; export const FacilityOperator: FC = () => { const formValues = useSelector(getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY)); - const { zoning } = formValues; + + const formErrors = useSelector(getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY)); + const { + facility_coords_source, + zoning, + facility_latitude, + facility_longitude, + legal_land_desc, + facility_pid_pin_crown_file_no, + } = formValues; + const [pin, setPin] = useState>([]); + + const regionOptions = useSelector(getRegionOptions); + + useEffect(() => { + // don't jump around the map while coords being entered and not yet valid + const invalidPin = Boolean(formErrors.facility_longitude || formErrors.facility_latitude); + if (!invalidPin) { + const latLng = [facility_latitude, facility_longitude]; + setPin(latLng); + } + }, [facility_longitude, facility_latitude]); + + const dataSourceOptions = [ + { value: "GPS", label: "GPS" }, + { value: "SUR", label: "Survey" }, + { value: "GGE", label: "Google Earth" }, + { value: "OTH", label: "Other" }, + ]; const address_type_code = "CAN"; @@ -49,6 +78,18 @@ export const FacilityOperator: FC = () => { rows={3} component={RenderAutoSizeField} /> + + + + + Facility Address diff --git a/services/common/src/constants/API.ts b/services/common/src/constants/API.ts index 4e4ca61943..a3009b5df0 100644 --- a/services/common/src/constants/API.ts +++ b/services/common/src/constants/API.ts @@ -363,3 +363,6 @@ export const DAM = (damGuid) => (damGuid ? `/dams/${damGuid}` : "/dams"); export const DOCUMENTS_COMPRESSION = (mineGuid) => `/mines/${mineGuid}/documents/zip`; export const POLL_DOCUMENTS_COMPRESSION_PROGRESS = (taskId) => `/mines/documents/zip/${taskId}`; + +// Regions +export const REGIONS_LIST = "/regions"; diff --git a/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx b/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx index 88d197c1d1..c2a59f06a2 100644 --- a/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx +++ b/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx @@ -1,76 +1,57 @@ import React, { FC, useEffect, useState } from "react"; -import { connect } from "react-redux"; -import { bindActionCreators } from "redux"; +import { useDispatch, useSelector } from "react-redux"; import { flattenObject } from "@common/utils/helpers"; import { Link, Prompt, useHistory, useLocation, useParams } from "react-router-dom"; -import { - submit, - formValueSelector, - getFormSyncErrors, - getFormValues, - reset, - touch, - change, -} from "redux-form"; -import { Row, Col, Typography, Divider } from "antd"; +import { getFormSyncErrors, getFormValues, reset, submit, touch } from "redux-form"; +import { Col, Divider, Row, Typography } from "antd"; import ArrowLeftOutlined from "@ant-design/icons/ArrowLeftOutlined"; import { getMines } from "@mds/common/redux/selectors/mineSelectors"; import { - getProjectSummary, getFormattedProjectSummary, getProject, + getProjectSummary, } from "@mds/common/redux/selectors/projectSelectors"; import { - getProjectSummaryDocumentTypesHash, getProjectSummaryAuthorizationTypesArray, + getProjectSummaryDocumentTypesHash, } from "@mds/common/redux/selectors/staticContentSelectors"; import { createProjectSummary, - updateProjectSummary, fetchProjectById, updateProject, + updateProjectSummary, } from "@mds/common/redux/actionCreators/projectActionCreator"; import { fetchMineRecordById } from "@mds/common/redux/actionCreators/mineActionCreator"; import { clearProjectSummary } from "@mds/common/redux/actions/projectActions"; import * as FORM from "@/constants/forms"; import Loading from "@/components/common/Loading"; import { - EDIT_PROJECT_SUMMARY, - MINE_DASHBOARD, ADD_PROJECT_SUMMARY, EDIT_PROJECT, + EDIT_PROJECT_SUMMARY, + MINE_DASHBOARD, } from "@/constants/routes"; import ProjectSummaryForm, { getProjectFormTabs, } from "@/components/Forms/projects/projectSummary/ProjectSummaryForm"; -import { IMine, IProjectSummary, IProject, Feature, removeNullValuesRecursive } from "@mds/common"; -import { ActionCreator } from "@mds/common/interfaces/actionCreator"; +import { Feature, IMine, IProject, IProjectSummary, removeNullValuesRecursive } from "@mds/common"; import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag"; import { isArray } from "lodash"; +import { fetchRegions } from "@mds/common/redux/slices/regionsSlice"; interface ProjectSummaryPageProps { mines: Partial[]; projectSummary: Partial; project: Partial; - fetchProjectById: ActionCreator; - createProjectSummary: ActionCreator; - updateProjectSummary: ActionCreator; - fetchMineRecordById: ActionCreator; - updateProject: ActionCreator; - clearProjectSummary: () => any; projectSummaryDocumentTypesHash: Record; - submit: (arg1?: string) => any; formValueSelector: (arg1: string, arg2?: any) => any; getFormSyncErrors: (arg1: string) => any; - reset: (arg1: string) => any; - touch: (arg1?: string, arg2?: any) => any; formErrors: Record; formValues: any; projectSummaryAuthorizationTypesArray: any[]; anyTouched: boolean; formattedProjectSummary: any; location: Record; - change: any; } interface IParams { @@ -80,29 +61,22 @@ interface IParams { tab?: any; } -export const ProjectSummaryPage: FC = (props) => { - const { - mines, - formattedProjectSummary, - project, - projectSummary, - projectSummaryAuthorizationTypesArray, - projectSummaryDocumentTypesHash, - formValues, - formErrors, - submit, - touch, - anyTouched, - reset, - fetchProjectById, - fetchMineRecordById, - clearProjectSummary, - createProjectSummary, - updateProjectSummary, - updateProject, - change, - } = props; +export const ProjectSummaryPage: FC = () => { + const anyTouched = useSelector( + (state) => state.form[FORM.ADD_EDIT_PROJECT_SUMMARY]?.anyTouched || false + ); + const mines = useSelector(getMines); + const projectSummary = useSelector(getProjectSummary); + const formattedProjectSummary = useSelector(getFormattedProjectSummary); + const project = useSelector(getProject); + const projectSummaryDocumentTypesHash = useSelector(getProjectSummaryDocumentTypesHash); + const projectSummaryAuthorizationTypesArray = useSelector( + getProjectSummaryAuthorizationTypesArray + ); + const formErrors = useSelector(getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY)); + const formValues = useSelector(getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY)); + const dispatch = useDispatch(); const { isFeatureEnabled } = useFeatureFlag(); const amsFeatureEnabled = isFeatureEnabled(Feature.AMS_AGENT); const { mineGuid, projectGuid, projectSummaryGuid, tab } = useParams(); @@ -116,9 +90,10 @@ export const ProjectSummaryPage: FC = (props) => { const handleFetchData = () => { if (projectGuid && projectSummaryGuid) { setIsEditMode(true); - return fetchProjectById(projectGuid); + dispatch(fetchRegions(undefined)); + return dispatch(fetchProjectById(projectGuid)); } - return fetchMineRecordById(mineGuid); + return dispatch(fetchMineRecordById(mineGuid)); }; useEffect(() => { @@ -126,7 +101,7 @@ export const ProjectSummaryPage: FC = (props) => { handleFetchData().then(() => setIsLoaded(true)); } return () => { - clearProjectSummary(); + dispatch(clearProjectSummary()); }; }, []); @@ -152,12 +127,18 @@ export const ProjectSummaryPage: FC = (props) => { } else { newAmsAuthorizations = newAmsAuthorizations.concat( authsOfType?.NEW.map((a) => - transformAuthorization(type, { ...a, project_summary_permit_type: ["NEW"] }) + transformAuthorization(type, { + ...a, + project_summary_permit_type: ["NEW"], + }) ) ); amendAmsAuthorizations = amendAmsAuthorizations.concat( authsOfType?.AMENDMENT.map((a) => - transformAuthorization(type, { ...a, project_summary_permit_type: ["AMENDMENT"] }) + transformAuthorization(type, { + ...a, + project_summary_permit_type: ["AMENDMENT"], + }) ) ); } @@ -185,20 +166,27 @@ export const ProjectSummaryPage: FC = (props) => { const handleUpdateProjectSummary = async (values, message) => { const payload = handleTransformPayload(values); setIsLoaded(false); - return updateProjectSummary( - { - projectGuid, - projectSummaryGuid, - }, - payload, - message + return dispatch( + updateProjectSummary( + { + projectGuid, + projectSummaryGuid, + }, + payload, + message + ) ) .then(async () => { - await updateProject( - { projectGuid }, - { mrc_review_required: payload.mrc_review_required, contacts: payload.contacts }, - "Successfully updated project.", - false + await dispatch( + updateProject( + { projectGuid }, + { + mrc_review_required: payload.mrc_review_required, + contacts: payload.contacts, + }, + "Successfully updated project.", + false + ) ); }) .then(async () => { @@ -210,12 +198,14 @@ export const ProjectSummaryPage: FC = (props) => { }; const handleCreateProjectSummary = async (values, message) => { - return createProjectSummary( - { - mineGuid: mineGuid, - }, - handleTransformPayload(values), - message + return dispatch( + createProjectSummary( + { + mineGuid: mineGuid, + }, + handleTransformPayload(values), + message + ) ).then(({ data: { project_guid, project_summary_guid } }) => { history.replace( EDIT_PROJECT_SUMMARY.dynamicRoute(project_guid, project_summary_guid, projectFormTabs[1]) @@ -248,8 +238,8 @@ export const ProjectSummaryPage: FC = (props) => { const errors = Object.keys(flattenObject(formErrors)); const values = { ...formValues, status_code: status_code }; - submit(FORM.ADD_EDIT_PROJECT_SUMMARY); - touch(FORM.ADD_EDIT_PROJECT_SUMMARY); + dispatch(submit(FORM.ADD_EDIT_PROJECT_SUMMARY)); + dispatch(touch(FORM.ADD_EDIT_PROJECT_SUMMARY)); if (errors.length === 0) { try { if (!isEditMode) { @@ -272,8 +262,8 @@ export const ProjectSummaryPage: FC = (props) => { const message = "Successfully saved a draft project description."; const values = { ...formValues, status_code: "DFT" }; - submit(FORM.ADD_EDIT_PROJECT_SUMMARY); - touch(FORM.ADD_EDIT_PROJECT_SUMMARY); + dispatch(submit(FORM.ADD_EDIT_PROJECT_SUMMARY)); + dispatch(touch(FORM.ADD_EDIT_PROJECT_SUMMARY)); const errors = Object.keys(flattenObject(formErrors)); if (errors.length === 0) { try { @@ -299,7 +289,10 @@ export const ProjectSummaryPage: FC = (props) => { : `New project description for ${mineName}`; const initialValues = isEditMode - ? { ...formattedProjectSummary, mrc_review_required: project.mrc_review_required } + ? { + ...formattedProjectSummary, + mrc_review_required: project.mrc_review_required, + } : {}; return ( @@ -309,7 +302,7 @@ export const ProjectSummaryPage: FC = (props) => { when={anyTouched} message={(newLocation, action) => { if (action === "REPLACE") { - reset(FORM.ADD_EDIT_PROJECT_SUMMARY); + dispatch(reset(FORM.ADD_EDIT_PROJECT_SUMMARY)); } return location.pathname !== newLocation.pathname && !newLocation.pathname.includes("project-description") && @@ -354,36 +347,4 @@ export const ProjectSummaryPage: FC = (props) => { ); }; -const selector = formValueSelector(FORM.ADD_EDIT_PROJECT_SUMMARY); -const mapStateToProps = (state) => ({ - anyTouched: state.form[FORM.ADD_EDIT_PROJECT_SUMMARY]?.anyTouched || false, - fieldsTouched: state.form[FORM.ADD_EDIT_PROJECT_SUMMARY]?.fields || {}, - mines: getMines(state), - projectSummary: getProjectSummary(state), - formattedProjectSummary: getFormattedProjectSummary(state), - project: getProject(state), - projectSummaryDocumentTypesHash: getProjectSummaryDocumentTypesHash(state), - projectSummaryAuthorizationTypesArray: getProjectSummaryAuthorizationTypesArray(state), - formErrors: getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY)(state), - formValues: getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY)(state), - contacts: selector(state, "contacts"), -}); - -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - createProjectSummary, - updateProjectSummary, - fetchMineRecordById, - clearProjectSummary, - fetchProjectById, - updateProject, - submit, - reset, - touch, - change, - }, - dispatch - ); - -export default connect(mapStateToProps, mapDispatchToProps)(ProjectSummaryPage); +export default ProjectSummaryPage; From 985e5885c7db8be0dea1b6acc1865003aded9d42 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Thu, 23 May 2024 15:46:16 -0600 Subject: [PATCH 02/13] Add regions reducer and regionsSlice This update introduces a new 'regionsSlice' along with 'regions' Reducer to manage the state of region related data in the Redux store. Regions state includes an array of regions, each with properties of name and regional_district_id. Additionally, the regions reducer is added to the rootReducerShared file. --- .../src/redux/reducers/rootReducerShared.ts | 2 + .../common/src/redux/slices/regionsSlice.ts | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 services/common/src/redux/slices/regionsSlice.ts diff --git a/services/common/src/redux/reducers/rootReducerShared.ts b/services/common/src/redux/reducers/rootReducerShared.ts index 74d92e7029..75a1e16604 100644 --- a/services/common/src/redux/reducers/rootReducerShared.ts +++ b/services/common/src/redux/reducers/rootReducerShared.ts @@ -33,6 +33,7 @@ import { } from "../reducers"; import reportSubmissionReducer from "@mds/common/components/reports/reportSubmissionSlice"; import verifiableCredentialsReducer from "@mds/common/redux/slices/verifiableCredentialsSlice"; +import regionsReducer from "@mds/common/redux/slices/regionsSlice"; import complianceCodeReducer, { complianceCodeReducerType } from "../slices/complianceCodesSlice"; export const sharedReducer = { ...activityReducer, @@ -77,5 +78,6 @@ export const sharedReducer = { loadingBar: loadingBarReducer, reportSubmission: reportSubmissionReducer, verifiableCredentials: verifiableCredentialsReducer, + regions: regionsReducer, [complianceCodeReducerType]: complianceCodeReducer, }; diff --git a/services/common/src/redux/slices/regionsSlice.ts b/services/common/src/redux/slices/regionsSlice.ts new file mode 100644 index 0000000000..ff3c25d393 --- /dev/null +++ b/services/common/src/redux/slices/regionsSlice.ts @@ -0,0 +1,70 @@ +import { createAppSlice } from "@mds/common/redux/createAppSlice"; +import { hideLoading, showLoading } from "react-redux-loading-bar"; +import CustomAxios from "@mds/common/redux/customAxios"; +import { ENVIRONMENT, REGIONS_LIST } from "@mds/common/constants"; +import { RootState } from "@mds/common/redux/rootState"; + +const createRequestHeader = REQUEST_HEADER.createRequestHeader; + +const rejectHandler = (action) => { + console.log(action.error); + console.log(action.error.stack); +}; + +interface Region { + name: string; + regional_district_id: number; +} + +interface RegionsState { + regions: Region[]; +} + +const initialState: RegionsState = { + regions: [], +}; + +const regionsSlice = createAppSlice({ + name: "regionsSlice", + initialState, + reducers: (create) => ({ + fetchRegions: create.asyncThunk( + async (_, thunkAPI) => { + const headers = createRequestHeader(); + thunkAPI.dispatch(showLoading()); + + const response = await CustomAxios({ + errorToastMessage: "default", + }).get(`${ENVIRONMENT.apiUrl}${REGIONS_LIST}`, headers); + + thunkAPI.dispatch(hideLoading()); + + return response.data; + }, + { + fulfilled: (state, action) => { + state.regions = action.payload; + }, + rejected: (state: RegionsState, action) => { + rejectHandler(action); + }, + } + ), + }), + selectors: { + getRegionOptions: (state: RegionsState) => { + return state.regions.map((region) => ({ + label: region.name, + value: region.regional_district_id, + })); + }, + }, +}); + +export const { fetchRegions } = regionsSlice.actions; +export const { getRegionOptions } = regionsSlice.getSelectors( + (rootState: RootState) => rootState.regions +); + +const regionsReducer = regionsSlice.reducer; +export default regionsReducer; From ec27ed7be27ad86761adce0f414c58b4f1733ae4 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Thu, 23 May 2024 15:46:55 -0600 Subject: [PATCH 03/13] Add region to project summary and create regions table A new 'regions' table is created with a primary key 'regional_district_id' and other requisite fields such as 'name', 'create_user' etc. The 'project_summary' table is updated with a new field 'regional_district_id' and a foreign key constraint is established to link it with the 'regions' table. --- ...2024.05.21.13.00__create_regions_table.sql | 41 +++++++++++++++++++ ...2.14.30__add_region_to_project_summary.sql | 7 ++++ 2 files changed, 48 insertions(+) create mode 100644 migrations/sql/V2024.05.21.13.00__create_regions_table.sql create mode 100644 migrations/sql/V2024.05.22.14.30__add_region_to_project_summary.sql diff --git a/migrations/sql/V2024.05.21.13.00__create_regions_table.sql b/migrations/sql/V2024.05.21.13.00__create_regions_table.sql new file mode 100644 index 0000000000..fd4b89df2e --- /dev/null +++ b/migrations/sql/V2024.05.21.13.00__create_regions_table.sql @@ -0,0 +1,41 @@ +CREATE TABLE IF NOT EXISTS regions ( + regional_district_id INT NOT NULL PRIMARY KEY, + name varchar(255) NOT NULL, + create_user character varying(60) NOT NULL, + create_timestamp timestamp with time zone DEFAULT now() NOT NULL, + update_user character varying(60) NOT NULL, + update_timestamp timestamp with time zone DEFAULT now() NOT NULL +); + +INSERT INTO regions (regional_district_id, name, create_user, update_user) +VALUES (4786586, 'Alberni-Clayoquot', 'system-mds', 'system-mds'), + (4786587, 'Bulkley-Nechako', 'system-mds', 'system-mds'), + (4786588, 'Capital', 'system-mds', 'system-mds'), + (4786590, 'Cariboo', 'system-mds', 'system-mds'), + (4786592, 'Central Coast', 'system-mds', 'system-mds'), + (4786597, 'Central Kootenay', 'system-mds', 'system-mds'), + (4786600, 'Central Okanagan', 'system-mds', 'system-mds'), + (4786601, 'Columbia Shuswap', 'system-mds', 'system-mds'), + (4786602, 'Comox Valley', 'system-mds', 'system-mds'), + (51541265, 'Comox-Strathcona (Island)', 'system-mds', 'system-mds'), + (51541257, 'Comox-Strathcona (Mainland)', 'system-mds', 'system-mds'), + (4786603, 'Cowichan Valley', 'system-mds', 'system-mds'), + (4786604, 'East Kootenay', 'system-mds', 'system-mds'), + (4786605, 'Fraser Valley', 'system-mds', 'system-mds'), + (4786636, 'Fraser-Fort George', 'system-mds', 'system-mds'), + (4786650, 'Kitimat-Stikine', 'system-mds', 'system-mds'), + (4786651, 'Kootenay Boundary', 'system-mds', 'system-mds'), + (4786671, 'Metro Vancouver', 'system-mds', 'system-mds'), + (4786672, 'Mount Waddington', 'system-mds', 'system-mds'), + (4786678, 'Nanaimo', 'system-mds', 'system-mds'), + (4786699, 'North Coast', 'system-mds', 'system-mds'), + (4786683, 'North Okanagan', 'system-mds', 'system-mds'), + (4786684, 'Northern Rockies', 'system-mds', 'system-mds'), + (4786686, 'Okanagan-Similkameen', 'system-mds', 'system-mds'), + (4786687, 'Peace River', 'system-mds', 'system-mds'), + (4786690, 'qathet', 'system-mds', 'system-mds'), + (4786712, 'Squamish-Lillooet', 'system-mds', 'system-mds'), + (7043686, 'Stikine', 'system-mds', 'system-mds'), + (4786713, 'Strathcona', 'system-mds', 'system-mds'), + (4786714, 'Sunshine Coast', 'system-mds', 'system-mds'), + (4786722, 'Thompson-Nicola', 'system-mds', 'system-mds'); \ No newline at end of file diff --git a/migrations/sql/V2024.05.22.14.30__add_region_to_project_summary.sql b/migrations/sql/V2024.05.22.14.30__add_region_to_project_summary.sql new file mode 100644 index 0000000000..aed6725fbf --- /dev/null +++ b/migrations/sql/V2024.05.22.14.30__add_region_to_project_summary.sql @@ -0,0 +1,7 @@ +ALTER TABLE project_summary + ADD regional_district_id integer; + +ALTER TABLE project_summary + ADD CONSTRAINT fk_regional_district_id + FOREIGN KEY (regional_district_id) + REFERENCES regions(regional_district_id); \ No newline at end of file From 25a338f38d6454d90a039ef9821d304ec705ee31 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Thu, 23 May 2024 16:01:34 -0600 Subject: [PATCH 04/13] Add region list functionality to API Three new files were created in the regions directory within the API to implement region listing functionality. This includes model, resource, and namespace files to support API retrieval of all region data from the database. Access controls were added for view_all and minespace_proponent roles. --- .../app/api/regions/models/__init__.py | 0 .../app/api/regions/models/regions.py | 17 +++++++++++++++ .../core-api/app/api/regions/namespace.py | 7 +++++++ .../app/api/regions/resources/__init__.py | 0 .../regions/resources/region_list_resource.py | 21 +++++++++++++++++++ 5 files changed, 45 insertions(+) create mode 100644 services/core-api/app/api/regions/models/__init__.py create mode 100644 services/core-api/app/api/regions/models/regions.py create mode 100644 services/core-api/app/api/regions/namespace.py create mode 100644 services/core-api/app/api/regions/resources/__init__.py create mode 100644 services/core-api/app/api/regions/resources/region_list_resource.py diff --git a/services/core-api/app/api/regions/models/__init__.py b/services/core-api/app/api/regions/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/core-api/app/api/regions/models/regions.py b/services/core-api/app/api/regions/models/regions.py new file mode 100644 index 0000000000..d44e0c8a31 --- /dev/null +++ b/services/core-api/app/api/regions/models/regions.py @@ -0,0 +1,17 @@ +from app.extensions import db +from app.api.utils.models_mixins import AuditMixin, Base + + +class Regions(AuditMixin, Base): + __tablename__ = "regions" + + regional_district_id = db.Column(db.Integer, nullable=False, primary_key=True) + name = db.Column(db.String, nullable=False) + + @classmethod + def get_all(cls): + return cls.query.all() + + @classmethod + def find_by_id(cls, regional_district_id): + return cls.query.filter_by(regional_district_id=regional_district_id).first() diff --git a/services/core-api/app/api/regions/namespace.py b/services/core-api/app/api/regions/namespace.py new file mode 100644 index 0000000000..4b6bb79370 --- /dev/null +++ b/services/core-api/app/api/regions/namespace.py @@ -0,0 +1,7 @@ +from flask_restx import Namespace + +from app.api.regions.resources.region_list_resource import RegionListResource + +api = Namespace('regions', description='Region options') + +api.add_resource(RegionListResource, '') diff --git a/services/core-api/app/api/regions/resources/__init__.py b/services/core-api/app/api/regions/resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/core-api/app/api/regions/resources/region_list_resource.py b/services/core-api/app/api/regions/resources/region_list_resource.py new file mode 100644 index 0000000000..3aa83113c0 --- /dev/null +++ b/services/core-api/app/api/regions/resources/region_list_resource.py @@ -0,0 +1,21 @@ +from flask_restx import Resource +from werkzeug.exceptions import NotFound + +from app.api.regions.models.regions import Regions +from app.api.regions.response_models import REGION +from app.api.utils.access_decorators import requires_any_of, VIEW_ALL, MINESPACE_PROPONENT +from app.api.utils.resources_mixins import UserMixin +from app.extensions import api + + +class RegionListResource(Resource, UserMixin): + @api.doc( + description="List all regions", + ) + @api.marshal_with(REGION, code=200, as_list=True) + @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) + def get(self): + regions = Regions.get_all() + if not regions: + raise NotFound('Region not found') + return regions From 9cf27798c0b47b8585703c528c74c8721ec4fed0 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Thu, 23 May 2024 16:05:19 -0600 Subject: [PATCH 05/13] Add regional district ID to project summary The project summary model, input handling, and responses have been updated to include a regional_district_id value. This new attribute can be assigned to a project summary using its respective foreign key from the regions dataset. This newly introduced attribute will aid in the routing and categorizing of projects based on regional districts. --- services/core-api/app/__init__.py | 2 ++ .../project_summary/models/project_summary.py | 12 +++++++++- .../resources/project_summary.py | 24 ++++++++++--------- .../app/api/projects/response_models.py | 3 ++- .../app/api/regions/response_models.py | 9 +++++++ 5 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 services/core-api/app/api/regions/response_models.py diff --git a/services/core-api/app/__init__.py b/services/core-api/app/__init__.py index 8cc1942372..6bcac5ad81 100644 --- a/services/core-api/app/__init__.py +++ b/services/core-api/app/__init__.py @@ -41,6 +41,7 @@ from app.api.dams.namespace import api as dams_api from app.api.verifiable_credentials.namespace import api as verifiable_credential_api from app.api.report_error.namespace import api as report_error_api +from app.api.regions.namespace import api as regions_api from app.commands import register_commands from app.config import Config @@ -196,6 +197,7 @@ def register_routes(app): root_api_namespace.add_namespace(dams_api) root_api_namespace.add_namespace(verifiable_credential_api) root_api_namespace.add_namespace(report_error_api) + root_api_namespace.add_namespace(regions_api) @root_api_namespace.route('/version/') class VersionCheck(Resource): 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 55631c8c04..da3c63748b 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 @@ -7,6 +7,7 @@ from werkzeug.exceptions import BadRequest from app.api.parties.party import PartyOrgBookEntity +from app.api.regions.models.regions import Regions from app.api.services.ams_api_service import AMSApiService from app.extensions import db @@ -68,6 +69,7 @@ class ProjectSummary(SoftDeleteMixin, AuditMixin, Base): zoning = db.Column(db.Boolean, nullable=True) zoning_reason = db.Column(db.String, nullable=True) nearest_municipality_guid = db.Column(UUID(as_uuid=True), db.ForeignKey('municipality.municipality_guid')) + regional_district_id = db.Column(db.Integer(), db.ForeignKey('regions.regional_district_id'), nullable=True) company_alias = db.Column(db.String(200), nullable=True) is_legal_address_same_as_mailing_address = db.Column(db.Boolean, nullable=True) @@ -984,6 +986,7 @@ def update(self, is_billing_address_same_as_legal_address=None, contacts=None, company_alias=None, + regional_district_id=None, add_to_session=True): # Update simple properties. @@ -1049,6 +1052,7 @@ def update(self, self.facility_lease_no = facility_lease_no self.zoning = zoning self.zoning_reason = zoning_reason + self.regional_district_id = regional_district_id if facility_operator and facility_type: if not facility_operator['party_type_code']: @@ -1105,6 +1109,11 @@ def update(self, for authorization in authorizations: self.create_or_update_authorization(authorization) + regional_district_name = regional_district_id is not None and Regions.find_by_id( + regional_district_id).name or None + + current_app.logger.info(regional_district_name) + if ams_authorizations: ams_results = [] if self.status_code == 'SUB': @@ -1132,7 +1141,8 @@ def update(self, facility_pid_pin_crown_file_no, company_alias, zoning, - zoning_reason) + zoning_reason, + regional_district_name) for authorization in ams_authorizations.get('amendments', []): self.create_or_update_authorization(authorization) 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 9b9e48fb3a..6c275a85fd 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 @@ -94,10 +94,10 @@ class ProjectSummaryResource(Resource, UserMixin): required=False, ) parser.add_argument( - 'agent', - type=dict, - location='json', - store_missing=False, + 'agent', + type=dict, + location='json', + store_missing=False, required=False ) parser.add_argument('is_agent', type=bool, help="True if an agent is applying on behalf of the Applicant", location='json', store_missing=False, required=False) @@ -130,10 +130,10 @@ class ProjectSummaryResource(Resource, UserMixin): required=False, ) parser.add_argument( - 'facility_operator', - type=dict, - location='json', - store_missing=False, + 'facility_operator', + type=dict, + location='json', + store_missing=False, required=False ) parser.add_argument('facility_type', type=str, store_missing=False, required=False) @@ -141,7 +141,7 @@ class ProjectSummaryResource(Resource, UserMixin): parser.add_argument('facility_latitude', type=lambda x: Decimal(x) if x else None, store_missing=False, required=False) parser.add_argument('facility_longitude', type=lambda x: Decimal(x) if x else None, store_missing=False, required=False) - + parser.add_argument('facility_coords_source', type=str, store_missing=False, required=False) parser.add_argument('facility_coords_source_desc', type=str, store_missing=False, required=False) parser.add_argument('facility_pid_pin_crown_file_no', type=str, store_missing=False, required=False) @@ -150,6 +150,7 @@ class ProjectSummaryResource(Resource, UserMixin): parser.add_argument('zoning', type=bool, store_missing=False, required=False) parser.add_argument('zoning_reason', type=str, store_missing=False, required=False) parser.add_argument('nearest_municipality', type=str, store_missing=False, required=False) + parser.add_argument('regional_district_id', type=int, store_missing=False, required=False) parser.add_argument( 'applicant', @@ -195,7 +196,7 @@ def put(self, project_guid, project_summary_guid): is_minespace_user()) project = Project.find_by_project_guid(project_guid) data = self.parser.parse_args() - + project_summary_validation = project_summary.validate_project_summary(data) if any(project_summary_validation[i] != [] for i in project_summary_validation): current_app.logger.error(f'Project Summary schema validation failed with errors: {project_summary_validation}') @@ -240,7 +241,8 @@ def put(self, project_guid, project_summary_guid): data.get('is_billing_address_same_as_mailing_address'), data.get('is_billing_address_same_as_legal_address'), data.get('contacts'), - data.get('company_alias')) + data.get('company_alias'), + data.get('regional_district_id')) project_summary.save() if prev_status == 'DFT' and project_summary.status_code == 'SUB': diff --git a/services/core-api/app/api/projects/response_models.py b/services/core-api/app/api/projects/response_models.py index dd8de16a1a..9abd8276f6 100644 --- a/services/core-api/app/api/projects/response_models.py +++ b/services/core-api/app/api/projects/response_models.py @@ -202,7 +202,8 @@ def format(self, value): 'is_billing_address_same_as_mailing_address': fields.Boolean, 'is_billing_address_same_as_legal_address': fields.Boolean, 'applicant': fields.Nested(PARTY), - 'municipality': fields.Nested(MUNICIPALITY_MODEL) + 'municipality': fields.Nested(MUNICIPALITY_MODEL), + 'regional_district_id': fields.Integer, }) REQUIREMENTS_MODEL = api.model( diff --git a/services/core-api/app/api/regions/response_models.py b/services/core-api/app/api/regions/response_models.py new file mode 100644 index 0000000000..e0d5f39451 --- /dev/null +++ b/services/core-api/app/api/regions/response_models.py @@ -0,0 +1,9 @@ +from flask_restx import fields + +from app.extensions import api + +REGION = api.model( + 'Region', { + 'name': fields.String, + 'regional_district_id': fields.Integer, + }) From 0242143214cf5ed2714aabf800a76bfe714a41b9 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Thu, 23 May 2024 16:06:00 -0600 Subject: [PATCH 06/13] Add regional district name to AMS API service The AMS API service has been updated to include the regional district name in the authorization application. Both the model and the payload for the HTTP request have been updated to accommodate this new attribute. --- services/core-api/app/api/services/ams_api_service.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/core-api/app/api/services/ams_api_service.py b/services/core-api/app/api/services/ams_api_service.py index a584ecbff3..b774047ef5 100644 --- a/services/core-api/app/api/services/ams_api_service.py +++ b/services/core-api/app/api/services/ams_api_service.py @@ -84,7 +84,8 @@ def create_new_ams_authorization(cls, facility_pid_pin_crown_file_no, company_alias, zoning, - zoning_reason + zoning_reason, + regional_district_name ): """Creates a new AMS authorization application""" @@ -203,7 +204,10 @@ def create_new_ams_authorization(cls, 'facilityoperator': facility_operator.get('name', ''), 'facilityoperatorphonenumber': cls.__format_phone_number(facility_operator.get('phone_no', '')), 'facilityoperatoremail': facility_operator.get('email', ''), - 'facilityoperatortitle': facility_operator.get('job_title', '') + 'facilityoperatortitle': facility_operator.get('job_title', ''), + 'regionaldistrict': { + 'name': regional_district_name + } } payload = json.dumps(ams_authorization_data) response = requests.post(Config.AMS_URL, data=payload, headers=headers) From d7debb51de9649cb72263e2ba11c01274d07c1d7 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 12:55:15 -0600 Subject: [PATCH 07/13] Updated ProjectSummaryPage.tsx form and subsequent tests --- .../projectSummary/BasicInformation.spec.tsx | 2 +- services/common/src/tests/mocks/dataMocks.tsx | 162 +++++++----------- .../projectSummary/ProjectSummaryForm.tsx | 120 ++++++------- .../pages/Project/ProjectSummaryPage.tsx | 29 ++-- .../ProjectSummaryPage.spec.tsx | 67 +++++--- .../ProjectSummaryPage.spec.tsx.snap | 60 ++++++- .../src/tests/mocks/dataMocks.js | 28 ++- 7 files changed, 246 insertions(+), 222 deletions(-) diff --git a/services/common/src/components/projectSummary/BasicInformation.spec.tsx b/services/common/src/components/projectSummary/BasicInformation.spec.tsx index f68c94fb40..3c4e4f5696 100644 --- a/services/common/src/components/projectSummary/BasicInformation.spec.tsx +++ b/services/common/src/components/projectSummary/BasicInformation.spec.tsx @@ -1,7 +1,7 @@ import React from "react"; import { render } from "@testing-library/react"; import FormWrapper from "@mds/common/components/forms/FormWrapper"; -import { FORM } from "@mds/common"; +import { FORM } from "@mds/common/constants"; import { PROJECTS } from "@mds/common/constants/reducerTypes"; import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper"; import { PROJECT } from "@mds/common/tests/mocks/dataMocks"; diff --git a/services/common/src/tests/mocks/dataMocks.tsx b/services/common/src/tests/mocks/dataMocks.tsx index 6d9396aeaa..ebc327e16a 100644 --- a/services/common/src/tests/mocks/dataMocks.tsx +++ b/services/common/src/tests/mocks/dataMocks.tsx @@ -7168,7 +7168,31 @@ export const PROJECT = { project_title: "Test Project Title", mine_name: "Sample Mine", mine_guid: "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - contacts: [], + contacts: [ + { + project_contact_guid: "65a02cd8-3edd-491f-acdd-585f9f9742ee", + project_guid: "aa5bbbeb-f8ab-496f-aff2-e71d314bcc3e", + job_title: null, + company_name: null, + email: "test@test.com", + phone_number: "999-999-9999", + phone_extension: null, + is_primary: true, + first_name: "Test", + last_name: "Testerson", + address: [ + { + suite_no: null, + address_line_1: "111 street", + address_line_2: null, + city: "Victoria", + sub_division_code: "BC", + post_code: "T5M0V3", + address_type_code: "CAN", + }, + ], + }, + ], project_summary: { documents: [], }, @@ -7277,106 +7301,44 @@ export const PROJECT_SUMMARY = { "OTHER", "WATER_LICENCE", ], - authorizations: { - MINES_ACT_PERMIT: [ - { - project_summary_authorization_guid: "83053925-c0ab-468e-b41c-32eb1607d1be", - project_summary_guid: "0bfc0d9e-542f-4424-91b5-c563dba02619", - project_summary_permit_type: ["NEW"], - project_summary_authorization_type: "MINES_ACT_PERMIT", - existing_permits_authorizations: [""], - amendment_changes: null, - amendment_severity: null, - is_contaminated: null, - new_type: null, - authorization_description: null, - exemption_requested: null, - }, - ], - WATER_LICENCE: [ - { - project_summary_authorization_guid: "ec3dcc27-cce9-4539-a9cd-1b3faa41fc9f", - project_summary_guid: "0bfc0d9e-542f-4424-91b5-c563dba02619", - project_summary_permit_type: ["AMENDMENT"], - project_summary_authorization_type: "WATER_LICENCE", - existing_permits_authorizations: ["PX-1234", "CX-5678"], - amendment_changes: null, - amendment_severity: null, - is_contaminated: null, - new_type: null, - authorization_description: null, - exemption_requested: null, - }, - ], - OCCUPANT_CUT_LICENCE: [ - { - project_summary_authorization_guid: "0d047eeb-7000-4649-89ae-6d295a12fc6a", - project_summary_guid: "0bfc0d9e-542f-4424-91b5-c563dba02619", - project_summary_permit_type: ["NEW", "NOTIFICATION"], - project_summary_authorization_type: "OCCUPANT_CUT_LICENCE", - existing_permits_authorizations: null, - amendment_changes: null, - amendment_severity: null, - is_contaminated: null, - new_type: null, - authorization_description: null, - exemption_requested: null, - }, - ], - OTHER: [ - { - project_summary_authorization_guid: "5600e1e3-b303-4070-b3e2-9986264c8c38", - project_summary_guid: "0bfc0d9e-542f-4424-91b5-c563dba02619", - project_summary_permit_type: ["OTHER"], - project_summary_authorization_type: "OTHER", - existing_permits_authorizations: null, - amendment_changes: null, - amendment_severity: null, - is_contaminated: null, - new_type: null, - authorization_description: "other legislation details", - exemption_requested: null, - }, - ], - AIR_EMISSIONS_DISCHARGE_PERMIT: { - types: ["AMENDMENT"], - NEW: [], - AMENDMENT: [ - { - project_summary_authorization_guid: "b8c63caa-e6e5-4e2e-80c9-327921f7efbf", - project_summary_guid: "0bfc0d9e-542f-4424-91b5-c563dba02619", - project_summary_permit_type: ["AMENDMENT"], - project_summary_authorization_type: "AIR_EMISSIONS_DISCHARGE_PERMIT", - existing_permits_authorizations: ["1234"], - amendment_changes: ["ILT"], - amendment_severity: "SIG", - is_contaminated: false, - new_type: null, - authorization_description: "adsf", - exemption_requested: true, - }, - ], - }, - MUNICIPAL_WASTEWATER_REGULATION: { - types: ["NEW"], - NEW: [ - { - project_summary_authorization_guid: "2ee7161f-7735-40c6-a8a4-ecb6cb79675e", - project_summary_guid: "0bfc0d9e-542f-4424-91b5-c563dba02619", - project_summary_permit_type: ["NEW"], - project_summary_authorization_type: "MUNICIPAL_WASTEWATER_REGULATION", - existing_permits_authorizations: null, - amendment_changes: null, - amendment_severity: null, - is_contaminated: null, - new_type: "APP", - authorization_description: "purpose of application", - exemption_requested: false, - }, - ], - AMENDMENT: [], + authorizations: [ + { + project_summary_authorization_guid: "92e1a34b-c181-4be8-8ad0-f96dcd3ab7cc", + project_summary_guid: "8b4b9781-2e59-43ef-8164-4cc3b964417a", + project_summary_permit_type: ["NEW"], + project_summary_authorization_type: "AIR_EMISSIONS_DISCHARGE_PERMIT", + existing_permits_authorizations: null, + amendment_changes: null, + amendment_severity: null, + is_contaminated: null, + new_type: "PER", + authorization_description: "sdfa", + amendment_documents: [], + exemption_requested: false, + ams_tracking_number: null, + ams_outcome: null, + ams_status_code: null, + ams_submission_timestamp: "2024-05-24T19:10:09.825194+00:00", + }, + { + project_summary_authorization_guid: "624d3acc-b62b-491e-82a3-67ef3b1bbf88", + project_summary_guid: "8b4b9781-2e59-43ef-8164-4cc3b964417a", + project_summary_permit_type: ["NEW"], + project_summary_authorization_type: "REFUSE_DISCHARGE_PERMIT", + existing_permits_authorizations: null, + amendment_changes: null, + amendment_severity: null, + is_contaminated: null, + new_type: "PER", + authorization_description: "asdf", + amendment_documents: [], + exemption_requested: false, + ams_tracking_number: null, + ams_outcome: null, + ams_status_code: null, + ams_submission_timestamp: "2024-05-24T19:17:09.212499+00:00", }, - }, + ], }; export const AUTHORIZATION_INVOLVED = { diff --git a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryForm.tsx b/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryForm.tsx index 6d5a12be6c..8bba60b095 100644 --- a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryForm.tsx +++ b/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryForm.tsx @@ -1,17 +1,7 @@ import React, { FC } from "react"; -import { connect } from "react-redux"; -import { RouteComponentProps, withRouter } from "react-router-dom"; +import { useSelector } from "react-redux"; import { flattenObject, resetForm } from "@common/utils/helpers"; -import { compose, bindActionCreators } from "redux"; -import { - reduxForm, - change, - arrayPush, - formValueSelector, - getFormValues, - getFormSyncErrors, - InjectedFormProps, -} from "redux-form"; +import { formValueSelector, getFormSyncErrors, getFormValues } from "redux-form"; import * as FORM from "@/constants/forms"; import DocumentUpload from "@/components/Forms/projects/projectSummary/DocumentUpload"; import ProjectContacts from "@/components/Forms/projects/projectSummary/ProjectContacts"; @@ -22,13 +12,14 @@ import Step from "@common/components/Step"; import ProjectLinks from "@mds/common/components/projects/ProjectLinks"; import { EDIT_PROJECT } from "@/constants/routes"; import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag"; -import { Feature, IProjectSummary, IProjectSummaryDocument } from "@mds/common"; +import { Feature, IProjectSummary } from "@mds/common"; import { Agent } from "./Agent"; import { LegalLandOwnerInformation } from "@mds/common/components/projectSummary/LegalLandOwnerInformation"; import { FacilityOperator } from "@mds/common/components/projectSummary/FacilityOperator"; import BasicInformation from "@mds/common/components/projectSummary/BasicInformation"; import Applicant from "@/components/Forms/projects/projectSummary/Applicant"; import Declaration from "@mds/common/components/projectSummary/Declaration"; +import FormWrapper from "@mds/common/components/forms/FormWrapper"; import { ApplicationSummary } from "./ApplicationSummary"; interface ProjectSummaryFormProps { @@ -42,14 +33,6 @@ interface ProjectSummaryFormProps { activeTab: string; } -interface StateProps { - documents: IProjectSummaryDocument; - formValues: any; - formErrors: any; - anyTouched: boolean; - // amendmentDocuments: IProjectSummaryDocument[]; -} - // converted to a function to make feature flag easier to work with // when removing feature flag, convert back to array export const getProjectFormTabs = (amsFeatureEnabled: boolean) => { @@ -79,10 +62,24 @@ export const getProjectFormTabs = (amsFeatureEnabled: boolean) => { ]; }; -export const ProjectSummaryForm: FC & - RouteComponentProps> = ({ documents = [], ...props }) => { +export const ProjectSummaryForm: FC = ({ ...props }) => { + const selector = formValueSelector(FORM.ADD_EDIT_PROJECT_SUMMARY); + + const formValues = + useSelector((state) => getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY)(state)) || {}; + const documents = useSelector((state) => selector(state, "documents")) || []; + const formErrors = useSelector((state) => + getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY)(state) + ); + const anyTouched = useSelector((state) => selector(state, "anyTouched")); + + const childProps = { + ...props, + formValues, + formErrors, + anyTouched, + }; + const { isFeatureEnabled } = useFeatureFlag(); const majorProjectsFeatureEnabled = isFeatureEnabled(Feature.MAJOR_PROJECT_LINK_PROJECTS); const amsFeatureEnabled = isFeatureEnabled(Feature.AMS_AGENT); @@ -101,61 +98,46 @@ export const ProjectSummaryForm: FC, "mine-components-and-offsite-infrastructure": , "purpose-and-authorization": ( - + ), "document-upload": ( - + ), "application-summary": , declaration: , }[tab]); - const errors = Object.keys(flattenObject(props.formErrors)); + const errors = Object.keys(flattenObject(formErrors)); const disabledTabs = errors.length > 0; return ( - {}} + initialValues={props.initialValues} + reduxFormConfig={{ + touchOnBlur: true, + touchOnChange: false, + onSubmitSuccess: resetForm(FORM.ADD_EDIT_PROJECT_SUMMARY), + }} > - {projectFormTabs - .filter((tab) => majorProjectsFeatureEnabled || tab !== "related-projects") - .map((tab) => ( - - {renderTabComponent(tab)} - - ))} - + + {projectFormTabs + .filter((tab) => majorProjectsFeatureEnabled || tab !== "related-projects") + .map((tab) => ( + + {renderTabComponent(tab)} + + ))} + + ); }; -const selector = formValueSelector(FORM.ADD_EDIT_PROJECT_SUMMARY); -const mapStateToProps = (state) => ({ - documents: selector(state, "documents"), - formValues: getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY)(state) || {}, - formErrors: getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY)(state), - anyTouched: selector(state, "anyTouched"), -}); - -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - change, - arrayPush, - }, - dispatch - ); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - reduxForm({ - form: FORM.ADD_EDIT_PROJECT_SUMMARY, - touchOnBlur: true, - touchOnChange: false, - onSubmitSuccess: resetForm(FORM.ADD_EDIT_PROJECT_SUMMARY), - onSubmit: () => {}, - }) -)(withRouter(ProjectSummaryForm)) as FC; +export default ProjectSummaryForm; diff --git a/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx b/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx index c2a59f06a2..8e4233151b 100644 --- a/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx +++ b/services/minespace-web/src/components/pages/Project/ProjectSummaryPage.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { flattenObject } from "@common/utils/helpers"; import { Link, Prompt, useHistory, useLocation, useParams } from "react-router-dom"; @@ -34,26 +34,11 @@ import { import ProjectSummaryForm, { getProjectFormTabs, } from "@/components/Forms/projects/projectSummary/ProjectSummaryForm"; -import { Feature, IMine, IProject, IProjectSummary, removeNullValuesRecursive } from "@mds/common"; +import { Feature, removeNullValuesRecursive } from "@mds/common"; import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag"; import { isArray } from "lodash"; import { fetchRegions } from "@mds/common/redux/slices/regionsSlice"; -interface ProjectSummaryPageProps { - mines: Partial[]; - projectSummary: Partial; - project: Partial; - projectSummaryDocumentTypesHash: Record; - formValueSelector: (arg1: string, arg2?: any) => any; - getFormSyncErrors: (arg1: string) => any; - formErrors: Record; - formValues: any; - projectSummaryAuthorizationTypesArray: any[]; - anyTouched: boolean; - formattedProjectSummary: any; - location: Record; -} - interface IParams { mineGuid?: string; projectGuid?: string; @@ -61,7 +46,7 @@ interface IParams { tab?: any; } -export const ProjectSummaryPage: FC = () => { +export const ProjectSummaryPage = () => { const anyTouched = useSelector( (state) => state.form[FORM.ADD_EDIT_PROJECT_SUMMARY]?.anyTouched || false ); @@ -96,9 +81,15 @@ export const ProjectSummaryPage: FC = () => { return dispatch(fetchMineRecordById(mineGuid)); }; + useEffect(() => { + if (project) { + setIsLoaded(true); + } + }, [project]); + useEffect(() => { if (!isLoaded) { - handleFetchData().then(() => setIsLoaded(true)); + handleFetchData(); } return () => { dispatch(clearProjectSummary()); diff --git a/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx b/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx index bc5d81e480..1f12207194 100644 --- a/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx +++ b/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx @@ -1,26 +1,15 @@ import React from "react"; -import { shallow } from "enzyme"; import { ProjectSummaryPage } from "@/components/pages/Project/ProjectSummaryPage"; -import * as MOCK from "@/tests/mocks/dataMocks"; - -const props: any = {}; -const dispatchProps: any = {}; - -const setupProps = () => { - props.projectSummaryDocumentTypesHash = MOCK.PROJECT_SUMMARY_DOCUMENT_TYPES_HASH; - props.mines = {}; - props.fieldsTouched = {}; - props.formattedProjectSummary = { - mine_guid: "123", - }; -}; - -const setupDispatchProps = () => { - dispatchProps.fetchProjectSummaryById = jest.fn(() => Promise.resolve()); - dispatchProps.createProjectSummary = jest.fn(() => Promise.resolve()); - dispatchProps.updateProjectSummary = jest.fn(() => Promise.resolve()); - dispatchProps.fetchMineRecordById = jest.fn(() => Promise.resolve()); -}; +import { render } from "@testing-library/react"; +import { + BULK_STATIC_CONTENT_RESPONSE, + PROJECT, + PROJECT_SUMMARY, +} from "@mds/common/tests/mocks/dataMocks"; +import { PROJECTS, STATIC_CONTENT } from "@mds/common/constants/reducerTypes"; +import { ReduxWrapper } from "@/tests/utils/ReduxWrapper"; +import { REGIONS } from "@/tests/mocks/dataMocks"; +import { BrowserRouter } from "react-router-dom"; function mockFunction() { const original = jest.requireActual("react-router-dom"); @@ -30,6 +19,7 @@ function mockFunction() { projectGuid: "74120872-74f2-4e27-82e6-878ddb472e5a", projectSummaryGuid: "70414192-ca71-4d03-93a5-630491e9c554", tab: "basic-information", + mineGuid: "12345678-74f2-4e27-82e6-878ddb472e5a", }), useLocation: jest.fn().mockReturnValue({ pathname: @@ -40,14 +30,35 @@ function mockFunction() { jest.mock("react-router-dom", () => mockFunction()); -beforeEach(() => { - setupProps(); - setupDispatchProps(); -}); +const initialState = { + regions: { regions: REGIONS }, + [PROJECTS]: { + project: PROJECT, + projectSummary: PROJECT_SUMMARY, + }, + [STATIC_CONTENT]: { + projectSummaryAuthorizationTypes: BULK_STATIC_CONTENT_RESPONSE.projectSummaryAuthorizationTypes, + projectSummaryDocumentTypes: BULK_STATIC_CONTENT_RESPONSE.projectSummaryDocumentTypes, + }, +}; + +jest.mock("@/components/Forms/projects/projectSummary/ProjectSummaryForm", () => ({ + __esModule: true, + default: jest.fn(() =>
Mock Project Summary Form
), + getProjectFormTabs: jest.fn().mockReturnValue(["mockTab1", "mockTab2"]), +})); describe("ProjectSummaryPage", () => { - it("renders properly", () => { - const component = shallow(); - expect(component).toMatchSnapshot(); + it("renders properly", async () => { + const { container, findByText } = render( + + + + + + ); + + await findByText(/Edit project description - Sample title/i); + expect(container).toMatchSnapshot(); }); }); diff --git a/services/minespace-web/src/tests/components/project/projectSummaryPage/__snapshots__/ProjectSummaryPage.spec.tsx.snap b/services/minespace-web/src/tests/components/project/projectSummaryPage/__snapshots__/ProjectSummaryPage.spec.tsx.snap index df93bef6e2..0f3b16447e 100644 --- a/services/minespace-web/src/tests/components/project/projectSummaryPage/__snapshots__/ProjectSummaryPage.spec.tsx.snap +++ b/services/minespace-web/src/tests/components/project/projectSummaryPage/__snapshots__/ProjectSummaryPage.spec.tsx.snap @@ -1,3 +1,61 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ProjectSummaryPage renders properly 1`] = ``; +exports[`ProjectSummaryPage renders properly 1`] = ` +
+
+
+

+ Edit project description - Sample title +

+
+
+ + +`; diff --git a/services/minespace-web/src/tests/mocks/dataMocks.js b/services/minespace-web/src/tests/mocks/dataMocks.js index b6a6e2bff4..34fe22daa6 100644 --- a/services/minespace-web/src/tests/mocks/dataMocks.js +++ b/services/minespace-web/src/tests/mocks/dataMocks.js @@ -1218,10 +1218,7 @@ export const PROJECT = { name: "Test Contact", }, ], - project_summary: { - project_summary_guid: "6bab1df6-e181-435a-abc0-e99466411880", - status_code: "SUB", - }, + project_summary: {}, major_mine_application: { major_mine_application_guid: "abcde12345", status_code: "DFT", @@ -1957,3 +1954,26 @@ export const NOTICE_OF_DEPARTURE_DETAILS = { }, ], }; + +export const REGIONS = [ + { + name: "Alberni-Clayoquot", + regional_district_id: 4786586, + }, + { + name: "Bulkley-Nechako", + regional_district_id: 4786587, + }, + { + name: "Capital", + regional_district_id: 4786588, + }, + { + name: "Cariboo", + regional_district_id: 4786590, + }, + { + name: "Central Coast", + regional_district_id: 4786592, + }, +]; From 92d92ae2cf6f2a37d67e7b568a8453ef9215044a Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 14:32:35 -0600 Subject: [PATCH 08/13] rebase fix --- .../common/src/components/projectSummary/FacilityOperator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/common/src/components/projectSummary/FacilityOperator.tsx b/services/common/src/components/projectSummary/FacilityOperator.tsx index 2c03b68209..41239d5c9e 100644 --- a/services/common/src/components/projectSummary/FacilityOperator.tsx +++ b/services/common/src/components/projectSummary/FacilityOperator.tsx @@ -1,7 +1,7 @@ -import React, { FC } from "react"; +import React, { FC, useEffect, useState } from "react"; import { useSelector } from "react-redux"; import { Col, Row, Typography } from "antd"; -import { Field, getFormValues } from "redux-form"; +import { Field, getFormSyncErrors, getFormValues } from "redux-form"; import { email, maxLength, From 36784f3426f5fb19496e95da7d9fdb43879f7988 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 14:38:28 -0600 Subject: [PATCH 09/13] update snap --- .../DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap b/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap index 8920ab5f2a..1d8cf4a04e 100644 --- a/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap +++ b/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap @@ -92,10 +92,7 @@ exports[`DocumentsTab renders properly 1`] = ` "status_code": "DFT", }, "project_guid": "8132462392222", - "project_summary": Object { - "project_summary_guid": "6bab1df6-e181-435a-abc0-e99466411880", - "status_code": "SUB", - }, + "project_summary": Object {}, "project_title": "Test Mine", "proponent_project_id": "Test-123", "update_timestamp": "2023-08-04T09:21:06.028471-06:00", From 054aafc93456180ecd0cc86f1f85c353f4c6ca8a Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 15:50:25 -0600 Subject: [PATCH 10/13] fix mock data --- .../DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap | 5 ++++- services/minespace-web/src/tests/mocks/dataMocks.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap b/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap index 1d8cf4a04e..8920ab5f2a 100644 --- a/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap +++ b/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap @@ -92,7 +92,10 @@ exports[`DocumentsTab renders properly 1`] = ` "status_code": "DFT", }, "project_guid": "8132462392222", - "project_summary": Object {}, + "project_summary": Object { + "project_summary_guid": "6bab1df6-e181-435a-abc0-e99466411880", + "status_code": "SUB", + }, "project_title": "Test Mine", "proponent_project_id": "Test-123", "update_timestamp": "2023-08-04T09:21:06.028471-06:00", diff --git a/services/minespace-web/src/tests/mocks/dataMocks.js b/services/minespace-web/src/tests/mocks/dataMocks.js index 34fe22daa6..5127a974fc 100644 --- a/services/minespace-web/src/tests/mocks/dataMocks.js +++ b/services/minespace-web/src/tests/mocks/dataMocks.js @@ -1218,7 +1218,10 @@ export const PROJECT = { name: "Test Contact", }, ], - project_summary: {}, + project_summary: { + project_summary_guid: "6bab1df6-e181-435a-abc0-e99466411880", + status_code: "SUB", + }, major_mine_application: { major_mine_application_guid: "abcde12345", status_code: "DFT", From 1ecf4096bd2cfffe3af3d157d64a5723c387fd48 Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 16:30:32 -0600 Subject: [PATCH 11/13] clean up --- .../projectSummary/FacilityOperator.tsx | 31 ++----------------- services/common/src/tests/mocks/dataMocks.tsx | 23 ++++++++++++++ .../project_summary/models/project_summary.py | 1 - .../ProjectSummaryPage.spec.tsx | 6 ++-- .../src/tests/mocks/dataMocks.js | 23 -------------- 5 files changed, 30 insertions(+), 54 deletions(-) diff --git a/services/common/src/components/projectSummary/FacilityOperator.tsx b/services/common/src/components/projectSummary/FacilityOperator.tsx index 41239d5c9e..fe3e014e3e 100644 --- a/services/common/src/components/projectSummary/FacilityOperator.tsx +++ b/services/common/src/components/projectSummary/FacilityOperator.tsx @@ -1,7 +1,7 @@ -import React, { FC, useEffect, useState } from "react"; +import React, { FC } from "react"; import { useSelector } from "react-redux"; import { Col, Row, Typography } from "antd"; -import { Field, getFormSyncErrors, getFormValues } from "redux-form"; +import { Field, getFormValues } from "redux-form"; import { email, maxLength, @@ -22,35 +22,10 @@ import { getRegionOptions } from "@mds/common/redux/slices/regionsSlice"; export const FacilityOperator: FC = () => { const formValues = useSelector(getFormValues(FORM.ADD_EDIT_PROJECT_SUMMARY)); - const formErrors = useSelector(getFormSyncErrors(FORM.ADD_EDIT_PROJECT_SUMMARY)); - const { - facility_coords_source, - zoning, - facility_latitude, - facility_longitude, - legal_land_desc, - facility_pid_pin_crown_file_no, - } = formValues; - const [pin, setPin] = useState>([]); + const { zoning } = formValues; const regionOptions = useSelector(getRegionOptions); - useEffect(() => { - // don't jump around the map while coords being entered and not yet valid - const invalidPin = Boolean(formErrors.facility_longitude || formErrors.facility_latitude); - if (!invalidPin) { - const latLng = [facility_latitude, facility_longitude]; - setPin(latLng); - } - }, [facility_longitude, facility_latitude]); - - const dataSourceOptions = [ - { value: "GPS", label: "GPS" }, - { value: "SUR", label: "Survey" }, - { value: "GGE", label: "Google Earth" }, - { value: "OTH", label: "Other" }, - ]; - const address_type_code = "CAN"; const provinceOptions = useSelector(getDropdownProvinceOptions).filter( diff --git a/services/common/src/tests/mocks/dataMocks.tsx b/services/common/src/tests/mocks/dataMocks.tsx index ebc327e16a..33b4767f9f 100644 --- a/services/common/src/tests/mocks/dataMocks.tsx +++ b/services/common/src/tests/mocks/dataMocks.tsx @@ -8138,3 +8138,26 @@ export const MINES_ACT_PERMITS_VC_LIST = [ cred_rev_id: "1234", }, ]; + +export const REGIONS = [ + { + name: "Alberni-Clayoquot", + regional_district_id: 4786586, + }, + { + name: "Bulkley-Nechako", + regional_district_id: 4786587, + }, + { + name: "Capital", + regional_district_id: 4786588, + }, + { + name: "Cariboo", + regional_district_id: 4786590, + }, + { + name: "Central Coast", + regional_district_id: 4786592, + }, +]; 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 da3c63748b..2cc8e5bd39 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 @@ -1112,7 +1112,6 @@ def update(self, regional_district_name = regional_district_id is not None and Regions.find_by_id( regional_district_id).name or None - current_app.logger.info(regional_district_name) if ams_authorizations: ams_results = [] diff --git a/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx b/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx index 1f12207194..fd9659ed0d 100644 --- a/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx +++ b/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx @@ -5,10 +5,10 @@ import { BULK_STATIC_CONTENT_RESPONSE, PROJECT, PROJECT_SUMMARY, + REGIONS, } from "@mds/common/tests/mocks/dataMocks"; import { PROJECTS, STATIC_CONTENT } from "@mds/common/constants/reducerTypes"; import { ReduxWrapper } from "@/tests/utils/ReduxWrapper"; -import { REGIONS } from "@/tests/mocks/dataMocks"; import { BrowserRouter } from "react-router-dom"; function mockFunction() { @@ -31,7 +31,9 @@ function mockFunction() { jest.mock("react-router-dom", () => mockFunction()); const initialState = { - regions: { regions: REGIONS }, + regions: { + regions: REGIONS, + }, [PROJECTS]: { project: PROJECT, projectSummary: PROJECT_SUMMARY, diff --git a/services/minespace-web/src/tests/mocks/dataMocks.js b/services/minespace-web/src/tests/mocks/dataMocks.js index 5127a974fc..b6a6e2bff4 100644 --- a/services/minespace-web/src/tests/mocks/dataMocks.js +++ b/services/minespace-web/src/tests/mocks/dataMocks.js @@ -1957,26 +1957,3 @@ export const NOTICE_OF_DEPARTURE_DETAILS = { }, ], }; - -export const REGIONS = [ - { - name: "Alberni-Clayoquot", - regional_district_id: 4786586, - }, - { - name: "Bulkley-Nechako", - regional_district_id: 4786587, - }, - { - name: "Capital", - regional_district_id: 4786588, - }, - { - name: "Cariboo", - regional_district_id: 4786590, - }, - { - name: "Central Coast", - regional_district_id: 4786592, - }, -]; From 8108f223799dc1ffbf4625c6908a37f57e0ceeac Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 16:43:51 -0600 Subject: [PATCH 12/13] clean up --- .../projectSummaryPage/ProjectSummaryPage.spec.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx b/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx index fd9659ed0d..9101ee9ecc 100644 --- a/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx +++ b/services/minespace-web/src/tests/components/project/projectSummaryPage/ProjectSummaryPage.spec.tsx @@ -44,6 +44,16 @@ const initialState = { }, }; +/** + * There seems to be a provider issue when rendering the ProjectSummaryForm component + * the FormWrapper which is used as a child component requires the ReduxWrapper from the common directory + * while the ProjectSummaryPage component requires the ReduxWrapper from the minespace directory. + * + * So for now, the ProjectSummaryForm component is being mocked to avoid the provider issue. + * + * There is work planned to move the Project Summary related components to the common directory, + * so this test will need to be updated once that work is completed. + */ jest.mock("@/components/Forms/projects/projectSummary/ProjectSummaryForm", () => ({ __esModule: true, default: jest.fn(() =>
Mock Project Summary Form
), From 3e5006525559a042c1d12dc818fc0049ea99709e Mon Sep 17 00:00:00 2001 From: matbusby-fw Date: Mon, 27 May 2024 17:30:29 -0600 Subject: [PATCH 13/13] rename migration for correct ordering --- ...ions_table.sql => V2024.05.22.13.00__create_regions_table.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename migrations/sql/{V2024.05.21.13.00__create_regions_table.sql => V2024.05.22.13.00__create_regions_table.sql} (100%) diff --git a/migrations/sql/V2024.05.21.13.00__create_regions_table.sql b/migrations/sql/V2024.05.22.13.00__create_regions_table.sql similarity index 100% rename from migrations/sql/V2024.05.21.13.00__create_regions_table.sql rename to migrations/sql/V2024.05.22.13.00__create_regions_table.sql