Skip to content

Commit

Permalink
Merge pull request #351 from cityofaustin/6250-sg-new-project-form
Browse files Browse the repository at this point in the history
Project Components: Extend "Map Project" step to include component attributes #6250
  • Loading branch information
sergiogcx authored Jun 25, 2021
2 parents 4f8fc82 + 70a7dd1 commit 4e25b51
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Delete extent project component */
DELETE FROM public.moped_components WHERE component_id = 0;

/* Then delete the rest of the components in the migration */
DELETE FROM public.moped_components
WHERE component_id >= 19 AND component_id <= 59;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* Adds the project extent component or generic type */
INSERT INTO
public.moped_components (component_id, component_name, status_id, component_subtype, line_representation)
VALUES
(19, 'Bike Parking', 1, null, false),
(20, 'Bike Parking', 1, 'Corral', false),
(21, 'Bike Lane', 1, 'Turn Lane', true),
(22, 'Bike Lane', 1, 'Wide Curb Lane', true),
(23, 'Pavement Marking', 1, 'Crossbike', false),
(24, 'Access Control', 1, 'Driveway Gate', false),
(25, 'Dynamic Speed Display Device', 1, null, false),
(26, 'Guardrail', 1, null, true),
(27, 'Highway', 1, 'Access Ramp', false),
(28, 'Highway', 1, 'Added Capacity / Lanes', true),
(29, 'Highway', 1, 'Collector Distributor', true),
(30, 'Highway', 1, 'Flyover', true),
(31, 'Highway', 1, 'Intersection Grade Separation', true),
(32, 'Highway', 1, 'Managed Lane', true),
(33, 'Highway', 1, 'Toll Road', true),
(34, 'Landscaping', 1, null, true),
(35, 'Placemaking', 1, null, true),
(36, 'Refuge Island', 1, 'Bike', false),
(37, 'Refuge Island', 1, 'Ped', false),
(38, 'Refuge Island', 1, 'Bike/Ped', false),
(39, 'Signal', 1, 'School Zone Beacon', false),
(40, 'Pavement Marking', 1, 'School Zone', false),
(41, 'Sidewalk', 1, null, true),
(42, 'Sidewalk', 1, 'In Street', true),
(43, 'Sidewalk', 1, 'Wide', true),
(44, 'Sidewalk', 1, 'With Curb and Gutter', true),
(45, 'Sidewalk', 1, 'Rams', true),
(46, 'Speed Management', 1, 'Chicane', false),
(47, 'Speed Management', 1, 'Nbhd Traffic Circle', false),
(48, 'Speed Management', 1, 'Speed Cushions (Asphalt)', false),
(49, 'Speed Management', 1, 'Speed Cushions (Rubber)', false),
(50, 'Speed Management', 1, 'Speed Humps', false),
(51, 'Pavement Marking', 1, 'Stop Bar', false),
(52, 'Transit', 1, 'Lane', true),
(53, 'Transit', 1, 'Managed Lane Access Point', true),
(54, 'Transit', 1, 'Transit Queue Jump', true),
(55, 'Transit', 1, 'Transit/Bike Lane', true),
(56, 'Pavement Marking', 1, 'Two-stage Bike Turn Queue', false),
(57, 'Pavement Marking', 1, 'Sharrows', false),
(58, 'Pavement Marking', 1, 'Sharrows (Wide Curb Lane)', false),
(59, 'Signage', 1, null, false),
(0, 'Project Extent - Generic', 1, null, false);
19 changes: 19 additions & 0 deletions moped-editor/src/queries/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export const ADD_PROJECT = gql`
fiscal_year
capitally_funded
start_date
moped_proj_components {
moped_proj_features_components {
moped_proj_feature {
feature_id
}
}
}
}
}
`;
Expand Down Expand Up @@ -543,6 +550,7 @@ export const COMPONENT_DETAILS_QUERY = gql`
}
moped_proj_features_components(where: { status_id: { _eq: 1 } }) {
project_features_components_id
moped_proj_features_id
status_id
moped_proj_feature {
location
Expand Down Expand Up @@ -628,3 +636,14 @@ export const DELETE_MOPED_COMPONENT = gql`
}
}
`;

export const UPDATE_NEW_PROJ_FEATURES = gql`
mutation UpdateNewProjectFeatures($featureList: [Int!]!, $projectId: Int!) {
update_moped_proj_features(
where: { feature_id: { _in: $featureList } }
_set: { project_id: $projectId }
) {
affected_rows
}
}
`;
20 changes: 17 additions & 3 deletions moped-editor/src/utils/mapHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,23 @@ export const getLayerSource = e =>
*/
export const createFeatureCollectionFromProjectFeatures = projectFeatureRecords => ({
type: "FeatureCollection",
features: projectFeatureRecords
? projectFeatureRecords.map(feature => feature.location)
: [],
features: projectFeatureRecords // Do we have a records object?
? // We do, then unpack features
projectFeatureRecords.reduce(
(accumulator, feature) => [
// First copy the current state of the accumulator
...accumulator,
// Then we must copy any individual feature (or features)
...(feature.location.type === "FeatureCollection"
? // We must unpack the features
feature.location.features
: // Provide the regular feature
[feature.location]),
],
[] // Initial accumulator state
)
: // We don't, return empty array
[],
});

/**
Expand Down
138 changes: 119 additions & 19 deletions moped-editor/src/views/projects/newProjectView/NewProjectView.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import {
Button,
Expand All @@ -20,7 +20,10 @@ import NewProjectTeam from "./NewProjectTeam";
import NewProjectMap from "./NewProjectMap";
import Page from "src/components/Page";
import { useMutation } from "@apollo/client";
import { ADD_PROJECT } from "../../../queries/project";
import {
ADD_PROJECT,
UPDATE_NEW_PROJ_FEATURES,
} from "../../../queries/project";
import { filterObjectByKeys } from "../../../utils/materialTableHelpers";
import { countFeatures, mapErrors, mapConfig } from "../../../utils/mapHelpers";

Expand Down Expand Up @@ -76,6 +79,16 @@ const NewProjectView = () => {
}
}, [success, newProjectId, navigate]);

/**
* Form State
* @type {Number} activeStep - The current step being rendered
* @type {Object} projectDetails - The current state of project details
* @type {boolean} nameError - When true, it denotes an error in the name
* @type {boolean} descriptionError - When true, it denotes an error in the project description
* @type {Object[]} personnel - An array of objects containing the personnel data
* @type {Object} featureCollection - The final GeoJSON to be inserted into a component
* @type {boolean} areNoFeaturesSelected - True when no features are selected
*/
const [activeStep, setActiveStep] = useState(0);
const [projectDetails, setProjectDetails] = useState({
fiscal_year: "",
Expand All @@ -89,13 +102,11 @@ const NewProjectView = () => {
});
const [nameError, setNameError] = useState(false);
const [descriptionError, setDescriptionError] = useState(false);

const [personnel, setPersonnel] = useState([]);
const [featureCollection, setFeatureCollection] = useState({
type: "FeatureCollection",
features: [],
});

const [areNoFeaturesSelected, setAreNoFeaturesSelected] = useState(false);

// Reset areNoFeaturesSelected once a feature is selected to remove error message
Expand All @@ -107,6 +118,10 @@ const NewProjectView = () => {
}
}, [featureCollection]);

/**
* Generates an object with labels for the steps
* @return {Object[]}
*/
const getSteps = () => {
return [
{ label: "Define project" },
Expand All @@ -119,6 +134,11 @@ const NewProjectView = () => {
];
};

/**
* Returns a component for a specific step number
* @param {Number} step - The step number component to render
* @return {JSX.Element|string}
*/
const getStepContent = step => {
switch (step) {
case 0:
Expand All @@ -145,8 +165,18 @@ const NewProjectView = () => {
return "Unknown step";
}
};

/**
* A constant object with all the steps generated by getSteps
* @constant
* @type {Object}
*/
const steps = getSteps();

/**
* Handles the logic for the "Next" button
* @return {string}
*/
const handleNext = () => {
let nameError = projectDetails.project_name.length === 0;
let descriptionError = projectDetails.project_description.length === 0;
Expand Down Expand Up @@ -175,6 +205,10 @@ const NewProjectView = () => {
setDescriptionError(descriptionError);
};

/**
* Handles the back step button logic
* @return {string}
*/
const handleBack = () => {
if (activeStep > 0) {
setActiveStep(prevActiveStep => prevActiveStep - 1);
Expand All @@ -189,22 +223,36 @@ const NewProjectView = () => {
}
};

/**
* Handles the reset button logic
*/
const handleReset = () => {
setActiveStep(0);
};

/**
* Add Project Apollo Mutation
*/
const [addProject] = useMutation(ADD_PROJECT);
const [updateFeatures] = useMutation(UPDATE_NEW_PROJ_FEATURES);

const timer = React.useRef();
/**
* Timer Reference Object
* @type {React.MutableRefObject}
*/
const timer = useRef();

React.useEffect(() => {
useEffect(() => {
const currentTimer = timer.current;

return () => {
clearTimeout(currentTimer);
};
}, []);

/**
* Persists a new project into the database
*/
const handleSubmit = () => {
if (countFeatures(featureCollection) < mapConfig.minimumFeaturesInProject) {
setAreNoFeaturesSelected(true);
Expand Down Expand Up @@ -238,24 +286,76 @@ const NewProjectView = () => {
...filterObjectByKeys(row, ["tableData"]),
}));

const projectFeatures = featureCollection.features.map(feature => ({
location: feature,
status_id: 1,
}));

addProject({
variables: {
object: {
...projectDetails,
moped_proj_features: { data: projectFeatures },
moped_proj_personnel: { data: cleanedPersonnel },
/**
* We now must generate the payload with variables for our GraphQL query.
* @type {Object}
*/
const variablePayload = {
object: {
// First we need to copy the project details
...projectDetails,
// Next we generate the project extent component
moped_proj_components: {
data: [
{
name: "Extent",
description: "Project full extent",
component_id: 0,
status_id: 1,
moped_proj_features_components: {
data: featureCollection.features.map(feature => ({
name: "Feature Extent Component",
description: "New Project Feature Extent",
status_id: 1,
moped_proj_feature_object: {
data: {
status_id: 1,
location: feature,
},
},
})),
},
},
],
},
// Finally we provide the project personnel
moped_proj_personnel: { data: cleanedPersonnel },
},
};

/**
* Persist the new project to database
*/
addProject({
variables: variablePayload,
})
// On success
.then(response => {
const { project_id } = response.data.insert_moped_project_one;
setNewProjectId(project_id);
// Destructure the data we need from the response
const {
project_id,
moped_proj_components,
} = response.data.insert_moped_project_one;

// Retrieve the feature_ids that need to be updated
const featuresToUpdate = moped_proj_components[0].moped_proj_features_components.map(
featureComponent => featureComponent.moped_proj_feature.feature_id
);

// Persist the feature updates, we must.
updateFeatures({
variables: {
featureList: featuresToUpdate,
projectId: project_id,
},
})
.then(() => setNewProjectId(project_id))
.catch(err => {
alert(err);
setNewProjectId(project_id);
});
})
// If there is an error, we must show it...
.catch(err => {
alert(err);
setLoading(false);
Expand Down
Loading

0 comments on commit 4e25b51

Please sign in to comment.