From 3413a2b7e7b94f4542edc4c50895e2b97570dcf4 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 6 Nov 2023 14:36:33 -0500 Subject: [PATCH 01/53] basic poc --- .../1699298159676_phase_timestamps/down.sql | 0 .../1699298159676_phase_timestamps/up.sql | 407 ++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 moped-database/migrations/1699298159676_phase_timestamps/down.sql create mode 100644 moped-database/migrations/1699298159676_phase_timestamps/up.sql diff --git a/moped-database/migrations/1699298159676_phase_timestamps/down.sql b/moped-database/migrations/1699298159676_phase_timestamps/down.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/moped-database/migrations/1699298159676_phase_timestamps/up.sql b/moped-database/migrations/1699298159676_phase_timestamps/up.sql new file mode 100644 index 0000000000..4da779f08c --- /dev/null +++ b/moped-database/migrations/1699298159676_phase_timestamps/up.sql @@ -0,0 +1,407 @@ +DROP VIEW IF EXISTS component_arcgis_online_view; + +DROP VIEW IF EXISTS project_list_view; + + +ALTER TABLE moped_proj_phases ALTER COLUMN phase_start TYPE timestamp WITH time zone, +ALTER COLUMN phase_end TYPE timestamp WITH time zone; + +SET session_replication_role = replica; +UPDATE + moped_proj_phases +SET + phase_start = subquery.phase_start, + phase_end = subquery.phase_end +FROM ( + SELECT + project_id, + phase_start AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_start, + phase_end AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_end + FROM + moped_proj_phases) AS subquery +WHERE + moped_proj_phases.project_id = subquery.project_id; + +-- latest version 1698786868086_add_component__subtype_to_view +CREATE OR REPLACE VIEW public.project_list_view +AS WITH project_person_list_lookup AS ( + SELECT + mpp.project_id, + string_agg(DISTINCT concat(mu.first_name, ' ', mu.last_name, ':', mpr.project_role_name), ','::text) AS project_team_members + FROM moped_proj_personnel mpp + JOIN moped_users mu ON mpp.user_id = mu.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE mpp.is_deleted = false + AND mppr.is_deleted = false + GROUP BY mpp.project_id + ), funding_sources_lookup AS ( + SELECT + mpf_1.project_id, + string_agg(mfs.funding_source_name, ', '::text) AS funding_source_name + FROM moped_proj_funding mpf_1 + LEFT JOIN moped_fund_sources mfs ON mpf_1.funding_source_id = mfs.funding_source_id + WHERE mpf_1.is_deleted = false + GROUP BY mpf_1.project_id + ), project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types mpt + LEFT JOIN moped_types mt ON mpt.project_type_id = mt.type_id AND mpt.is_deleted = false + GROUP BY mpt.project_id + ), child_project_lookup AS ( + SELECT jsonb_agg(children.project_id) AS children_project_ids, + children.parent_project_id AS parent_id + FROM moped_project AS children + JOIN moped_project AS parent ON (parent.project_id = children.parent_project_id) + WHERE children.is_deleted = false + GROUP BY parent_id + ), work_activities AS ( + SELECT + project_id, + string_agg(task_order_objects.task_order_object ->> 'display_name'::text, + ', '::text) AS task_order_names, + string_agg(task_order_objects.task_order_object ->> 'task_order'::text, + ', '::text) AS task_order_names_short, + jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + string_agg(DISTINCT mpwa.contractor, + ', '::text) AS contractors, + string_agg(mpwa.contract_number, + ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 + AND mpwa.is_deleted = FALSE + GROUP BY + mpwa.project_id + ), moped_proj_components_subtypes AS ( + SELECT + mpc.project_id, + string_agg(DISTINCT mc.component_name_full, ', '::text) AS components + FROM moped_proj_components mpc + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + GROUP BY mpc.project_id + ) + SELECT + mp.project_id, + mp.project_name, + mp.project_description, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + mp.updated_at, + current_phase.phase_name as current_phase, + current_phase.phase_key as current_phase_key, + current_phase.phase_name_simple as current_phase_simple, + ppll.project_team_members, + me.entity_name AS project_sponsor, + mel.entity_name AS project_lead, + mpps.name AS public_process_status, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + proj_notes.project_note, + proj_notes.date_created as project_note_date_created, + work_activities.contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders, + (SELECT project_name + FROM moped_project + WHERE project_id = mp.parent_project_id + ) as parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partner, + (SELECT JSON_AGG(json_build_object('signal_id', feature_signals.signal_id, 'knack_id', feature_signals.knack_id, 'location_name', feature_signals.location_name, 'signal_type', feature_signals.signal_type, 'id', feature_signals.id)) + FROM moped_proj_components components + LEFT JOIN feature_signals + ON (feature_signals.component_id = components.project_component_id) + WHERE TRUE + AND components.is_deleted = false + AND components.project_id = mp.project_id + AND feature_signals.signal_id is not null + AND feature_signals.is_deleted = false + ) as project_feature, + fsl.funding_source_name, + ptl.type_name, + ( -- get the date of the construction phase with the earliest start date + SELECT min(phases.phase_start) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 9 -- phase_id 9 is construction + AND phases.is_deleted = false + ) AS construction_start_date, + ( -- get the date of the completion phase with the latest end date + SELECT max(phases.phase_end) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 11 -- phase_id 11 is complete + AND phases.is_deleted = false + ) AS completion_end_date, + ( -- get me a list of the inspectors for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Inspector'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_inspector, + ( -- get me a list of the designers for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Designer'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_designer, + ( -- get me all of the tags added to a project + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags ptags + JOIN moped_tags tags ON ptags.tag_id = tags.id + WHERE 1 = 1 + AND ptags.is_deleted = false + AND ptags.project_id = mp.project_id + GROUP BY ptags.project_id) AS project_tags, + concat(added_by_user.first_name, ' ', added_by_user.last_name) AS added_by, + mpcs.components + FROM moped_project mp + LEFT JOIN project_person_list_lookup ppll ON mp.project_id = ppll.project_id + LEFT JOIN funding_sources_lookup fsl ON fsl.project_id = mp.project_id + LEFT JOIN project_type_lookup ptl ON ptl.project_id = mp.project_id + LEFT JOIN moped_entity me ON me.entity_id = mp.project_sponsor + LEFT JOIN moped_entity mel ON mel.entity_id = mp.project_lead_id + LEFT JOIN moped_proj_partners mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false + LEFT JOIN moped_entity me2 ON mpp2.entity_id = me2.entity_id + LEFT JOIN work_activities on work_activities.project_id = mp.project_id + LEFT JOIN moped_users added_by_user ON mp.added_by = added_by_user.user_id + LEFT JOIN current_phase_view current_phase on mp.project_id = current_phase.project_id + LEFT JOIN moped_public_process_statuses mpps ON mpps.id = mp.public_process_status_id + LEFT JOIN child_project_lookup cpl on cpl.parent_id = mp.project_id + LEFT JOIN moped_proj_components_subtypes mpcs on mpcs.project_id = mp.project_id + LEFT JOIN LATERAL + ( + SELECT mpn.project_note, mpn.date_created + FROM moped_proj_notes mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.date_created DESC + LIMIT 1 + ) as proj_notes on true + WHERE + mp.is_deleted = false + GROUP BY + mp.project_id, + mp.project_name, + mp.project_description, + ppll.project_team_members, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + me.entity_name, + mel.entity_name, + mp.updated_at, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + current_phase.phase_name, + current_phase.phase_key, + current_phase.phase_name_simple, + ptl.type_name, + mpcs.components, + fsl.funding_source_name, + added_by_user.first_name, + added_by_user.last_name, + mpps.name, + cpl.children_project_ids, + proj_notes.project_note, + proj_notes.date_created, + work_activities.contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders; + +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name AS component_name, + mc.component_subtype AS component_subtype, + mc.component_name || ' - ' || mc.component_subtype AS component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id::text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id::text || '?tab=map&project_component_id=' || mpc.project_component_id::text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id::text, ', ') AS feature_ids, + ST_AsGeoJSON(ST_Union(ARRAY_AGG(geography)))::json AS "geometry", + STRING_AGG(DISTINCT signal_id::text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography::geometry, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography::geometry, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography::geometry, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography::geometry, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography::geometry, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE) feature_union + GROUP BY + component_id) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT council_district_id::text, ', ') AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); From cc11ce6fa90e5aa0d2a421aa565220edec78826e Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 7 Nov 2023 13:33:20 -0500 Subject: [PATCH 02/53] upgrade mui date pickers --- moped-editor/package-lock.json | 176 ++++++++++++++++++++------------- moped-editor/package.json | 2 +- 2 files changed, 106 insertions(+), 72 deletions(-) diff --git a/moped-editor/package-lock.json b/moped-editor/package-lock.json index d4f660c630..4a6214acf6 100644 --- a/moped-editor/package-lock.json +++ b/moped-editor/package-lock.json @@ -1,12 +1,12 @@ { "name": "atd-moped-editor", - "version": "1.35.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "atd-moped-editor", - "version": "1.35.0", + "version": "2.0.0", "license": "CC0-1.0", "dependencies": { "@apollo/client": "^3.8.1", @@ -22,7 +22,7 @@ "@mui/material": "^5.14.7", "@mui/styles": "^5.14.7", "@mui/x-data-grid": "^6.12.0", - "@mui/x-date-pickers": "^6.12.0", + "@mui/x-date-pickers": "^6.18.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/user-event": "^14.4.3", "@turf/bbox": "^6.5.0", @@ -8791,9 +8791,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", - "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -11029,11 +11029,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", + "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -11042,13 +11042,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz", - "integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.17.tgz", + "integrity": "sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ==", "dependencies": { - "@babel/runtime": "^7.22.10", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", + "@babel/runtime": "^7.23.2", + "@types/prop-types": "^15.7.9", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -11060,7 +11059,13 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@mui/x-data-grid": { @@ -11097,13 +11102,14 @@ } }, "node_modules/@mui/x-date-pickers": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.12.0.tgz", - "integrity": "sha512-lEfdPKdr2o2jUvEviYB/xaYaHJ3Gf9u/AvU3eCX6R0mzIpi1h1SsmrFOTcBIFkwz1iekUNIdZjfrkKmLX+n6dA==", - "dependencies": { - "@babel/runtime": "^7.22.11", - "@mui/utils": "^5.14.5", - "@types/react-transition-group": "^4.4.6", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.0.tgz", + "integrity": "sha512-y4UlkHQXiNRfb6FWQ/GWir0sZ+9kL+GEEZssG+XWP3KJ+d3lONRteusl4AJkYJBdIAOh+5LnMV9RAQKq9Sl7yw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", "clsx": "^2.0.0", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" @@ -11118,7 +11124,6 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/base": "^5.0.0-alpha.87", "@mui/material": "^5.8.6", "@mui/system": "^5.8.0", "date-fns": "^2.25.0", @@ -11161,6 +11166,37 @@ } } }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/base": { + "version": "5.0.0-beta.23", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.23.tgz", + "integrity": "sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/x-date-pickers/node_modules/clsx": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", @@ -12846,9 +12882,9 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.9", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" }, "node_modules/@types/q": { "version": "1.5.5", @@ -12892,18 +12928,10 @@ "@types/react": "^17" } }, - "node_modules/@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", + "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==", "dependencies": { "@types/react": "*" } @@ -38009,9 +38037,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "@babel/runtime": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", - "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -39587,19 +39615,18 @@ } }, "@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", + "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", "requires": {} }, "@mui/utils": { - "version": "5.14.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz", - "integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==", + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.17.tgz", + "integrity": "sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ==", "requires": { - "@babel/runtime": "^7.22.10", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", + "@babel/runtime": "^7.23.2", + "@types/prop-types": "^15.7.9", "prop-types": "^15.8.1", "react-is": "^18.2.0" } @@ -39624,18 +39651,33 @@ } }, "@mui/x-date-pickers": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.12.0.tgz", - "integrity": "sha512-lEfdPKdr2o2jUvEviYB/xaYaHJ3Gf9u/AvU3eCX6R0mzIpi1h1SsmrFOTcBIFkwz1iekUNIdZjfrkKmLX+n6dA==", - "requires": { - "@babel/runtime": "^7.22.11", - "@mui/utils": "^5.14.5", - "@types/react-transition-group": "^4.4.6", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.0.tgz", + "integrity": "sha512-y4UlkHQXiNRfb6FWQ/GWir0sZ+9kL+GEEZssG+XWP3KJ+d3lONRteusl4AJkYJBdIAOh+5LnMV9RAQKq9Sl7yw==", + "requires": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", "clsx": "^2.0.0", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "dependencies": { + "@mui/base": { + "version": "5.0.0-beta.23", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.23.tgz", + "integrity": "sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw==", + "requires": { + "@babel/runtime": "^7.23.2", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + } + }, "clsx": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", @@ -40969,9 +41011,9 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.9", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" }, "@types/q": { "version": "1.5.5", @@ -41015,18 +41057,10 @@ "@types/react": "^17" } }, - "@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", - "requires": { - "@types/react": "*" - } - }, "@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", + "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==", "requires": { "@types/react": "*" } diff --git a/moped-editor/package.json b/moped-editor/package.json index 60f29258a3..3ab6c8a12c 100644 --- a/moped-editor/package.json +++ b/moped-editor/package.json @@ -53,7 +53,7 @@ "@mui/material": "^5.14.7", "@mui/styles": "^5.14.7", "@mui/x-data-grid": "^6.12.0", - "@mui/x-date-pickers": "^6.12.0", + "@mui/x-date-pickers": "^6.18.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/user-event": "^14.4.3", "@turf/bbox": "^6.5.0", From 0ec6465f6da333086fc278d337d672fde6d3b389 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 7 Nov 2023 13:33:45 -0500 Subject: [PATCH 03/53] add more controlled form components --- .../components/forms/ControlledCheckbox.js | 28 +++++++++++++++ .../components/forms/ControlledDateField.js | 36 +++++++++++++++++++ .../src/components/forms/ControlledSwitch.js | 29 +++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 moped-editor/src/components/forms/ControlledCheckbox.js create mode 100644 moped-editor/src/components/forms/ControlledDateField.js create mode 100644 moped-editor/src/components/forms/ControlledSwitch.js diff --git a/moped-editor/src/components/forms/ControlledCheckbox.js b/moped-editor/src/components/forms/ControlledCheckbox.js new file mode 100644 index 0000000000..2c921b2833 --- /dev/null +++ b/moped-editor/src/components/forms/ControlledCheckbox.js @@ -0,0 +1,28 @@ +import React from "react"; +import Checkbox from "@mui/material/Checkbox"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import { Controller } from "react-hook-form"; + +const ControlledCheckbox = ({ name, control, label }) => { + return ( + { + return ( + field.onChange(e.target.checked)} + /> + } + /> + ); + }} + /> + ); +}; + +export default ControlledCheckbox; diff --git a/moped-editor/src/components/forms/ControlledDateField.js b/moped-editor/src/components/forms/ControlledDateField.js new file mode 100644 index 0000000000..dcff397edd --- /dev/null +++ b/moped-editor/src/components/forms/ControlledDateField.js @@ -0,0 +1,36 @@ +import React from "react"; +import { DatePicker } from "@mui/x-date-pickers"; +import { Controller } from "react-hook-form"; + +const ControlledDateField = ({ name, control, label }) => { + return ( + { + let value = field.value; + if (value !== null && !isNaN(new Date(field.value))) { + value = new Date(field.value); + } + return ( + + field.onChange(newValue ? newValue.toISOString() : newValue) + } + /> + ); + }} + /> + ); +}; + +export default ControlledDateField; diff --git a/moped-editor/src/components/forms/ControlledSwitch.js b/moped-editor/src/components/forms/ControlledSwitch.js new file mode 100644 index 0000000000..52d0dc64fa --- /dev/null +++ b/moped-editor/src/components/forms/ControlledSwitch.js @@ -0,0 +1,29 @@ +import Switch from "@mui/material/Switch"; +import { Controller } from "react-hook-form"; +import FormControlLabel from "@mui/material/FormControlLabel"; + +const ControlledSwitch = ({ name, control, label }) => { + return ( + { + return ( + field.onChange(e.target.checked)} + color="primary" + inputProps={{ "aria-label": "primary checkbox" }} + /> + } + /> + ); + }} + /> + ); +}; + +export default ControlledSwitch; From 78baeb3934e5a19aa6fe57780aa92e8f0d3b69ad Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 7 Nov 2023 13:34:46 -0500 Subject: [PATCH 04/53] make comparisonVariable optional --- .../projectView/ProjectComponents/utils/form.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectComponents/utils/form.js b/moped-editor/src/views/projects/projectView/ProjectComponents/utils/form.js index 661f120b62..23e974adc8 100644 --- a/moped-editor/src/views/projects/projectView/ProjectComponents/utils/form.js +++ b/moped-editor/src/views/projects/projectView/ProjectComponents/utils/form.js @@ -374,13 +374,17 @@ export const useResetDependentFieldOnParentFieldChange = ({ // when the parent value changes, compare to previous value // if it is different, reset the dependent field to its default useEffect(() => { + if (disable) return; // keep update from firing if the parent value hasn't changed - if ( - get(parentValue, comparisonVariable) === - get(previousParentFormValue, comparisonVariable) - ) + if (comparisonVariable) { + if ( + get(parentValue, comparisonVariable) === + get(previousParentFormValue, comparisonVariable) + ) + return; + } else if (parentValue === previousParentFormValue) { return; - if (disable) return; + } setValue(dependentFieldName, valueToSet); setPreviousParentValue(parentValue); From 00cebb31e14b31bdafd6287859b9a3aa86bea39a Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 7 Nov 2023 13:35:18 -0500 Subject: [PATCH 05/53] rework phase queries --- moped-editor/src/queries/project.js | 83 +++++++++++++++-------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/moped-editor/src/queries/project.js b/moped-editor/src/queries/project.js index a348cf43c0..f0ec954912 100644 --- a/moped-editor/src/queries/project.js +++ b/moped-editor/src/queries/project.js @@ -141,9 +141,7 @@ export const SUMMARY_QUERY = gql` is_deleted: { _eq: false } } ) { - project_geography( - where: { is_deleted: { _eq: false } } - ) { + project_geography(where: { is_deleted: { _eq: false } }) { council_districts } moped_proj_components(where: { is_deleted: { _eq: false } }) { @@ -290,7 +288,10 @@ export const TIMELINE_QUERY = gql` project_id phase_start phase_end + phase_id subphase_id + is_phase_start_confirmed + is_phase_end_confirmed moped_subphase { subphase_id subphase_name @@ -301,9 +302,7 @@ export const TIMELINE_QUERY = gql` phase_name } } - moped_milestones( - where: { is_deleted: { _eq: false } } - ) { + moped_milestones(where: { is_deleted: { _eq: false } }) { milestone_id milestone_name } @@ -326,11 +325,37 @@ export const TIMELINE_QUERY = gql` } `; +export const ADD_PROJECT_PHASE = gql` + mutation AddProjectPhase( + $objects: [moped_proj_phases_insert_input!]! + $current_phase_ids_to_clear: [Int!]! + ) { + insert_moped_proj_phases(objects: $objects) { + returning { + phase_id + phase_description + phase_start + phase_end + project_phase_id + is_current_phase + project_id + } + } + update_moped_proj_phases( + _set: { is_current_phase: false } + where: { project_phase_id: { _in: $current_phase_ids_to_clear } } + ) { + affected_rows + } + } +`; + // use this to update a single moped_proj_phase -export const UPDATE_PROJECT_PHASES_MUTATION = gql` +export const UPDATE_PROJECT_PHASE = gql` mutation ProjectPhasesMutation( $project_phase_id: Int! $object: moped_proj_phases_set_input! + $current_phase_ids_to_clear: [Int!]! ) { update_moped_proj_phases_by_pk( pk_columns: { project_phase_id: $project_phase_id } @@ -345,15 +370,21 @@ export const UPDATE_PROJECT_PHASES_MUTATION = gql` is_current_phase phase_description } + update_moped_proj_phases( + _set: { is_current_phase: false } + where: { project_phase_id: { _in: $current_phase_ids_to_clear } } + ) { + affected_rows + } } `; -// provide an array of project_phase_id's to set them not current -export const CLEAR_CURRENT_PROJECT_PHASES_MUTATION = gql` - mutation ClearCurrentProjectPhasePKs($ids: [Int!]!) { +// Delete a project phase **and** make it not current +export const DELETE_PROJECT_PHASE = gql` + mutation DeleteProjectPhase($project_phase_id: Int!) { update_moped_proj_phases( - _set: { is_current_phase: false } - where: { project_phase_id: { _in: $ids } } + _set: { is_deleted: true, is_current_phase: false } + where: { project_phase_id: { _eq: $project_phase_id } } ) { affected_rows } @@ -389,18 +420,6 @@ export const UPDATE_PROJECT_MILESTONES_MUTATION = gql` } `; -// Delete a project phase **and** make it not current -export const DELETE_PROJECT_PHASE = gql` - mutation DeleteProjectPhase($project_phase_id: Int!) { - update_moped_proj_phases( - _set: { is_deleted: true, is_current_phase: false } - where: { project_phase_id: { _eq: $project_phase_id } } - ) { - affected_rows - } - } -`; - export const DELETE_PROJECT_MILESTONE = gql` mutation DeleteProjectMilestone($project_milestone_id: Int!) { update_moped_proj_milestones( @@ -412,22 +431,6 @@ export const DELETE_PROJECT_MILESTONE = gql` } `; -export const ADD_PROJECT_PHASE = gql` - mutation AddProjectPhase($objects: [moped_proj_phases_insert_input!]!) { - insert_moped_proj_phases(objects: $objects) { - returning { - phase_id - phase_description - phase_start - phase_end - project_phase_id - is_current_phase - project_id - } - } - } -`; - export const ADD_PROJECT_MILESTONE = gql` mutation AddProjectMilestone( $objects: [moped_proj_milestones_insert_input!]! From 937f250201814c8f43e39bf8506c9d802b920d48 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 7 Nov 2023 13:36:20 -0500 Subject: [PATCH 06/53] use data grid + form for phases --- .../projects/projectView/ProjectPhase/form.js | 46 ++ .../projectView/ProjectPhaseDialog.js | 43 ++ .../projects/projectView/ProjectPhaseForm.js | 347 +++++++++++ .../projectView/ProjectPhaseToolbar.js | 26 + .../projects/projectView/ProjectPhases.js | 568 ++++++------------ .../projects/projectView/ProjectTimeline.js | 1 - 6 files changed, 653 insertions(+), 378 deletions(-) create mode 100644 moped-editor/src/views/projects/projectView/ProjectPhase/form.js create mode 100644 moped-editor/src/views/projects/projectView/ProjectPhaseDialog.js create mode 100644 moped-editor/src/views/projects/projectView/ProjectPhaseForm.js create mode 100644 moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/form.js b/moped-editor/src/views/projects/projectView/ProjectPhase/form.js new file mode 100644 index 0000000000..41912e1446 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/form.js @@ -0,0 +1,46 @@ +import * as yup from "yup"; + +export const phaseValidationSchema = yup.object().shape({ + phase_id: yup + .number("Phase is required") + .nullable() + .required("Phase is required"), + subphase_id: yup.number().nullable().optional(), + phase_start: yup.string().nullable().optional(), + phase_end: yup.string().nullable().optional(), + is_current_phase: yup.boolean(), + is_phase_start_confirmed: yup.boolean(), + is_phase_end_confirmed: yup.boolean(), + description: yup + .string() + .max(500, "Must be less than 500 characters") + .nullable(), + project_phase_id: yup.number().nullable().optional(), + project_id: yup.number().required(), +}); + +/** + * Only these fields will be included in the form submit payload + */ +const FORM_PAYLOAD_FIELDS = []; + +export const onSubmitActivity = ({ data, mutate, onSubmitCallback }) => { + const { id } = data; + + const payload = FORM_PAYLOAD_FIELDS.reduce((obj, key) => { + obj[key] = data[key]; + return obj; + }, {}); + + const variables = { object: payload }; + + if (id) { + variables.id = id; + } else { + variables.object.project_id = data.project_id; + } + + mutate({ + variables, + }).then(() => onSubmitCallback()); +}; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseDialog.js b/moped-editor/src/views/projects/projectView/ProjectPhaseDialog.js new file mode 100644 index 0000000000..c9947627b3 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseDialog.js @@ -0,0 +1,43 @@ +import IconButton from "@mui/material/IconButton"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import CloseIcon from "@mui/icons-material/Close"; +import ProjectPhaseForm from "./ProjectPhaseForm"; + +const ProjectPhaseDialog = ({ + onClose, + phase, + phases, + currentProjectPhaseIds, + onSubmitCallback, +}) => { + const titleText = phase.project_phase_id ? "Edit phase" : "Add phase"; + return ( + + + {titleText} + + + + + + + + + ); +}; + +export default ProjectPhaseDialog; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js new file mode 100644 index 0000000000..46cdc7d1a7 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -0,0 +1,347 @@ +import { useMemo, useEffect } from "react"; +import { useQuery, useMutation } from "@apollo/client"; +import { useForm } from "react-hook-form"; +import Alert from "@mui/material/Alert"; +import Button from "@mui/material/Button"; +import CheckCircle from "@mui/icons-material/CheckCircle"; +import CircularProgress from "@mui/material/CircularProgress"; +import FormControl from "@mui/material/FormControl"; +import FormHelperText from "@mui/material/FormHelperText"; +import Grid from "@mui/material/Grid"; +import { yupResolver } from "@hookform/resolvers/yup"; +import ControlledAutocomplete from "src/components/forms/ControlledAutocomplete"; +import ControlledDateField from "src/components/forms/ControlledDateField"; +import ControlledTextInput from "src/components/forms/ControlledTextInput"; +import ControlledCheckbox from "src/components/forms/ControlledCheckbox"; +import ControlledSwitch from "src/components/forms/ControlledSwitch"; +import { phaseValidationSchema } from "./ProjectPhase/form"; +import { useResetDependentFieldOnParentFieldChange } from "./ProjectComponents/utils/form"; +import { UPDATE_PROJECT_PHASE, ADD_PROJECT_PHASE } from "src/queries/project"; + +const DEFAULT_VALUES = { + project_phase_id: null, + phase_id: null, + subphase_id: null, + phase_start: null, + is_phase_start_confirmed: true, + is_phase_end_confirmed: false, + phase_end: null, + phase_description: null, + is_current_phase: false, + project_id: null, +}; + +const useDefaultValues = (phase) => + useMemo(() => { + // initialize form with default values plus the project id + let defaultValues = { ...DEFAULT_VALUES, project_id: phase.project_id }; + + if (phase.project_phase_id) { + // we are editing a phase: update all defaults from phase + Object.keys(DEFAULT_VALUES).forEach((key) => { + defaultValues[key] = phase[key]; + }); + } else { + // default the phase_start to midnight (local) on the current date + defaultValues.phase_start = new Date( + new Date().setHours(0, 0, 0, 0) + ).toISOString(); + } + return defaultValues; + }, [phase]); + +const useSubphases = (phase_id, phases) => + useMemo( + () => + phase_id + ? phases.find((p) => p.phase_id === phase_id)?.moped_subphases || [] + : [], + [phase_id, phases] + ); + +const useCurrentPhaseIdsToClear = ( + thisProjectPhaseId, + isCurrent, + currentProjectPhaseIds +) => { + if (!isCurrent) { + // nothing to do + return []; + } + // return all project phase IDs except the one we're editing + return currentProjectPhaseIds.filter( + (projectPhaseId) => projectPhaseId !== thisProjectPhaseId + ); +}; + +export const onSubmitPhase = ({ + data, + mutate, + currentPhaseIdsToClear, + onSubmitCallback, +}) => { + const { project_phase_id } = data; + delete data.project_phase_id; + + const variables = { current_phase_ids_to_clear: currentPhaseIdsToClear }; + + if (!project_phase_id) { + // inserting a new mutation - which has a slightly different + // variable shape bc the mutation supports multiple inserts + variables.objects = [data]; + } else { + variables.project_phase_id = project_phase_id; + variables.object = data; + } + + mutate({ + variables, + refetchQueries: ["ProjectSummary"], + }).then(() => onSubmitCallback()); +}; + +const ProjectPhaseForm = ({ + phase, + phases, + currentProjectPhaseIds, + onSubmitCallback, +}) => { + console.log("TODO: CHECK SEED DATA!"); + const isNewPhase = !phase.project_phase_id; + + const defaultValues = useDefaultValues(phase); + + /** initiatlize react hook form with validation */ + const { + handleSubmit, + control, + watch, + setValue, + formState: { isDirty, errors: formErrors }, + } = useForm({ + defaultValues, + resolver: yupResolver(phaseValidationSchema), + }); + + const subphases = useSubphases(watch("phase_id"), phases); + + useResetDependentFieldOnParentFieldChange({ + parentValue: watch("phase_id"), + dependentFieldName: "subphase_id", + valueToSet: null, + setValue, + }); + + const [mutate, mutationState] = useMutation( + isNewPhase ? ADD_PROJECT_PHASE : UPDATE_PROJECT_PHASE + ); + + const currentPhaseIdsToClear = useCurrentPhaseIdsToClear( + phase.project_phase_id, + watch("is_current_phase"), + currentProjectPhaseIds + ); + + const [phase_start, phase_end] = watch(["phase_start", "phase_end"]); + + useEffect(() => { + if (phase_start !== defaultValues.phase_start) { + // phase start has been edited + if ( + phase_start && + new Date(phase_start).getTime() < new Date().getTime() + ) { + setValue("is_phase_start_confirmed", true); + } + } + }, [phase_start, defaultValues, setValue]); + + useEffect(() => { + if (phase_end !== defaultValues.phase_end) { + // phase start has been edited + if (phase_end && new Date(phase_end).getTime() < new Date().getTime()) { + setValue("is_phase_end_confirmed", true); + } + } + }, [phase_end, defaultValues, setValue]); + + if (mutationState.error) { + console.error(mutationState.error); + return ( + + + + Something went wrong. Refresh the page to try again. + + + + ); + } + // else if (loadingStatuses || loadingTaskOrders) { + // return ( + // + // + // + // + // + // ); + // } + + return ( +
+ onSubmitPhase({ + data, + currentPhaseIdsToClear, + mutate, + onSubmitCallback, + }) + )} + autoComplete="off" + > + + + + + field.onChange(phase?.phase_id || null) + } + valueHandler={(value) => + value ? phases.find((p) => p.phase_id === value) : null + } + isOptionEqualToValue={(option, selectedOption) => + option?.phase_id === selectedOption?.phase_id + } + getOptionLabel={(option) => option?.phase_name || ""} + error={formErrors?.phase_id} + /> + {formErrors?.phase_id && ( + {formErrors.phase_id.message} + )} + + + + + 0} + onChangeHandler={(subphase, field) => + field.onChange(subphase?.subphase_id || null) + } + valueHandler={(value) => + value + ? subphases.find((s) => s.subphase_id === value) || null + : null + } + isOptionEqualToValue={(option, selectedOption) => + option?.subphase_id === selectedOption?.subphase_id + } + getOptionLabel={(option) => option?.subphase_name || ""} + error={formErrors?.subphase_id} + /> + {formErrors?.subphase_id && ( + {formErrors.subphase_id.message} + )} + + + + + + {formErrors?.phase_start && ( + {formErrors?.phase_start.message} + )} + + + + + + + + + + + {formErrors?.phase_end && ( + {formErrors.phase_end.message} + )} + + + + + + + + + + + {formErrors?.phase_description && ( + + {formErrors.phase_description.message} + + )} + + + + + + + + + + + + + +
+ ); +}; + +export default ProjectPhaseForm; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js b/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js new file mode 100644 index 0000000000..c697609922 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js @@ -0,0 +1,26 @@ +import AddCircleIcon from "@mui/icons-material/AddCircle"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import ButtonDropdownMenu from "src/components/ButtonDropdownMenu"; + +/** Custom toolbar title that resembles the material table titles we use */ +const ProjectPhaseToolbar = ({ addAction, setIsDialogOpen }) => ( + + + Phases + +
+ +
+
+); + +export default ProjectPhaseToolbar; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 17445690b0..5723abf3f4 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -1,411 +1,225 @@ -import React, { useState } from "react"; - -// Material -import { - CircularProgress, - Typography, - FormControl, - FormHelperText, - TextField, - Box, -} from "@mui/material"; -import Autocomplete from "@mui/material/Autocomplete"; +import { useCallback, useMemo, useState } from "react"; +import { useMutation } from "@apollo/client"; +import { CircularProgress, Box, IconButton } from "@mui/material"; +import { DataGrid } from "@mui/x-data-grid"; import { green } from "@mui/material/colors"; - import { EditOutlined as EditOutlinedIcon, DeleteOutline as DeleteOutlineIcon, CheckCircleOutline, } from "@mui/icons-material"; -import MaterialTable, { - MTableEditRow, - MTableAction, - MTableToolbar, -} from "@material-table/core"; -import typography from "../../../theme/typography"; - -// Query -import { - UPDATE_PROJECT_PHASES_MUTATION, - DELETE_PROJECT_PHASE, - ADD_PROJECT_PHASE, - CLEAR_CURRENT_PROJECT_PHASES_MUTATION, -} from "../../../queries/project"; -import { PAGING_DEFAULT_COUNT } from "../../../constants/tables"; -import { useMutation } from "@apollo/client"; -import { format } from "date-fns"; -import parseISO from "date-fns/parseISO"; - -import DateFieldEditComponent from "./DateFieldEditComponent"; -import ToggleEditComponent from "./ToggleEditComponent"; -import DropDownSelectComponent from "./DropDownSelectComponent"; -import ButtonDropdownMenu from "../../../components/ButtonDropdownMenu"; +import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; +import ProjectPhaseDialog from "./ProjectPhaseDialog"; +import { DELETE_PROJECT_PHASE } from "src/queries/project"; /** - * Identify any current moped_proj_phases - * @param {Int} newCurrentPhaseId - the ID of the phase that should be marked as current - optional - * @param {Array} existingProjPhases - array of this project's moped_proj_phases - * @return {Array} array of moped_proj_phases.project_phase_id primary keys + * Get the project_phase_ids of the project's current phase. Although only + * one phase should ever be current, we handle the possibilty that there + * are multiple current phases + * @param {Array} projphases - array of this project's moped_proj_phases + * @return {Array} of IDs of current project phases */ -const getCurrentPhaseIDs = (newCurrentPhaseId, existingProjPhases) => - existingProjPhases - .filter( - ({ is_current_phase, project_phase_id }) => - is_current_phase && project_phase_id !== newCurrentPhaseId - ) - .map(({ project_phase_id }) => project_phase_id); +const useCurrentProjectPhaseIDs = (projectPhases) => + useMemo( + () => + projectPhases + ? projectPhases + .filter(({ is_current_phase }) => is_current_phase) + .map(({ project_phase_id }) => project_phase_id) + : [], + [projectPhases] + ); -/** - * Replace all object properties which are empty strings "" with null - */ -const replaceEmptyStrings = (obj) => { - Object.keys(obj).forEach((key) => { - if (obj[key] === "") { - obj[key] = null; - } - }); -}; +const usePhaseNameLookup = (phases) => + useMemo( + () => + phases.reduce( + (obj, item) => + Object.assign(obj, { + [item.phase_id]: item.phase_name, + }), + {} + ), + [phases] + ); + +const useSubphaseNameLookup = (subphases) => + useMemo( + () => + subphases.reduce( + (obj, item) => + Object.assign(obj, { + [item.subphase_id]: item.subphase_name, + }), + {} + ), + [subphases] + ); + +/** Hook that provides memoized column settings */ +const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => + useMemo(() => { + return [ + { + headerName: "Phase", + field: "moped_phase", + minWidth: 200, + valueGetter: ({ row }) => row.moped_phase?.phase_name, + }, + { + headerName: "Subphase", + field: "moped_subphase", + minWidth: 200, + valueGetter: ({ row }) => row.moped_subphase?.subphase_name, + }, + { + headerName: "Start", + field: "phase_start", + valueGetter: ({ row }) => + row.phase_start ? new Date(row.phase_start).toLocaleDateString() : "", + minWidth: 150, + }, + { + headerName: "End", + field: "phase_end", + valueGetter: ({ row }) => + row.phase_end ? new Date(row.phase_end).toLocaleDateString() : "", + minWidth: 150, + }, + { + headerName: "Description", + field: "phase_description", + minWidth: 300, + }, + { + headerName: "Current", + field: "is_current_phase", + minWidth: 150, + renderCell: ({ row }) => + row.is_current_phase ? ( + + + Yes + + ) : ( + "" + ), + }, + { + headerName: "", + field: "_edit", + hideable: false, + filterable: false, + sortable: false, + renderCell: ({ row }) => { + return deleteInProgress ? ( + + ) : ( +
+ setEditPhase(row)} + > + + + + onDeletePhase({ project_phase_id: row.project_phase_id }) + } + > + + +
+ ); + }, + }, + ]; + }, [deleteInProgress, onDeletePhase, setEditPhase]); /** * ProjectPhases Component - renders Project Phase table * @return {JSX.Element} * @constructor */ -const ProjectPhases = ({ - projectId, - loading, - data, - refetch, - projectViewRefetch, -}) => { - const [isMutating, setIsMutating] = useState(false); - const [isDialogOpen, setIsDialogOpen] = useState(false); - - // Mutations - const [updateProjectPhase] = useMutation(UPDATE_PROJECT_PHASES_MUTATION); - const [clearCurrentProjectPhases] = useMutation( - CLEAR_CURRENT_PROJECT_PHASES_MUTATION +const ProjectPhases = ({ projectId, loading, data, refetch }) => { + const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false); + const [editPhase, setEditPhase] = useState(null); + + const [deletePhase, { loading: deleteInProgress }] = + useMutation(DELETE_PROJECT_PHASE); + + const onClickAddPhase = () => setEditPhase({ project_id: projectId }); + + const onDeletePhase = useCallback( + ({ project_phase_id }) => { + window.confirm("Are you sure you want to delete this phase?") && + deletePhase({ + variables: { project_phase_id }, + refetchQueries: ["ProjectSummary"], + }).then(() => { + refetch(); + }); + }, + [deletePhase, refetch] ); - const [deleteProjectPhase] = useMutation(DELETE_PROJECT_PHASE); - const [addProjectPhase] = useMutation(ADD_PROJECT_PHASE); - // Dropdown options - const phaseOptions = data?.moped_phases || []; - - // Hide Phase template dialog - const handleTemplateModalClose = () => { - setIsDialogOpen(false); - }; - - // If the query is loading or data object is undefined, - // stop here and just render the spinner. - if (loading || !data) return ; - - /** - * Set is_current_phase of all proj phases except currentPhaseId to false - * to ensure there is only one active phase - */ - const updateExistingPhases = async (projPhasesIdsToUpdate) => { - // Execute update mutation - // We bundle all phases into this single mutation so that it can be cleanly awaited - await clearCurrentProjectPhases({ - variables: { ids: projPhasesIdsToUpdate }, - }).catch((err) => { - console.error(err); - }); - }; + const columns = useColumns({ + setEditPhase, + deleteInProgress: false, + onDeletePhase, + }); - const phaseNameLookup = data.moped_phases.reduce( - (obj, item) => - Object.assign(obj, { - [item.phase_id]: item.phase_name, - }), - {} + const currentProjectPhaseIds = useCurrentProjectPhaseIDs( + data?.moped_proj_phases ); - const subphaseNameLookup = data.moped_subphases.reduce( - (obj, item) => - Object.assign(obj, { - [item.subphase_id]: item.subphase_name, - }), - {} - ); + const phaseNameLookup = usePhaseNameLookup(data?.moped_phases || []); - /** - * Column configuration for Phases table - */ - const phasesColumns = [ - { - title: "Phase", - field: "moped_phase", - validate: (row) => !!row.moped_phase?.phase_id, - render: (row) => row.moped_phase?.phase_name, - editComponent: (props) => ( - - phase.phase_name} - isOptionEqualToValue={(option, value) => - option.phase_id === value.phase_id - } - value={props.value || null} - onChange={(event, value) => { - return props.onChange(value); - }} - renderInput={(params) => ( - - )} - /> - Required - - ), - width: "25%", - }, - { - title: "Subphase", - field: "subphase_id", - render: (rowData) => rowData.moped_subphase?.subphase_name, - editComponent: (props) => ( - - ), - width: "20%", - }, - { - title: "Description", - field: "phase_description", - width: "25%", - }, - { - title: "Start", - field: "phase_start", - render: (rowData) => - rowData.phase_start - ? format(parseISO(rowData.phase_start), "MM/dd/yyyy") - : undefined, - editComponent: (props) => , - width: "10%", - }, - { - title: "End", - field: "phase_end", - render: (rowData) => - rowData.phase_end - ? format(parseISO(rowData.phase_end), "MM/dd/yyyy") - : undefined, - editComponent: (props) => , - width: "10%", - }, - { - title: "Current", - field: "is_current_phase", - lookup: { true: "Yes", false: "No" }, - render: (rowData) => - rowData.is_current_phase ? ( - - - Yes - - ) : ( - "" - ), - editComponent: (props) => ( - - ), - width: "10%", - }, - ]; + const subphaseNameLookup = useSubphaseNameLookup(data?.moped_subphases || []); + + const onSubmitCallback = () => { + refetch().then(() => setEditPhase(null)); + }; return ( <> - ( - { - if (e.keyCode === 13) { - // Bypass default MaterialTable behavior of submitting the entire form when a user hits enter - // See https://github.com/mbrn/material-table/pull/2008#issuecomment-662529834 - } - }} - /> - ), - Action: (props) => { - // If isn't the add action - if ( - typeof props.action === typeof Function || - props.action.tooltip !== "Add" - ) { - return ; - } else { - return ( - - ); - } - }, - Toolbar: (props) => ( - // to have it align with table content -
- -
- ), - }} - editable={{ - onRowAdd: async (newData) => { - setIsMutating(true); - const { moped_phase, ...rest } = newData; - - const newPhasePayload = { - project_id: projectId, - phase_id: moped_phase.phase_id, - ...rest, - }; - - replaceEmptyStrings(newPhasePayload); - - // if necessary, updates existing phases in table to ensure only one is marked "current" - if (newPhasePayload.is_current_phase) { - const projPhasesIdsToUpdate = getCurrentPhaseIDs( - null, - data.moped_proj_phases - ); - if (projPhasesIdsToUpdate.length > 0) { - await updateExistingPhases(projPhasesIdsToUpdate); - } - } - - // Execute insert mutation, returns promise - await addProjectPhase({ - variables: { - objects: [newPhasePayload], - }, - }).catch((err) => { - console.error(err); - }); - // Refetch data - await refetch(); - if (!!projectViewRefetch) { - await projectViewRefetch(); - } - setIsMutating(false); - }, - onRowUpdate: async (newData, oldData) => { - setIsMutating(true); - const { - project_phase_id, - moped_phase, - moped_subphase, - __typename, - ...updatedPhasePayload - } = newData; - // extract phase_id from moped_phase object - updatedPhasePayload.phase_id = moped_phase.phase_id; - - // Remove extraneous fields given by MaterialTable that - // Hasura doesn't need - delete updatedPhasePayload.tableData; - - replaceEmptyStrings(updatedPhasePayload); - - // if necessary, updates existing phases in table to ensure only one is marked "current" - if (updatedPhasePayload.is_current_phase) { - const projPhasesIdsToUpdate = getCurrentPhaseIDs( - project_phase_id, - data.moped_proj_phases - ); - if (projPhasesIdsToUpdate.length > 0) { - await updateExistingPhases(projPhasesIdsToUpdate); - } - } - - // Execute update mutation - await updateProjectPhase({ - variables: { project_phase_id, object: updatedPhasePayload }, - }).catch((err) => { - console.error(err); - }); - // Refetch data - await refetch(); - - if (!!projectViewRefetch) { - await projectViewRefetch(); - } - setIsMutating(false); - }, - onRowDelete: async (oldData) => { - // Execute delete mutation - setIsMutating(true); - await deleteProjectPhase({ - variables: { - project_phase_id: oldData.project_phase_id, - }, - }).catch((err) => { - console.error(err); - }); - await refetch(); - - if (!!projectViewRefetch) { - await projectViewRefetch(); - } - setIsMutating(false); - }, + row.project_phase_id} + disableRowSelectionOnClick + getRowHeight={() => "auto"} + hideFooterPagination={true} + localeText={{ noRowsLabel: "No phases" }} + rows={data?.moped_proj_phases || []} + slots={{ + toolbar: ProjectPhaseToolbar, }} - title={ - - Phases - - } - options={{ - ...(data.moped_proj_phases.length < PAGING_DEFAULT_COUNT + 1 && { - paging: false, - }), - search: false, - rowStyle: { fontFamily: typography.fontFamily }, - actionsColumnIndex: -1, - addRowPosition: "first", - idSynonym: "project_phase_id", - }} - localization={{ - header: { - actions: "", - }, - body: { - emptyDataSourceMessage: ( - - No project phases to display - - ), + slotProps={{ + toolbar: { + addAction: onClickAddPhase, + setIsDialogOpen: setIsTemplateDialogOpen, }, }} /> + {editPhase && ( + setEditPhase(null)} + onSubmitCallback={onSubmitCallback} + phases={data?.moped_phases} + currentProjectPhaseIds={currentProjectPhaseIds} + projectId={projectId} + /> + )} setIsTemplateDialogOpen(false)} selectedPhases={data.moped_proj_phases} phaseNameLookup={phaseNameLookup} subphaseNameLookup={subphaseNameLookup} diff --git a/moped-editor/src/views/projects/projectView/ProjectTimeline.js b/moped-editor/src/views/projects/projectView/ProjectTimeline.js index b93fc20b9e..02ab8e51df 100644 --- a/moped-editor/src/views/projects/projectView/ProjectTimeline.js +++ b/moped-editor/src/views/projects/projectView/ProjectTimeline.js @@ -53,7 +53,6 @@ const ProjectTimeline = (props) => { loading={loading} data={data} refetch={refetch} - projectViewRefetch={projectViewRefetch} /> From 53f15c3fe2d2be2deb72cb5791ea53c00bf12b1b Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 16 Nov 2023 14:06:51 -0500 Subject: [PATCH 07/53] add substantial_completion_date to plv and agol view --- .../down.sql | 416 +++++++++++++++++ .../up.sql | 426 ++++++++++++++++++ .../down.sql | 0 .../up.sql | 208 +++++++++ .../views/component_arcgis_online_view.sql | 7 +- moped-database/views/project_list_view.sql | 13 +- 6 files changed, 1066 insertions(+), 4 deletions(-) create mode 100644 moped-database/migrations/1700159831602_substantial_completion_date/down.sql create mode 100644 moped-database/migrations/1700159831602_substantial_completion_date/up.sql create mode 100644 moped-database/migrations/1700160199724_substantial_completion_date_component_view/down.sql create mode 100644 moped-database/migrations/1700160199724_substantial_completion_date_component_view/up.sql diff --git a/moped-database/migrations/1700159831602_substantial_completion_date/down.sql b/moped-database/migrations/1700159831602_substantial_completion_date/down.sql new file mode 100644 index 0000000000..768f85c2a6 --- /dev/null +++ b/moped-database/migrations/1700159831602_substantial_completion_date/down.sql @@ -0,0 +1,416 @@ +-- revert to version 1698786868086_add_component__subtype_to_view +DROP VIEW public.project_list_view cascade; +CREATE OR REPLACE VIEW public.project_list_view +AS WITH project_person_list_lookup AS ( + SELECT + mpp.project_id, + string_agg(DISTINCT concat(mu.first_name, ' ', mu.last_name, ':', mpr.project_role_name), ','::text) AS project_team_members + FROM moped_proj_personnel mpp + JOIN moped_users mu ON mpp.user_id = mu.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE mpp.is_deleted = false + AND mppr.is_deleted = false + GROUP BY mpp.project_id + ), funding_sources_lookup AS ( + SELECT + mpf_1.project_id, + string_agg(mfs.funding_source_name, ', '::text) AS funding_source_name + FROM moped_proj_funding mpf_1 + LEFT JOIN moped_fund_sources mfs ON mpf_1.funding_source_id = mfs.funding_source_id + WHERE mpf_1.is_deleted = false + GROUP BY mpf_1.project_id + ), project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types mpt + LEFT JOIN moped_types mt ON mpt.project_type_id = mt.type_id AND mpt.is_deleted = false + GROUP BY mpt.project_id + ), child_project_lookup AS ( + SELECT jsonb_agg(children.project_id) AS children_project_ids, + children.parent_project_id AS parent_id + FROM moped_project AS children + JOIN moped_project AS parent ON (parent.project_id = children.parent_project_id) + WHERE children.is_deleted = false + GROUP BY parent_id + ), work_activities AS ( + SELECT + project_id, + string_agg(task_order_objects.task_order_object ->> 'display_name'::text, + ', '::text) AS task_order_names, + string_agg(task_order_objects.task_order_object ->> 'task_order'::text, + ', '::text) AS task_order_names_short, + jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + string_agg(DISTINCT mpwa.contractor, + ', '::text) AS contractors, + string_agg(mpwa.contract_number, + ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 + AND mpwa.is_deleted = FALSE + GROUP BY + mpwa.project_id + ), moped_proj_components_subtypes AS ( + SELECT + mpc.project_id, + string_agg(DISTINCT mc.component_name_full, ', '::text) AS components + FROM moped_proj_components mpc + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + GROUP BY mpc.project_id + ) + SELECT + mp.project_id, + mp.project_name, + mp.project_description, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + mp.updated_at, + current_phase.phase_name as current_phase, + current_phase.phase_key as current_phase_key, + current_phase.phase_name_simple as current_phase_simple, + ppll.project_team_members, + me.entity_name AS project_sponsor, + mel.entity_name AS project_lead, + mpps.name AS public_process_status, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + proj_notes.project_note, + proj_notes.date_created as project_note_date_created, + work_activities.contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders, + (SELECT project_name + FROM moped_project + WHERE project_id = mp.parent_project_id + ) as parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partner, + (SELECT JSON_AGG(json_build_object('signal_id', feature_signals.signal_id, 'knack_id', feature_signals.knack_id, 'location_name', feature_signals.location_name, 'signal_type', feature_signals.signal_type, 'id', feature_signals.id)) + FROM moped_proj_components components + LEFT JOIN feature_signals + ON (feature_signals.component_id = components.project_component_id) + WHERE TRUE + AND components.is_deleted = false + AND components.project_id = mp.project_id + AND feature_signals.signal_id is not null + AND feature_signals.is_deleted = false + ) as project_feature, + fsl.funding_source_name, + ptl.type_name, + ( -- get the date of the construction phase with the earliest start date + SELECT min(phases.phase_start) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 9 -- phase_id 9 is construction + AND phases.is_deleted = false + ) AS construction_start_date, + ( -- get the date of the completion phase with the latest end date + SELECT max(phases.phase_end) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 11 -- phase_id 11 is complete + AND phases.is_deleted = false + ) AS completion_end_date, + ( -- get me a list of the inspectors for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Inspector'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_inspector, + ( -- get me a list of the designers for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Designer'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_designer, + ( -- get me all of the tags added to a project + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags ptags + JOIN moped_tags tags ON ptags.tag_id = tags.id + WHERE 1 = 1 + AND ptags.is_deleted = false + AND ptags.project_id = mp.project_id + GROUP BY ptags.project_id) AS project_tags, + concat(added_by_user.first_name, ' ', added_by_user.last_name) AS added_by, + mpcs.components + FROM moped_project mp + LEFT JOIN project_person_list_lookup ppll ON mp.project_id = ppll.project_id + LEFT JOIN funding_sources_lookup fsl ON fsl.project_id = mp.project_id + LEFT JOIN project_type_lookup ptl ON ptl.project_id = mp.project_id + LEFT JOIN moped_entity me ON me.entity_id = mp.project_sponsor + LEFT JOIN moped_entity mel ON mel.entity_id = mp.project_lead_id + LEFT JOIN moped_proj_partners mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false + LEFT JOIN moped_entity me2 ON mpp2.entity_id = me2.entity_id + LEFT JOIN work_activities on work_activities.project_id = mp.project_id + LEFT JOIN moped_users added_by_user ON mp.added_by = added_by_user.user_id + LEFT JOIN current_phase_view current_phase on mp.project_id = current_phase.project_id + LEFT JOIN moped_public_process_statuses mpps ON mpps.id = mp.public_process_status_id + LEFT JOIN child_project_lookup cpl on cpl.parent_id = mp.project_id + LEFT JOIN moped_proj_components_subtypes mpcs on mpcs.project_id = mp.project_id + LEFT JOIN LATERAL + ( + SELECT mpn.project_note, mpn.date_created + FROM moped_proj_notes mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.date_created DESC + LIMIT 1 + ) as proj_notes on true + WHERE + mp.is_deleted = false + GROUP BY + mp.project_id, + mp.project_name, + mp.project_description, + ppll.project_team_members, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + me.entity_name, + mel.entity_name, + mp.updated_at, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + current_phase.phase_name, + current_phase.phase_key, + current_phase.phase_name_simple, + ptl.type_name, + mpcs.components, + fsl.funding_source_name, + added_by_user.first_name, + added_by_user.last_name, + mpps.name, + cpl.children_project_ids, + proj_notes.project_note, + proj_notes.date_created, + work_activities.contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders; + +-- rebuild component view at version 1699553498334_increase_agol_point_radius +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.line_geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(geography) + ) + ):: json AS "geometry", + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(line_geography) + ) + ):: json AS "line_geometry", + STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM + ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_signals.geography, 7):: geometry + ) AS line_geography, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography :: geometry, + feature_street_segments.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_intersections.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_drawn_points.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography :: geometry, + feature_drawn_lines.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE + ) feature_union + GROUP BY + component_id + ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG( + DISTINCT council_district_id :: text, + ', ' + ) AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id + ) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id + ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id + ) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id + ) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); diff --git a/moped-database/migrations/1700159831602_substantial_completion_date/up.sql b/moped-database/migrations/1700159831602_substantial_completion_date/up.sql new file mode 100644 index 0000000000..4c8456d33e --- /dev/null +++ b/moped-database/migrations/1700159831602_substantial_completion_date/up.sql @@ -0,0 +1,426 @@ +DROP VIEW public.project_list_view cascade; +CREATE OR REPLACE VIEW public.project_list_view +AS WITH project_person_list_lookup AS ( + SELECT + mpp.project_id, + string_agg(DISTINCT concat(mu.first_name, ' ', mu.last_name, ':', mpr.project_role_name), ','::text) AS project_team_members + FROM moped_proj_personnel mpp + JOIN moped_users mu ON mpp.user_id = mu.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE mpp.is_deleted = false + AND mppr.is_deleted = false + GROUP BY mpp.project_id + ), funding_sources_lookup AS ( + SELECT + mpf_1.project_id, + string_agg(mfs.funding_source_name, ', '::text) AS funding_source_name + FROM moped_proj_funding mpf_1 + LEFT JOIN moped_fund_sources mfs ON mpf_1.funding_source_id = mfs.funding_source_id + WHERE mpf_1.is_deleted = false + GROUP BY mpf_1.project_id + ), project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types mpt + LEFT JOIN moped_types mt ON mpt.project_type_id = mt.type_id AND mpt.is_deleted = false + GROUP BY mpt.project_id + ), child_project_lookup AS ( + SELECT jsonb_agg(children.project_id) AS children_project_ids, + children.parent_project_id AS parent_id + FROM moped_project AS children + JOIN moped_project AS parent ON (parent.project_id = children.parent_project_id) + WHERE children.is_deleted = false + GROUP BY parent_id + ), work_activities AS ( + SELECT + project_id, + string_agg(task_order_objects.task_order_object ->> 'display_name'::text, + ', '::text) AS task_order_names, + string_agg(task_order_objects.task_order_object ->> 'task_order'::text, + ', '::text) AS task_order_names_short, + jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + string_agg(DISTINCT mpwa.contractor, + ', '::text) AS contractors, + string_agg(mpwa.contract_number, + ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 + AND mpwa.is_deleted = FALSE + GROUP BY + mpwa.project_id + ), moped_proj_components_subtypes AS ( + SELECT + mpc.project_id, + string_agg(DISTINCT mc.component_name_full, ', '::text) AS components + FROM moped_proj_components mpc + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + GROUP BY mpc.project_id + ) + SELECT + mp.project_id, + mp.project_name, + mp.project_description, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + mp.updated_at, + current_phase.phase_name as current_phase, + current_phase.phase_key as current_phase_key, + current_phase.phase_name_simple as current_phase_simple, + ppll.project_team_members, + me.entity_name AS project_sponsor, + mel.entity_name AS project_lead, + mpps.name AS public_process_status, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + proj_notes.project_note, + proj_notes.date_created as project_note_date_created, + work_activities.contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders, + (SELECT project_name + FROM moped_project + WHERE project_id = mp.parent_project_id + ) as parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partner, + (SELECT JSON_AGG(json_build_object('signal_id', feature_signals.signal_id, 'knack_id', feature_signals.knack_id, 'location_name', feature_signals.location_name, 'signal_type', feature_signals.signal_type, 'id', feature_signals.id)) + FROM moped_proj_components components + LEFT JOIN feature_signals + ON (feature_signals.component_id = components.project_component_id) + WHERE TRUE + AND components.is_deleted = false + AND components.project_id = mp.project_id + AND feature_signals.signal_id is not null + AND feature_signals.is_deleted = false + ) as project_feature, + fsl.funding_source_name, + ptl.type_name, + ( -- get the date of the construction phase with the earliest start date + SELECT min(phases.phase_start) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 9 -- phase_id 9 is construction + AND phases.is_deleted = false + ) AS construction_start_date, + ( -- get the date of the completion phase with the latest end date + SELECT max(phases.phase_end) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 11 -- phase_id 11 is complete + AND phases.is_deleted = false + ) AS completion_end_date, + ( -- get the earliest date of the complete or post construction phase + SELECT + min(least(phases.phase_end, phases.phase_start)) + FROM + moped_proj_phases phases + WHERE + TRUE + AND phases.project_id = mp.project_id + AND phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + ) AS substantial_completion_date, + ( -- get me a list of the inspectors for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Inspector'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_inspector, + ( -- get me a list of the designers for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Designer'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_designer, + ( -- get me all of the tags added to a project + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags ptags + JOIN moped_tags tags ON ptags.tag_id = tags.id + WHERE 1 = 1 + AND ptags.is_deleted = false + AND ptags.project_id = mp.project_id + GROUP BY ptags.project_id) AS project_tags, + concat(added_by_user.first_name, ' ', added_by_user.last_name) AS added_by, + mpcs.components + FROM moped_project mp + LEFT JOIN project_person_list_lookup ppll ON mp.project_id = ppll.project_id + LEFT JOIN funding_sources_lookup fsl ON fsl.project_id = mp.project_id + LEFT JOIN project_type_lookup ptl ON ptl.project_id = mp.project_id + LEFT JOIN moped_entity me ON me.entity_id = mp.project_sponsor + LEFT JOIN moped_entity mel ON mel.entity_id = mp.project_lead_id + LEFT JOIN moped_proj_partners mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false + LEFT JOIN moped_entity me2 ON mpp2.entity_id = me2.entity_id + LEFT JOIN work_activities on work_activities.project_id = mp.project_id + LEFT JOIN moped_users added_by_user ON mp.added_by = added_by_user.user_id + LEFT JOIN current_phase_view current_phase on mp.project_id = current_phase.project_id + LEFT JOIN moped_public_process_statuses mpps ON mpps.id = mp.public_process_status_id + LEFT JOIN child_project_lookup cpl on cpl.parent_id = mp.project_id + LEFT JOIN moped_proj_components_subtypes mpcs on mpcs.project_id = mp.project_id + LEFT JOIN LATERAL + ( + SELECT mpn.project_note, mpn.date_created + FROM moped_proj_notes mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.date_created DESC + LIMIT 1 + ) as proj_notes on true + WHERE + mp.is_deleted = false + GROUP BY + mp.project_id, + mp.project_name, + mp.project_description, + ppll.project_team_members, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + me.entity_name, + mel.entity_name, + mp.updated_at, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + current_phase.phase_name, + current_phase.phase_key, + current_phase.phase_name_simple, + ptl.type_name, + mpcs.components, + fsl.funding_source_name, + added_by_user.first_name, + added_by_user.last_name, + mpps.name, + cpl.children_project_ids, + proj_notes.project_note, + proj_notes.date_created, + work_activities.contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders; + +-- rebuild component view at version 1699553498334_increase_agol_point_radius +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.line_geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(geography) + ) + ):: json AS "geometry", + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(line_geography) + ) + ):: json AS "line_geometry", + STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM + ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_signals.geography, 7):: geometry + ) AS line_geography, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography :: geometry, + feature_street_segments.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_intersections.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_drawn_points.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography :: geometry, + feature_drawn_lines.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE + ) feature_union + GROUP BY + component_id + ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG( + DISTINCT council_district_id :: text, + ', ' + ) AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id + ) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id + ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id + ) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id + ) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); diff --git a/moped-database/migrations/1700160199724_substantial_completion_date_component_view/down.sql b/moped-database/migrations/1700160199724_substantial_completion_date_component_view/down.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/moped-database/migrations/1700160199724_substantial_completion_date_component_view/up.sql b/moped-database/migrations/1700160199724_substantial_completion_date_component_view/up.sql new file mode 100644 index 0000000000..e5ab25735e --- /dev/null +++ b/moped-database/migrations/1700160199724_substantial_completion_date_component_view/up.sql @@ -0,0 +1,208 @@ +DROP VIEW component_arcgis_online_view; +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.line_geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + COALESCE(mpc.completion_date, substantial_completion_date) as substantial_completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(geography) + ) + ):: json AS "geometry", + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(line_geography) + ) + ):: json AS "line_geometry", + STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM + ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_signals.geography, 7):: geometry + ) AS line_geography, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography :: geometry, + feature_street_segments.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_intersections.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_drawn_points.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography :: geometry, + feature_drawn_lines.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE + ) feature_union + GROUP BY + component_id + ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG( + DISTINCT council_district_id :: text, + ', ' + ) AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id + ) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id + ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id + ) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id + ) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); diff --git a/moped-database/views/component_arcgis_online_view.sql b/moped-database/views/component_arcgis_online_view.sql index 951c4ebbf5..7459615a2e 100644 --- a/moped-database/views/component_arcgis_online_view.sql +++ b/moped-database/views/component_arcgis_online_view.sql @@ -1,5 +1,5 @@ --- current version: 1699553498334_increase_agol_point_radius -DROP VIEW IF EXISTS component_arcgis_online_view; +-- current version: 1700160199724_substantial_completion_date_component_view +DROP VIEW component_arcgis_online_view; CREATE OR REPLACE VIEW component_arcgis_online_view AS ( SELECT mpc.project_id, @@ -21,7 +21,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( mpc.is_deleted is_project_component_deleted, plv.is_deleted is_project_deleted, mpc.interim_project_component_id, - mpc.completion_date, + mpc.completion_date, + COALESCE(mpc.completion_date, substantial_completion_date) as substantial_completion_date, mpc.srts_id, mpc.location_description, plv.project_name, diff --git a/moped-database/views/project_list_view.sql b/moped-database/views/project_list_view.sql index 13add1a563..cff43a82f8 100644 --- a/moped-database/views/project_list_view.sql +++ b/moped-database/views/project_list_view.sql @@ -1,4 +1,4 @@ --- latest version 1698786868086_add_component__subtype_to_view +-- latest version 1700159831602_substantial_completion_date CREATE OR REPLACE VIEW public.project_list_view AS WITH project_person_list_lookup AS ( SELECT @@ -116,6 +116,17 @@ AS WITH project_person_list_lookup AS ( AND phases.phase_id = 11 -- phase_id 11 is complete AND phases.is_deleted = false ) AS completion_end_date, + ( -- get the earliest date of the complete or post construction phase + SELECT + min(least(phases.phase_end, phases.phase_start)) + FROM + moped_proj_phases phases + WHERE + TRUE + AND phases.project_id = mp.project_id + AND phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + ) AS substantial_completion_date, ( -- get me a list of the inspectors for this project SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg FROM moped_proj_personnel mpp From 7c1de3b51969dc3f851efa93dc0044ed2bc56921 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 20 Nov 2023 13:22:23 -0500 Subject: [PATCH 08/53] status updates in progress --- moped-editor/src/queries/project.js | 4 + .../projects/projectView/ProjectPhase/form.js | 1 + .../projects/projectView/ProjectPhaseForm.js | 74 +++++++++++++------ 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/moped-editor/src/queries/project.js b/moped-editor/src/queries/project.js index f0ec954912..c625e8270b 100644 --- a/moped-editor/src/queries/project.js +++ b/moped-editor/src/queries/project.js @@ -329,6 +329,7 @@ export const ADD_PROJECT_PHASE = gql` mutation AddProjectPhase( $objects: [moped_proj_phases_insert_input!]! $current_phase_ids_to_clear: [Int!]! + $project_note: moped_proj_notes_insert_input! ) { insert_moped_proj_phases(objects: $objects) { returning { @@ -341,6 +342,9 @@ export const ADD_PROJECT_PHASE = gql` project_id } } + insert_moped_proj_notes_one(object: $project_note) { + project_note_id + } update_moped_proj_phases( _set: { is_current_phase: false } where: { project_phase_id: { _in: $current_phase_ids_to_clear } } diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/form.js b/moped-editor/src/views/projects/projectView/ProjectPhase/form.js index 41912e1446..20db39cb1e 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/form.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/form.js @@ -17,6 +17,7 @@ export const phaseValidationSchema = yup.object().shape({ .nullable(), project_phase_id: yup.number().nullable().optional(), project_id: yup.number().required(), + status_update: yup.string().nullable().optional().max(5000), }); /** diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js index 46cdc7d1a7..0fb080b86e 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -29,6 +29,7 @@ const DEFAULT_VALUES = { phase_description: null, is_current_phase: false, project_id: null, + status_update: null, }; const useDefaultValues = (phase) => @@ -80,10 +81,26 @@ export const onSubmitPhase = ({ currentPhaseIdsToClear, onSubmitCallback, }) => { - const { project_phase_id } = data; + const { project_phase_id, status_update } = data; delete data.project_phase_id; + delete data.status_update; - const variables = { current_phase_ids_to_clear: currentPhaseIdsToClear }; + let project_note = null; + + if (status_update) { + project_note = { + project_id: data.project_id, + project_note: status_update, + added_by_user_id: 1, + project_note_type: 2, + phase_id: data.phase_id, + }; + } + + const variables = { + current_phase_ids_to_clear: currentPhaseIdsToClear, + project_note, + }; if (!project_phase_id) { // inserting a new mutation - which has a slightly different @@ -107,6 +124,7 @@ const ProjectPhaseForm = ({ onSubmitCallback, }) => { console.log("TODO: CHECK SEED DATA!"); + // throw `you need to use metadata preset to handle note added_by_user_id`? const isNewPhase = !phase.project_phase_id; const defaultValues = useDefaultValues(phase); @@ -125,6 +143,8 @@ const ProjectPhaseForm = ({ const subphases = useSubphases(watch("phase_id"), phases); + const isCurrentPhase = watch("is_current_phase"); + useResetDependentFieldOnParentFieldChange({ parentValue: watch("phase_id"), dependentFieldName: "subphase_id", @@ -138,7 +158,7 @@ const ProjectPhaseForm = ({ const currentPhaseIdsToClear = useCurrentPhaseIdsToClear( phase.project_phase_id, - watch("is_current_phase"), + isCurrentPhase, currentProjectPhaseIds ); @@ -177,15 +197,6 @@ const ProjectPhaseForm = ({ ); } - // else if (loadingStatuses || loadingTaskOrders) { - // return ( - // - // - // - // - // - // ); - // } return (
- - - - - + + + + + + {isCurrentPhase && isNewPhase && ( + + + + {formErrors?.status_update && ( + + {formErrors.status_update.message} + + )} + + + )} From 45d77fc85e7681f12444a88be5f288eb24e64dae Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 18 Dec 2023 10:41:58 -0500 Subject: [PATCH 09/53] add migra to remove phase confirm triggers --- .../down.sql | 0 .../1702674851259_remove_phase_confirm_trigger/up.sql | 3 +++ .../migrations/1702674851260_phase_timestamps/down.sql | 0 .../up.sql | 0 4 files changed, 3 insertions(+) rename moped-database/migrations/{1699298159676_phase_timestamps => 1702674851259_remove_phase_confirm_trigger}/down.sql (100%) create mode 100644 moped-database/migrations/1702674851259_remove_phase_confirm_trigger/up.sql create mode 100644 moped-database/migrations/1702674851260_phase_timestamps/down.sql rename moped-database/migrations/{1699298159676_phase_timestamps => 1702674851260_phase_timestamps}/up.sql (100%) diff --git a/moped-database/migrations/1699298159676_phase_timestamps/down.sql b/moped-database/migrations/1702674851259_remove_phase_confirm_trigger/down.sql similarity index 100% rename from moped-database/migrations/1699298159676_phase_timestamps/down.sql rename to moped-database/migrations/1702674851259_remove_phase_confirm_trigger/down.sql diff --git a/moped-database/migrations/1702674851259_remove_phase_confirm_trigger/up.sql b/moped-database/migrations/1702674851259_remove_phase_confirm_trigger/up.sql new file mode 100644 index 0000000000..2be33490d0 --- /dev/null +++ b/moped-database/migrations/1702674851259_remove_phase_confirm_trigger/up.sql @@ -0,0 +1,3 @@ +DROP TRIGGER set_moped_proj_phases_confirmed_dates_trigger ON public.moped_proj_phases; + +DROP FUNCTION moped_proj_phases_confirmed_dates; diff --git a/moped-database/migrations/1702674851260_phase_timestamps/down.sql b/moped-database/migrations/1702674851260_phase_timestamps/down.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/moped-database/migrations/1699298159676_phase_timestamps/up.sql b/moped-database/migrations/1702674851260_phase_timestamps/up.sql similarity index 100% rename from moped-database/migrations/1699298159676_phase_timestamps/up.sql rename to moped-database/migrations/1702674851260_phase_timestamps/up.sql From efae14eb52faa9345bd3f7d033e629a180946f94 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 18 Dec 2023 10:43:22 -0500 Subject: [PATCH 10/53] remove status update, mostly --- moped-editor/src/queries/project.js | 4 -- .../projects/projectView/ProjectPhaseForm.js | 55 +++++++++++-------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/moped-editor/src/queries/project.js b/moped-editor/src/queries/project.js index c625e8270b..f0ec954912 100644 --- a/moped-editor/src/queries/project.js +++ b/moped-editor/src/queries/project.js @@ -329,7 +329,6 @@ export const ADD_PROJECT_PHASE = gql` mutation AddProjectPhase( $objects: [moped_proj_phases_insert_input!]! $current_phase_ids_to_clear: [Int!]! - $project_note: moped_proj_notes_insert_input! ) { insert_moped_proj_phases(objects: $objects) { returning { @@ -342,9 +341,6 @@ export const ADD_PROJECT_PHASE = gql` project_id } } - insert_moped_proj_notes_one(object: $project_note) { - project_note_id - } update_moped_proj_phases( _set: { is_current_phase: false } where: { project_phase_id: { _in: $current_phase_ids_to_clear } } diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js index 0fb080b86e..ff847b0c1e 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -1,10 +1,11 @@ import { useMemo, useEffect } from "react"; -import { useQuery, useMutation } from "@apollo/client"; +import { useMutation } from "@apollo/client"; import { useForm } from "react-hook-form"; import Alert from "@mui/material/Alert"; import Button from "@mui/material/Button"; import CheckCircle from "@mui/icons-material/CheckCircle"; import CircularProgress from "@mui/material/CircularProgress"; +// import Divider from "@mui/material/Divider"; import FormControl from "@mui/material/FormControl"; import FormHelperText from "@mui/material/FormHelperText"; import Grid from "@mui/material/Grid"; @@ -178,7 +179,7 @@ const ProjectPhaseForm = ({ useEffect(() => { if (phase_end !== defaultValues.phase_end) { - // phase start has been edited + // phase end has been edited if (phase_end && new Date(phase_end).getTime() < new Date().getTime()) { setValue("is_phase_end_confirmed", true); } @@ -326,6 +327,12 @@ const ProjectPhaseForm = ({ + {/* */} + - {isCurrentPhase && isNewPhase && ( - - - - {formErrors?.status_update && ( - - {formErrors.status_update.message} - - )} - - - )} + {/* + + + + + + + {formErrors?.status_update && ( + + {formErrors.status_update.message} + + )} + + */} From 2aa6f06f108b0d35ac0ce2366b572d70e982d98f Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 18 Dec 2023 10:43:56 -0500 Subject: [PATCH 11/53] use Mon Dec 18 10:43:56 EST 2023 column type --- .../projects/projectView/ProjectPhases.js | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 5723abf3f4..79134ba372 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -2,11 +2,12 @@ import { useCallback, useMemo, useState } from "react"; import { useMutation } from "@apollo/client"; import { CircularProgress, Box, IconButton } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; -import { green } from "@mui/material/colors"; +import { grey, green } from "@mui/material/colors"; import { EditOutlined as EditOutlinedIcon, DeleteOutline as DeleteOutlineIcon, CheckCircleOutline, + HelpOutline, } from "@mui/icons-material"; import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; @@ -76,15 +77,47 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => { headerName: "Start", field: "phase_start", + type: "date", + /** valueGetter is used by the date sort function inherently used by the `date` type column */ valueGetter: ({ row }) => - row.phase_start ? new Date(row.phase_start).toLocaleDateString() : "", + row.phase_start ? new Date(row.phase_start) : null, + /** the renderCell function controls the react node rendered for this cell */ + renderCell: ({ row }) => { + const strToRender = row.phase_start + ? new Date(row.phase_start).toLocaleDateString() + : ""; + return ( + + {strToRender} + {!row.is_phase_start_confirmed && ( + + )} + + ); + }, minWidth: 150, }, { headerName: "End", field: "phase_end", + type: "date", + /** valueGetter is used by the date sort function inherently used by the `date` type column */ valueGetter: ({ row }) => - row.phase_end ? new Date(row.phase_end).toLocaleDateString() : "", + row.phase_end ? new Date(row.phase_end) : null, + /** the renderCell function controls the react node rendered for this cell */ + renderCell: ({ row }) => { + const strToRender = row.phase_end + ? new Date(row.phase_end).toLocaleDateString() + : ""; + return ( + + {strToRender} + {!row.is_phase_end_confirmed && ( + + )} + + ); + }, minWidth: 150, }, { @@ -100,7 +133,6 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => row.is_current_phase ? ( - Yes ) : ( "" From d8cececa8f64bfab9a866b9dc62f0e102ccbf85f Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 18 Dec 2023 15:16:10 -0500 Subject: [PATCH 12/53] add popover and italic styles and request feedback in slack --- .../projects/projectView/ProjectPhases.js | 81 ++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 79134ba372..2a8e0bf2be 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -1,6 +1,12 @@ import { useCallback, useMemo, useState } from "react"; import { useMutation } from "@apollo/client"; -import { CircularProgress, Box, IconButton } from "@mui/material"; +import { + CircularProgress, + Box, + IconButton, + Popover, + Typography, +} from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; import { grey, green } from "@mui/material/colors"; import { @@ -8,6 +14,7 @@ import { DeleteOutline as DeleteOutlineIcon, CheckCircleOutline, HelpOutline, + ChildFriendly, } from "@mui/icons-material"; import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; @@ -58,6 +65,50 @@ const useSubphaseNameLookup = (subphases) => [subphases] ); +const John = ({ children, isEnabled }) => { + const [anchorEl, setAnchorEl] = useState(null); + + const handlePopoverOpen = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handlePopoverClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + return ( +
+ {children} + + Estimated date + +
+ ); +}; + /** Hook that provides memoized column settings */ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => useMemo(() => { @@ -86,13 +137,24 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => const strToRender = row.phase_start ? new Date(row.phase_start).toLocaleDateString() : ""; + const showTentativeIcon = + !row.is_phase_start_confirmed && strToRender; return ( - - {strToRender} - {!row.is_phase_start_confirmed && ( - - )} - + + + + {`${strToRender}${showTentativeIcon ? "*" : ""}`} + + {/* {showTentativeIcon && ( + + )} */} + + ); }, minWidth: 150, @@ -109,10 +171,11 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => const strToRender = row.phase_end ? new Date(row.phase_end).toLocaleDateString() : ""; + const showTentativeIcon = !row.is_phase_end_confirmed && strToRender; return ( - + {strToRender} - {!row.is_phase_end_confirmed && ( + {!row.is_phase_end_confirmed && showTentativeIcon && ( )} From 2940064d53be3c5de32431e02ce10b7c22a194e4 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 8 Jan 2024 14:41:36 -0500 Subject: [PATCH 13/53] use confirmed dates in substantial completion date --- .../up.sql | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/moped-database/migrations/1700159831602_substantial_completion_date/up.sql b/moped-database/migrations/1700159831602_substantial_completion_date/up.sql index 4c8456d33e..3a6a9f9cdd 100644 --- a/moped-database/migrations/1700159831602_substantial_completion_date/up.sql +++ b/moped-database/migrations/1700159831602_substantial_completion_date/up.sql @@ -41,7 +41,7 @@ AS WITH project_person_list_lookup AS ( string_agg(task_order_objects.task_order_object ->> 'task_order'::text, ', '::text) AS task_order_names_short, jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, - string_agg(DISTINCT mpwa.contractor, + string_agg(DISTINCT mpwa.workgroup_contractor, ', '::text) AS contractors, string_agg(mpwa.contract_number, ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa @@ -116,17 +116,37 @@ AS WITH project_person_list_lookup AS ( AND phases.phase_id = 11 -- phase_id 11 is complete AND phases.is_deleted = false ) AS completion_end_date, - ( -- get the earliest date of the complete or post construction phase - SELECT - min(least(phases.phase_end, phases.phase_start)) - FROM - moped_proj_phases phases - WHERE - TRUE - AND phases.project_id = mp.project_id - AND phase_name_simple = 'Complete' - AND phases.is_deleted = FALSE - ) AS substantial_completion_date, + ( + SELECT + min(min_confirmed_date) + FROM ( + -- earliest phase start matching criteria + SELECT + min(phases.phase_start) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_start IS NOT NULL + AND phases.is_phase_start_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + UNION ALL + -- earliest phase end matching criteria + SELECT + min(phases.phase_end) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_end IS NOT NULL + AND phases.is_phase_end_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE) min_confirmed_dates) AS substantial_completion_date, ( -- get me a list of the inspectors for this project SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg FROM moped_proj_personnel mpp From 4643bfafdebd6c577eb9227828da7b66a07ff593 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 8 Jan 2024 15:23:53 -0500 Subject: [PATCH 14/53] update migration timestamps to now --- .../down.sql | 0 .../up.sql | 0 .../down.sql | 0 .../up.sql | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename moped-database/migrations/{1700159831602_substantial_completion_date => 1704744986000_substantial_completion_date}/down.sql (100%) rename moped-database/migrations/{1700159831602_substantial_completion_date => 1704744986000_substantial_completion_date}/up.sql (100%) rename moped-database/migrations/{1700160199724_substantial_completion_date_component_view => 1704744986001_substantial_completion_date_component_view}/down.sql (100%) rename moped-database/migrations/{1700160199724_substantial_completion_date_component_view => 1704744986001_substantial_completion_date_component_view}/up.sql (100%) diff --git a/moped-database/migrations/1700159831602_substantial_completion_date/down.sql b/moped-database/migrations/1704744986000_substantial_completion_date/down.sql similarity index 100% rename from moped-database/migrations/1700159831602_substantial_completion_date/down.sql rename to moped-database/migrations/1704744986000_substantial_completion_date/down.sql diff --git a/moped-database/migrations/1700159831602_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql similarity index 100% rename from moped-database/migrations/1700159831602_substantial_completion_date/up.sql rename to moped-database/migrations/1704744986000_substantial_completion_date/up.sql diff --git a/moped-database/migrations/1700160199724_substantial_completion_date_component_view/down.sql b/moped-database/migrations/1704744986001_substantial_completion_date_component_view/down.sql similarity index 100% rename from moped-database/migrations/1700160199724_substantial_completion_date_component_view/down.sql rename to moped-database/migrations/1704744986001_substantial_completion_date_component_view/down.sql diff --git a/moped-database/migrations/1700160199724_substantial_completion_date_component_view/up.sql b/moped-database/migrations/1704744986001_substantial_completion_date_component_view/up.sql similarity index 100% rename from moped-database/migrations/1700160199724_substantial_completion_date_component_view/up.sql rename to moped-database/migrations/1704744986001_substantial_completion_date_component_view/up.sql From 420c4d2f0aaa20653ea5429716e661a5274b0a70 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 8 Jan 2024 15:30:04 -0500 Subject: [PATCH 15/53] linting --- .../up.sql | 64 ++++++++++--------- moped-database/views/project_list_view.sql | 64 ++++++++++--------- 2 files changed, 66 insertions(+), 62 deletions(-) diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql index 3a6a9f9cdd..5140172359 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql @@ -116,37 +116,39 @@ AS WITH project_person_list_lookup AS ( AND phases.phase_id = 11 -- phase_id 11 is complete AND phases.is_deleted = false ) AS completion_end_date, - ( - SELECT - min(min_confirmed_date) - FROM ( - -- earliest phase start matching criteria - SELECT - min(phases.phase_start) AS min_confirmed_date - FROM - moped_proj_phases phases - LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id - WHERE - TRUE - AND phases.phase_start IS NOT NULL - AND phases.is_phase_start_confirmed = TRUE - AND phases.project_id = mp.project_id - AND moped_phases.phase_name_simple = 'Complete' - AND phases.is_deleted = FALSE - UNION ALL - -- earliest phase end matching criteria - SELECT - min(phases.phase_end) AS min_confirmed_date - FROM - moped_proj_phases phases - LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id - WHERE - TRUE - AND phases.phase_end IS NOT NULL - AND phases.is_phase_end_confirmed = TRUE - AND phases.project_id = mp.project_id - AND moped_phases.phase_name_simple = 'Complete' - AND phases.is_deleted = FALSE) min_confirmed_dates) AS substantial_completion_date, + ( -- get the earliest confirmed phase_start or phase_end with a simple phase of 'Complete' + SELECT + min(min_confirmed_date) + FROM ( + -- earliest confirmed phase start + SELECT + min(phases.phase_start) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_start IS NOT NULL + AND phases.is_phase_start_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + UNION ALL + -- earliest confirmed phase end + SELECT + min(phases.phase_end) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_end IS NOT NULL + AND phases.is_phase_end_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + ) min_confirmed_dates + ) AS substantial_completion_date, ( -- get me a list of the inspectors for this project SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg FROM moped_proj_personnel mpp diff --git a/moped-database/views/project_list_view.sql b/moped-database/views/project_list_view.sql index 8ad95345b0..4f67cb86c9 100644 --- a/moped-database/views/project_list_view.sql +++ b/moped-database/views/project_list_view.sql @@ -119,37 +119,39 @@ AS WITH project_person_list_lookup AS ( AND phases.phase_id = 11 -- phase_id 11 is complete AND phases.is_deleted = false ) AS completion_end_date, - ( - SELECT - min(min_confirmed_date) - FROM ( - -- earliest phase start matching criteria - SELECT - min(phases.phase_start) AS min_confirmed_date - FROM - moped_proj_phases phases - LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id - WHERE - TRUE - AND phases.phase_start IS NOT NULL - AND phases.is_phase_start_confirmed = TRUE - AND phases.project_id = mp.project_id - AND moped_phases.phase_name_simple = 'Complete' - AND phases.is_deleted = FALSE - UNION ALL - -- earliest phase end matching criteria - SELECT - min(phases.phase_end) AS min_confirmed_date - FROM - moped_proj_phases phases - LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id - WHERE - TRUE - AND phases.phase_end IS NOT NULL - AND phases.is_phase_end_confirmed = TRUE - AND phases.project_id = mp.project_id - AND moped_phases.phase_name_simple = 'Complete' - AND phases.is_deleted = FALSE) min_confirmed_dates) AS substantial_completion_date, + ( -- get the earliest confirmed phase_start or phase_end with a simple phase of 'Complete' + SELECT + min(min_confirmed_date) + FROM ( + -- earliest confirmed phase start + SELECT + min(phases.phase_start) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_start IS NOT NULL + AND phases.is_phase_start_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + UNION ALL + -- earliest confirmed phase end + SELECT + min(phases.phase_end) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_end IS NOT NULL + AND phases.is_phase_end_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + ) min_confirmed_dates + ) AS substantial_completion_date, ( -- get me a list of the inspectors for this project SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg FROM moped_proj_personnel mpp From c99255f35e77c27eaf3941b132571e11e27eae15 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 8 Jan 2024 17:21:35 -0500 Subject: [PATCH 16/53] combine migrations --- .../down.sql | 226 +++++++++++++++++- .../up.sql | 5 +- .../down.sql | 0 .../up.sql | 208 ---------------- .../views/component_arcgis_online_view.sql | 2 +- 5 files changed, 221 insertions(+), 220 deletions(-) delete mode 100644 moped-database/migrations/1704744986001_substantial_completion_date_component_view/down.sql delete mode 100644 moped-database/migrations/1704744986001_substantial_completion_date_component_view/up.sql diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/down.sql b/moped-database/migrations/1704744986000_substantial_completion_date/down.sql index 768f85c2a6..67a4428ef4 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/down.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/down.sql @@ -1,5 +1,6 @@ --- revert to version 1698786868086_add_component__subtype_to_view +--revert to version 1700515730257_fix_delete_component_bug DROP VIEW public.project_list_view cascade; + CREATE OR REPLACE VIEW public.project_list_view AS WITH project_person_list_lookup AS ( SELECT @@ -41,9 +42,9 @@ AS WITH project_person_list_lookup AS ( ', '::text) AS task_order_names, string_agg(task_order_objects.task_order_object ->> 'task_order'::text, ', '::text) AS task_order_names_short, - jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, - string_agg(DISTINCT mpwa.contractor, - ', '::text) AS contractors, + jsonb_agg(DISTINCT task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + string_agg(DISTINCT mpwa.workgroup_contractor, + ', '::text) AS workgroup_contractors, string_agg(mpwa.contract_number, ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 @@ -55,7 +56,8 @@ AS WITH project_person_list_lookup AS ( mpc.project_id, string_agg(DISTINCT mc.component_name_full, ', '::text) AS components FROM moped_proj_components mpc - LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + WHERE mpc.is_deleted = FALSE GROUP BY mpc.project_id ) SELECT @@ -78,7 +80,7 @@ AS WITH project_person_list_lookup AS ( mp.knack_project_id, proj_notes.project_note, proj_notes.date_created as project_note_date_created, - work_activities.contractors, + work_activities.workgroup_contractors, work_activities.contract_numbers, work_activities.task_order_names, work_activities.task_order_names_short, @@ -201,13 +203,12 @@ AS WITH project_person_list_lookup AS ( cpl.children_project_ids, proj_notes.project_note, proj_notes.date_created, - work_activities.contractors, + work_activities.workgroup_contractors, work_activities.contract_numbers, work_activities.task_order_names, work_activities.task_order_names_short, work_activities.task_orders; --- rebuild component view at version 1699553498334_increase_agol_point_radius CREATE OR REPLACE VIEW component_arcgis_online_view AS ( SELECT mpc.project_id, @@ -260,7 +261,214 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( plv.project_inspector, plv.project_designer, plv.project_tags, - plv.contractors, + plv.workgroup_contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(geography) + ) + ):: json AS "geometry", + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(line_geography) + ) + ):: json AS "line_geometry", + STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM + ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_signals.geography, 7):: geometry + ) AS line_geography, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography :: geometry, + feature_street_segments.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_intersections.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_drawn_points.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography :: geometry, + feature_drawn_lines.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE + ) feature_union + GROUP BY + component_id + ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG( + DISTINCT council_district_id :: text, + ', ' + ) AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id + ) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id + ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id + ) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id + ) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); + +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.line_geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.workgroup_contractors, plv.contract_numbers, plv.knack_project_id as knack_data_tracker_project_record_id, 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql index 5140172359..b0a4ac25a4 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql @@ -239,7 +239,7 @@ AS WITH project_person_list_lookup AS ( work_activities.task_order_names_short, work_activities.task_orders; --- rebuild component view at version 1699553498334_increase_agol_point_radius +-- add substantial_completion_date to agol view CREATE OR REPLACE VIEW component_arcgis_online_view AS ( SELECT mpc.project_id, @@ -261,7 +261,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( mpc.is_deleted is_project_component_deleted, plv.is_deleted is_project_deleted, mpc.interim_project_component_id, - mpc.completion_date, + mpc.completion_date, + COALESCE(mpc.completion_date, substantial_completion_date) as substantial_completion_date, mpc.srts_id, mpc.location_description, plv.project_name, diff --git a/moped-database/migrations/1704744986001_substantial_completion_date_component_view/down.sql b/moped-database/migrations/1704744986001_substantial_completion_date_component_view/down.sql deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/moped-database/migrations/1704744986001_substantial_completion_date_component_view/up.sql b/moped-database/migrations/1704744986001_substantial_completion_date_component_view/up.sql deleted file mode 100644 index e5ab25735e..0000000000 --- a/moped-database/migrations/1704744986001_substantial_completion_date_component_view/up.sql +++ /dev/null @@ -1,208 +0,0 @@ -DROP VIEW component_arcgis_online_view; -CREATE OR REPLACE VIEW component_arcgis_online_view AS ( - SELECT - mpc.project_id, - comp_geography.project_component_id, - comp_geography.feature_ids, - mpc.component_id, - comp_geography.geometry, - comp_geography.line_geometry, - comp_geography.signal_ids, - council_districts.council_districts, - comp_geography.length_feet_total, - mc.component_name, - mc.component_subtype, - mc.component_name_full, - subcomponents.subcomponents, - work_types.work_types, - component_tags.component_tags, - mpc.description AS component_description, - mpc.is_deleted is_project_component_deleted, - plv.is_deleted is_project_deleted, - mpc.interim_project_component_id, - mpc.completion_date, - COALESCE(mpc.completion_date, substantial_completion_date) as substantial_completion_date, - mpc.srts_id, - mpc.location_description, - plv.project_name, - plv.project_description, - plv.ecapris_subproject_id, - plv.updated_at, - mpc.phase_id AS component_phase_id, - mph.phase_name AS component_phase_name, - mph.phase_name_simple as component_phase_name_simple, - current_phase.phase_id AS project_phase_id, - current_phase.phase_name AS project_phase_name, - current_phase.phase_name_simple AS project_phase_name_simple, - COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, - COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, - plv.project_team_members, - plv.project_sponsor, - plv.project_lead, - plv.public_process_status, - plv.interim_project_id, - plv.project_partner, - plv.task_order_names, - plv.funding_source_name, - plv.type_name, - plv.project_note, - plv.project_note_date_created, - plv.construction_start_date, - plv.completion_end_date, - plv.project_inspector, - plv.project_designer, - plv.project_tags, - plv.contractors, - plv.contract_numbers, - plv.knack_project_id as knack_data_tracker_project_record_id, - 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, - 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, - added_by - FROM - moped_proj_components mpc - LEFT JOIN ( - -- group feature properties by project component ID - SELECT - component_id AS project_component_id, - STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, - ST_AsGeoJSON( - ST_Union( - ARRAY_AGG(geography) - ) - ):: json AS "geometry", - ST_AsGeoJSON( - ST_Union( - ARRAY_AGG(line_geography) - ) - ):: json AS "line_geometry", - STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, - SUM(length_feet) as length_feet_total - FROM - ( - -- union all features - SELECT - id, - feature_signals.component_id, - feature_signals.geography :: geometry, - ST_ExteriorRing( - ST_Buffer(feature_signals.geography, 7):: geometry - ) AS line_geography, - feature_signals.signal_id, - NULL AS length_feet - FROM - feature_signals - WHERE - feature_signals.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_street_segments.component_id, - feature_street_segments.geography :: geometry, - feature_street_segments.geography :: geometry as line_geography, - NULL AS signal_id, - length_feet - FROM - feature_street_segments - WHERE - feature_street_segments.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_intersections.component_id, - feature_intersections.geography :: geometry, - ST_ExteriorRing( - ST_Buffer(feature_intersections.geography, 7):: geometry - ) AS line_geography, - NULL AS signal_id, - NULL AS length_feet - FROM - feature_intersections - WHERE - feature_intersections.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_drawn_points.component_id, - feature_drawn_points.geography :: geometry, - ST_ExteriorRing( - ST_Buffer(feature_drawn_points.geography, 7):: geometry - ) AS line_geography, - NULL AS signal_id, - NULL AS length_feet - FROM - feature_drawn_points - WHERE - feature_drawn_points.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_drawn_lines.component_id, - feature_drawn_lines.geography :: geometry, - feature_drawn_lines.geography :: geometry as line_geography, - NULL AS signal_id, - length_feet - FROM - feature_drawn_lines - WHERE - feature_drawn_lines.is_deleted = FALSE - ) feature_union - GROUP BY - component_id - ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group council districts by project component id - SELECT - component_id AS project_component_id, - STRING_AGG( - DISTINCT council_district_id :: text, - ', ' - ) AS council_districts - FROM - features_council_districts - LEFT JOIN features ON features.id = features_council_districts.feature_id - WHERE - features.is_deleted = FALSE - GROUP BY - component_id - ) council_districts ON council_districts.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group subcomponents by project component id - SELECT - project_component_id, - string_agg(ms.subcomponent_name, ', ') subcomponents - FROM - moped_proj_components_subcomponents mpcs - LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id - GROUP BY - project_component_id - ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group work types by project component id - SELECT - project_component_id, - string_agg(mwt.name, ', ') work_types - FROM - moped_proj_component_work_types mpcwt - LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id - GROUP BY - project_component_id - ) work_types ON work_types.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group project component tags by project component id - SELECT - project_component_id, - string_agg(mct.type || ' - ' || mct.name, ', ') component_tags - FROM - moped_proj_component_tags mpct - LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id - GROUP BY - project_component_id - ) component_tags ON component_tags.project_component_id = mpc.project_component_id - LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id - LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id - LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id - LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id -WHERE - mpc.is_deleted = FALSE - AND plv.is_deleted = FALSE -); diff --git a/moped-database/views/component_arcgis_online_view.sql b/moped-database/views/component_arcgis_online_view.sql index 79ee898fa6..7bf63f91cd 100644 --- a/moped-database/views/component_arcgis_online_view.sql +++ b/moped-database/views/component_arcgis_online_view.sql @@ -1,4 +1,4 @@ --- current version: 1704744986001_substantial_completion_date_component_view +-- current_version: 1704744986000_substantial_completion_date DROP VIEW component_arcgis_online_view; CREATE OR REPLACE VIEW component_arcgis_online_view AS ( From bd3334aa9c9cb5c9c503c471311ff02283bde992 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 8 Jan 2024 21:07:49 -0500 Subject: [PATCH 17/53] its workgroup_contractors? --- .../migrations/1704744986000_substantial_completion_date/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql index b0a4ac25a4..0d8eb70d71 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql @@ -293,7 +293,7 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( plv.project_inspector, plv.project_designer, plv.project_tags, - plv.contractors, + plv.workgroup_contractors, plv.contract_numbers, plv.knack_project_id as knack_data_tracker_project_record_id, 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, From e2dce18bf075a04db73fbe60ee787bab3eab0b3c Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 8 Jan 2024 21:11:53 -0500 Subject: [PATCH 18/53] sync migrations back up with reality --- .../up.sql | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql index 0d8eb70d71..77342b7ad7 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql @@ -1,4 +1,4 @@ -DROP VIEW public.project_list_view cascade; +DROP VIEW project_list_view CASCADE; CREATE OR REPLACE VIEW public.project_list_view AS WITH project_person_list_lookup AS ( SELECT @@ -40,9 +40,9 @@ AS WITH project_person_list_lookup AS ( ', '::text) AS task_order_names, string_agg(task_order_objects.task_order_object ->> 'task_order'::text, ', '::text) AS task_order_names_short, - jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + jsonb_agg(DISTINCT task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, string_agg(DISTINCT mpwa.workgroup_contractor, - ', '::text) AS contractors, + ', '::text) AS workgroup_contractors, string_agg(mpwa.contract_number, ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 @@ -53,8 +53,9 @@ AS WITH project_person_list_lookup AS ( SELECT mpc.project_id, string_agg(DISTINCT mc.component_name_full, ', '::text) AS components - FROM moped_proj_components mpc - LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + FROM moped_proj_components mpc + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + WHERE mpc.is_deleted = FALSE GROUP BY mpc.project_id ) SELECT @@ -77,7 +78,7 @@ AS WITH project_person_list_lookup AS ( mp.knack_project_id, proj_notes.project_note, proj_notes.date_created as project_note_date_created, - work_activities.contractors, + work_activities.workgroup_contractors, work_activities.contract_numbers, work_activities.task_order_names, work_activities.task_order_names_short, @@ -233,7 +234,7 @@ AS WITH project_person_list_lookup AS ( cpl.children_project_ids, proj_notes.project_note, proj_notes.date_created, - work_activities.contractors, + work_activities.workgroup_contractors, work_activities.contract_numbers, work_activities.task_order_names, work_activities.task_order_names_short, From 15059538e3f0f95bd500a9ad5edb16be8db52f0c Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 9 Jan 2024 13:06:09 -0500 Subject: [PATCH 19/53] remove unused imports --- .../src/views/projects/projectView/ProjectPhaseToolbar.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js b/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js index c697609922..fb8c221e54 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseToolbar.js @@ -1,10 +1,8 @@ -import AddCircleIcon from "@mui/icons-material/AddCircle"; -import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import ButtonDropdownMenu from "src/components/ButtonDropdownMenu"; -/** Custom toolbar title that resembles the material table titles we use */ +/** Custom toolbar title that resembles material table titles */ const ProjectPhaseToolbar = ({ addAction, setIsDialogOpen }) => ( From 17273b1c73fa1bf9744a1ce2def763a059cbb926 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 9 Jan 2024 13:10:47 -0500 Subject: [PATCH 20/53] use delete in progress state --- moped-editor/src/views/projects/projectView/ProjectPhases.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 2a8e0bf2be..fdf9756cd1 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -14,7 +14,6 @@ import { DeleteOutline as DeleteOutlineIcon, CheckCircleOutline, HelpOutline, - ChildFriendly, } from "@mui/icons-material"; import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; @@ -264,7 +263,7 @@ const ProjectPhases = ({ projectId, loading, data, refetch }) => { const columns = useColumns({ setEditPhase, - deleteInProgress: false, + deleteInProgress, onDeletePhase, }); From 2092ef8116a718c18a0574a9de16fca354e9575e Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 9 Jan 2024 13:11:01 -0500 Subject: [PATCH 21/53] remove unused prop --- .../src/views/projects/projectView/ProjectTimeline.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectTimeline.js b/moped-editor/src/views/projects/projectView/ProjectTimeline.js index 02ab8e51df..c2464f24b2 100644 --- a/moped-editor/src/views/projects/projectView/ProjectTimeline.js +++ b/moped-editor/src/views/projects/projectView/ProjectTimeline.js @@ -18,7 +18,7 @@ import ProjectMilestones from "./ProjectMilestones"; * @return {JSX.Element} * @constructor */ -const ProjectTimeline = (props) => { +const ProjectTimeline = () => { /** Params Hook * @type {integer} projectId * */ @@ -36,8 +36,6 @@ const ProjectTimeline = (props) => { fetchPolicy: "no-cache", }); - const projectViewRefetch = props.refetch; - // If the query is loading or data object is undefined, // stop here and just render the spinner. if (loading || !data) return ; From ccf79064723c55a237edf3f14f788fc9cb9d8714 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 9 Jan 2024 13:53:47 -0500 Subject: [PATCH 22/53] docstrings --- moped-editor/src/components/forms/ControlledCheckbox.js | 7 +++++++ moped-editor/src/components/forms/ControlledSwitch.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/moped-editor/src/components/forms/ControlledCheckbox.js b/moped-editor/src/components/forms/ControlledCheckbox.js index 2c921b2833..0d66b9bd6c 100644 --- a/moped-editor/src/components/forms/ControlledCheckbox.js +++ b/moped-editor/src/components/forms/ControlledCheckbox.js @@ -3,6 +3,13 @@ import Checkbox from "@mui/material/Checkbox"; import FormControlLabel from "@mui/material/FormControlLabel"; import { Controller } from "react-hook-form"; +/** + * A react-hook-form wrapper of the MUI Checkbox component + * @param {object} control - react-hook-form `control` object from useController - required + * @param {string} name - unique field name which be used in react-hook-form data object + * @param {string} label - the label to render next to the checkbox + * @return {JSX.Element} + */ const ControlledCheckbox = ({ name, control, label }) => { return ( { return ( Date: Tue, 9 Jan 2024 17:00:53 -0500 Subject: [PATCH 23/53] docstring --- moped-editor/src/components/forms/ControlledDateField.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/moped-editor/src/components/forms/ControlledDateField.js b/moped-editor/src/components/forms/ControlledDateField.js index dcff397edd..a42eb26c0d 100644 --- a/moped-editor/src/components/forms/ControlledDateField.js +++ b/moped-editor/src/components/forms/ControlledDateField.js @@ -2,6 +2,13 @@ import React from "react"; import { DatePicker } from "@mui/x-date-pickers"; import { Controller } from "react-hook-form"; +/** + * A react-hook-form wrapper of the MUI DatePicker component + * @param {object} control - react-hook-form `control` object from useController - required + * @param {string} name - unique field name which be used in react-hook-form data object + * @param {string} label - the label to render next to the checkbox + * @return {JSX.Element} + */ const ControlledDateField = ({ name, control, label }) => { return ( Date: Tue, 9 Jan 2024 17:01:20 -0500 Subject: [PATCH 24/53] wip moving stuff to helpers file --- .../projects/projectView/ProjectPhase/form.js | 47 ------ .../projectView/ProjectPhase/helpers.js | 121 +++++++++++++++ .../projects/projectView/ProjectPhaseForm.js | 140 ++---------------- .../projects/projectView/ProjectPhases.js | 29 ++-- 4 files changed, 153 insertions(+), 184 deletions(-) delete mode 100644 moped-editor/src/views/projects/projectView/ProjectPhase/form.js create mode 100644 moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/form.js b/moped-editor/src/views/projects/projectView/ProjectPhase/form.js deleted file mode 100644 index 20db39cb1e..0000000000 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/form.js +++ /dev/null @@ -1,47 +0,0 @@ -import * as yup from "yup"; - -export const phaseValidationSchema = yup.object().shape({ - phase_id: yup - .number("Phase is required") - .nullable() - .required("Phase is required"), - subphase_id: yup.number().nullable().optional(), - phase_start: yup.string().nullable().optional(), - phase_end: yup.string().nullable().optional(), - is_current_phase: yup.boolean(), - is_phase_start_confirmed: yup.boolean(), - is_phase_end_confirmed: yup.boolean(), - description: yup - .string() - .max(500, "Must be less than 500 characters") - .nullable(), - project_phase_id: yup.number().nullable().optional(), - project_id: yup.number().required(), - status_update: yup.string().nullable().optional().max(5000), -}); - -/** - * Only these fields will be included in the form submit payload - */ -const FORM_PAYLOAD_FIELDS = []; - -export const onSubmitActivity = ({ data, mutate, onSubmitCallback }) => { - const { id } = data; - - const payload = FORM_PAYLOAD_FIELDS.reduce((obj, key) => { - obj[key] = data[key]; - return obj; - }, {}); - - const variables = { object: payload }; - - if (id) { - variables.id = id; - } else { - variables.object.project_id = data.project_id; - } - - mutate({ - variables, - }).then(() => onSubmitCallback()); -}; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js new file mode 100644 index 0000000000..60b77baae5 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -0,0 +1,121 @@ +import { useMemo } from "react"; +import * as yup from "yup"; + +export const phaseValidationSchema = yup.object().shape({ + phase_id: yup + .number("Phase is required") + .nullable() + .required("Phase is required"), + subphase_id: yup.number().nullable().optional(), + phase_start: yup.string().nullable().optional(), + phase_end: yup.string().nullable().optional(), + is_current_phase: yup.boolean(), + is_phase_start_confirmed: yup.boolean(), + is_phase_end_confirmed: yup.boolean(), + description: yup + .string() + .max(500, "Must be less than 500 characters") + .nullable(), + project_phase_id: yup.number().nullable().optional(), + project_id: yup.number().required(), +}); + +const DEFAULT_FORM_VALUES = { + project_phase_id: null, + phase_id: null, + subphase_id: null, + phase_start: null, + is_phase_start_confirmed: true, + is_phase_end_confirmed: false, + phase_end: null, + phase_description: null, + is_current_phase: false, + project_id: null, +}; + +/** + * Hook which provides initialize form values + * @param {object} phase - an optoinal `moped_proj_phase` object whose values will + * override the DEFAULT_FORM_VALUES + */ +export const useDefaultValues = (phase) => + useMemo(() => { + // initialize form with default values plus the project id + let defaultValues = { + ...DEFAULT_FORM_VALUES, + project_id: phase.project_id, + }; + + if (phase.project_phase_id) { + // we are editing a phase: update all defaults from phase + Object.keys(DEFAULT_FORM_VALUES).forEach((key) => { + defaultValues[key] = phase[key]; + }); + } else { + // default the phase_start to midnight (local) on the current date + defaultValues.phase_start = new Date( + new Date().setHours(0, 0, 0, 0) + ).toISOString(); + } + return defaultValues; + }, [phase]); + +/** + * Hook which returns an array of subphase options given an input `phase_id` + * and an array of `moped_phases` objects + */ +export const useSubphases = (phase_id, phases) => + useMemo( + () => + phase_id + ? phases.find((p) => p.phase_id === phase_id)?.moped_subphases || [] + : [], + [phase_id, phases] + ); + +/** + * Hook which returns an array of `moped_proj_phases.project_phase_id`s which + * need to have their `is_current` flag cleared + */ +export const useCurrentPhaseIdsToClear = ( + thisProjectPhaseId, + isCurrent, + currentProjectPhaseIds +) => { + if (!isCurrent) { + // nothing to do + return []; + } + // return all project phase IDs except the one we're editing + return currentProjectPhaseIds.filter( + (projectPhaseId) => projectPhaseId !== thisProjectPhaseId + ); +}; + +export const onSubmitPhase = ({ + data, + mutate, + currentPhaseIdsToClear, + onSubmitCallback, +}) => { + const { project_phase_id } = data; + delete data.project_phase_id; + + const variables = { + current_phase_ids_to_clear: currentPhaseIdsToClear, + }; + + if (!project_phase_id) { + // inserting a new mutation - which has a slightly different + // variable shape bc the mutation supports multiple inserts + variables.objects = [data]; + } else { + variables.project_phase_id = project_phase_id; + variables.object = data; + } + + mutate({ + variables, + refetchQueries: ["ProjectSummary"], + }).then(() => onSubmitCallback()); +}; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js index ff847b0c1e..5e1d66cf00 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -1,4 +1,4 @@ -import { useMemo, useEffect } from "react"; +import { useEffect } from "react"; import { useMutation } from "@apollo/client"; import { useForm } from "react-hook-form"; import Alert from "@mui/material/Alert"; @@ -15,109 +15,16 @@ import ControlledDateField from "src/components/forms/ControlledDateField"; import ControlledTextInput from "src/components/forms/ControlledTextInput"; import ControlledCheckbox from "src/components/forms/ControlledCheckbox"; import ControlledSwitch from "src/components/forms/ControlledSwitch"; -import { phaseValidationSchema } from "./ProjectPhase/form"; +import { + phaseValidationSchema, + onSubmitPhase, + useDefaultValues, + useSubphases, + useCurrentPhaseIdsToClear, +} from "./ProjectPhase/helpers"; import { useResetDependentFieldOnParentFieldChange } from "./ProjectComponents/utils/form"; import { UPDATE_PROJECT_PHASE, ADD_PROJECT_PHASE } from "src/queries/project"; -const DEFAULT_VALUES = { - project_phase_id: null, - phase_id: null, - subphase_id: null, - phase_start: null, - is_phase_start_confirmed: true, - is_phase_end_confirmed: false, - phase_end: null, - phase_description: null, - is_current_phase: false, - project_id: null, - status_update: null, -}; - -const useDefaultValues = (phase) => - useMemo(() => { - // initialize form with default values plus the project id - let defaultValues = { ...DEFAULT_VALUES, project_id: phase.project_id }; - - if (phase.project_phase_id) { - // we are editing a phase: update all defaults from phase - Object.keys(DEFAULT_VALUES).forEach((key) => { - defaultValues[key] = phase[key]; - }); - } else { - // default the phase_start to midnight (local) on the current date - defaultValues.phase_start = new Date( - new Date().setHours(0, 0, 0, 0) - ).toISOString(); - } - return defaultValues; - }, [phase]); - -const useSubphases = (phase_id, phases) => - useMemo( - () => - phase_id - ? phases.find((p) => p.phase_id === phase_id)?.moped_subphases || [] - : [], - [phase_id, phases] - ); - -const useCurrentPhaseIdsToClear = ( - thisProjectPhaseId, - isCurrent, - currentProjectPhaseIds -) => { - if (!isCurrent) { - // nothing to do - return []; - } - // return all project phase IDs except the one we're editing - return currentProjectPhaseIds.filter( - (projectPhaseId) => projectPhaseId !== thisProjectPhaseId - ); -}; - -export const onSubmitPhase = ({ - data, - mutate, - currentPhaseIdsToClear, - onSubmitCallback, -}) => { - const { project_phase_id, status_update } = data; - delete data.project_phase_id; - delete data.status_update; - - let project_note = null; - - if (status_update) { - project_note = { - project_id: data.project_id, - project_note: status_update, - added_by_user_id: 1, - project_note_type: 2, - phase_id: data.phase_id, - }; - } - - const variables = { - current_phase_ids_to_clear: currentPhaseIdsToClear, - project_note, - }; - - if (!project_phase_id) { - // inserting a new mutation - which has a slightly different - // variable shape bc the mutation supports multiple inserts - variables.objects = [data]; - } else { - variables.project_phase_id = project_phase_id; - variables.object = data; - } - - mutate({ - variables, - refetchQueries: ["ProjectSummary"], - }).then(() => onSubmitCallback()); -}; - const ProjectPhaseForm = ({ phase, phases, @@ -125,7 +32,7 @@ const ProjectPhaseForm = ({ onSubmitCallback, }) => { console.log("TODO: CHECK SEED DATA!"); - // throw `you need to use metadata preset to handle note added_by_user_id`? + console.log("TODO: METADATA PRESET?"); const isNewPhase = !phase.project_phase_id; const defaultValues = useDefaultValues(phase); @@ -165,6 +72,9 @@ const ProjectPhaseForm = ({ const [phase_start, phase_end] = watch(["phase_start", "phase_end"]); + /** + * Defaults is_phase_start_confirmed to true if date is today or before + */ useEffect(() => { if (phase_start !== defaultValues.phase_start) { // phase start has been edited @@ -177,6 +87,9 @@ const ProjectPhaseForm = ({ } }, [phase_start, defaultValues, setValue]); + /** + * Defaults is_phase_end_confirmed to true if date is today or before + */ useEffect(() => { if (phase_end !== defaultValues.phase_end) { // phase end has been edited @@ -340,29 +253,6 @@ const ProjectPhaseForm = ({ />
- {/* - - - - - - - {formErrors?.status_update && ( - - {formErrors.status_update.message} - - )} - - */}
diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index fdf9756cd1..9ff5ba7990 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -138,20 +138,21 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => : ""; const showTentativeIcon = !row.is_phase_start_confirmed && strToRender; + + const showConfirmedIcon = row.is_phase_start_confirmed && strToRender; + return ( - - {`${strToRender}${showTentativeIcon ? "*" : ""}`} + + {strToRender} - {/* {showTentativeIcon && ( + {showTentativeIcon && ( - )} */} + )} + {showConfirmedIcon && ( + + )} ); @@ -171,12 +172,16 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => ? new Date(row.phase_end).toLocaleDateString() : ""; const showTentativeIcon = !row.is_phase_end_confirmed && strToRender; + const showConfirmedIcon = row.is_phase_end_confirmed && strToRender; return ( {strToRender} - {!row.is_phase_end_confirmed && showTentativeIcon && ( + {showTentativeIcon && ( )} + {showConfirmedIcon && ( + + )} ); }, @@ -185,12 +190,12 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => { headerName: "Description", field: "phase_description", - minWidth: 300, + minWidth: 350, }, { headerName: "Current", field: "is_current_phase", - minWidth: 150, + minWidth: 50, renderCell: ({ row }) => row.is_current_phase ? ( From 97406f458b4d38fcf080627bc5048f41f6ce0e25 Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 11:17:37 -0500 Subject: [PATCH 25/53] more docstrings and hellper-ifying --- .../projectView/ProjectPhase/helpers.js | 41 ++++++++++++++- .../projects/projectView/ProjectPhases.js | 50 ++----------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index 60b77baae5..894b521724 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -34,7 +34,7 @@ const DEFAULT_FORM_VALUES = { }; /** - * Hook which provides initialize form values + * Hook which provides initial form values * @param {object} phase - an optoinal `moped_proj_phase` object whose values will * override the DEFAULT_FORM_VALUES */ @@ -73,9 +73,45 @@ export const useSubphases = (phase_id, phases) => [phase_id, phases] ); +export const usePhaseNameLookup = (phases) => + useMemo( + () => + phases.reduce( + (obj, item) => + Object.assign(obj, { + [item.phase_id]: item.phase_name, + }), + {} + ), + [phases] + ); + +/** + * Hoolk which returns an array of project_phase_ids of the project's current phase(s). + * Although only one phase should ever be current, we handle the possibilty that there + * are multiple + * @param {Array} projectPhases - array of this project's moped_proj_phases + * @return {Array} of project_phase_id's of current project phases + */ +export const useCurrentProjectPhaseIDs = (projectPhases) => + useMemo( + () => + projectPhases + ? projectPhases + .filter(({ is_current_phase }) => is_current_phase) + .map(({ project_phase_id }) => project_phase_id) + : [], + [projectPhases] + ); + /** * Hook which returns an array of `moped_proj_phases.project_phase_id`s which - * need to have their `is_current` flag cleared + * need to have their `is_current` flag cleared. + * @param {int} thisProjectPhaseId - the `project_phase_id` that is being edited + * @param {bool} isCurrent - if the phase that is being edited is set as the current phase + * @param {array} currentProjectPhaseIds - an array of all project_phase_ids that are marked as current. + * (this is the output of the useCurrentProjectPhaseIDs hook) + * @return {Array} of project_phase_id's which need to set to `is_current` = false */ export const useCurrentPhaseIdsToClear = ( thisProjectPhaseId, @@ -108,6 +144,7 @@ export const onSubmitPhase = ({ if (!project_phase_id) { // inserting a new mutation - which has a slightly different // variable shape bc the mutation supports multiple inserts + // via the phase template feature variables.objects = [data]; } else { variables.project_phase_id = project_phase_id; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 9ff5ba7990..964cb9658e 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -19,50 +19,10 @@ import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; import ProjectPhaseDialog from "./ProjectPhaseDialog"; import { DELETE_PROJECT_PHASE } from "src/queries/project"; - -/** - * Get the project_phase_ids of the project's current phase. Although only - * one phase should ever be current, we handle the possibilty that there - * are multiple current phases - * @param {Array} projphases - array of this project's moped_proj_phases - * @return {Array} of IDs of current project phases - */ -const useCurrentProjectPhaseIDs = (projectPhases) => - useMemo( - () => - projectPhases - ? projectPhases - .filter(({ is_current_phase }) => is_current_phase) - .map(({ project_phase_id }) => project_phase_id) - : [], - [projectPhases] - ); - -const usePhaseNameLookup = (phases) => - useMemo( - () => - phases.reduce( - (obj, item) => - Object.assign(obj, { - [item.phase_id]: item.phase_name, - }), - {} - ), - [phases] - ); - -const useSubphaseNameLookup = (subphases) => - useMemo( - () => - subphases.reduce( - (obj, item) => - Object.assign(obj, { - [item.subphase_id]: item.subphase_name, - }), - {} - ), - [subphases] - ); +import { + useCurrentProjectPhaseIDs, + usePhaseNameLookup, +} from "./ProjectPhase/helpers"; const John = ({ children, isEnabled }) => { const [anchorEl, setAnchorEl] = useState(null); @@ -244,7 +204,7 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => * @return {JSX.Element} * @constructor */ -const ProjectPhases = ({ projectId, loading, data, refetch }) => { +const ProjectPhases = ({ projectId, data, refetch }) => { const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false); const [editPhase, setEditPhase] = useState(null); From 99d09d76e28715728fb530e690a23fe65dc2f8df Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 11:17:47 -0500 Subject: [PATCH 26/53] remove unused loading prop --- moped-editor/src/views/projects/projectView/ProjectTimeline.js | 1 - 1 file changed, 1 deletion(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectTimeline.js b/moped-editor/src/views/projects/projectView/ProjectTimeline.js index c2464f24b2..761ed812af 100644 --- a/moped-editor/src/views/projects/projectView/ProjectTimeline.js +++ b/moped-editor/src/views/projects/projectView/ProjectTimeline.js @@ -48,7 +48,6 @@ const ProjectTimeline = () => { From f171d63ecd46c5c4cb905f91c29348e8f4a8dce9 Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 12:03:03 -0500 Subject: [PATCH 27/53] fixes invalid date handling --- .../components/forms/ControlledDateField.js | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/moped-editor/src/components/forms/ControlledDateField.js b/moped-editor/src/components/forms/ControlledDateField.js index a42eb26c0d..1ebaebad73 100644 --- a/moped-editor/src/components/forms/ControlledDateField.js +++ b/moped-editor/src/components/forms/ControlledDateField.js @@ -3,7 +3,17 @@ import { DatePicker } from "@mui/x-date-pickers"; import { Controller } from "react-hook-form"; /** - * A react-hook-form wrapper of the MUI DatePicker component + * Test if an input is not null and can be coerced to a Date object. + * @param {any} value - any value, but in our case either a string, a Date object, + * an Invalid Date object, or null + * @returns true if the value is not null and can be coerced to a Date object + */ +const isValidDateStringOrObject = (value) => { + return value !== null && !isNaN(new Date(value)); +}; + +/** + * A react-hook-form wrapper of the MUI DatePicker component. * @param {object} control - react-hook-form `control` object from useController - required * @param {string} name - unique field name which be used in react-hook-form data object * @param {string} label - the label to render next to the checkbox @@ -15,8 +25,15 @@ const ControlledDateField = ({ name, control, label }) => { name={name} control={control} render={({ field }) => { + /** + * The MUI component requires a Date object as the input value. + * So we try to construct a Date object from the value in + * react-hook-form. + * + * Otherwise just pass whatever the value is in state + */ let value = field.value; - if (value !== null && !isNaN(new Date(field.value))) { + if (isValidDateStringOrObject(value)) { value = new Date(field.value); } return ( @@ -25,14 +42,24 @@ const ControlledDateField = ({ name, control, label }) => { label={label} slotProps={{ textField: { size: "small" }, - field: { - clearable: true, - }, + field: { clearable: true }, }} value={value} - onChange={(newValue) => - field.onChange(newValue ? newValue.toISOString() : newValue) - } + onChange={(newValue) => { + /** + * This component's value is a Date object or an Invalid Date object. + * If the date object is valid, we can convert it to an ISO string + * and store the string in react-hook-form state. + * + * If the date object is invalid, we store the invalid date in + * react-hook-form-state and let the Yup schema validation prevent + * form submit. + */ + const valueToStore = isValidDateStringOrObject(newValue) + ? newValue.toISOString() + : newValue; + field.onChange(valueToStore); + }} /> ); }} From f93b08ad52f4a261f4d9c34894c0c3c78eefe87f Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 12:03:42 -0500 Subject: [PATCH 28/53] add date validation and more docstrings --- .../projectView/ProjectPhase/helpers.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index 894b521724..3fb0d156a4 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -7,8 +7,8 @@ export const phaseValidationSchema = yup.object().shape({ .nullable() .required("Phase is required"), subphase_id: yup.number().nullable().optional(), - phase_start: yup.string().nullable().optional(), - phase_end: yup.string().nullable().optional(), + phase_start: yup.date().nullable().optional(), + phase_end: yup.date().nullable().optional(), is_current_phase: yup.boolean(), is_phase_start_confirmed: yup.boolean(), is_phase_end_confirmed: yup.boolean(), @@ -73,6 +73,10 @@ export const useSubphases = (phase_id, phases) => [phase_id, phases] ); +/** + * Hook which returns an object of phase IDs with their name. Taking + * the shape of { [phase_id]: phase_name } + */ export const usePhaseNameLookup = (phases) => useMemo( () => @@ -86,6 +90,23 @@ export const usePhaseNameLookup = (phases) => [phases] ); +/** + * Hook which returns an object of subphase IDs with their name. Taking + * the shape of { [subphase_id]: subphase_name } + */ +export const useSubphaseNameLookup = (subphases) => + useMemo( + () => + subphases.reduce( + (obj, item) => + Object.assign(obj, { + [item.subphase_id]: item.subphase_name, + }), + {} + ), + [subphases] + ); + /** * Hoolk which returns an array of project_phase_ids of the project's current phase(s). * Although only one phase should ever be current, we handle the possibilty that there From e712786fe08d04bdb1e005fc21000f8b41b310a0 Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 12:04:32 -0500 Subject: [PATCH 29/53] fix import --- moped-editor/src/views/projects/projectView/ProjectPhases.js | 1 + 1 file changed, 1 insertion(+) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 964cb9658e..18cc4584e6 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -22,6 +22,7 @@ import { DELETE_PROJECT_PHASE } from "src/queries/project"; import { useCurrentProjectPhaseIDs, usePhaseNameLookup, + useSubphaseNameLookup, } from "./ProjectPhase/helpers"; const John = ({ children, isEnabled }) => { From f48f8d496c649e9d3726cbf5240d55643591d5d0 Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 12:07:46 -0500 Subject: [PATCH 30/53] remove comments --- .../src/views/projects/projectView/ProjectPhaseForm.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js index 5e1d66cf00..c29e9d0789 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -5,7 +5,6 @@ import Alert from "@mui/material/Alert"; import Button from "@mui/material/Button"; import CheckCircle from "@mui/icons-material/CheckCircle"; import CircularProgress from "@mui/material/CircularProgress"; -// import Divider from "@mui/material/Divider"; import FormControl from "@mui/material/FormControl"; import FormHelperText from "@mui/material/FormHelperText"; import Grid from "@mui/material/Grid"; @@ -31,8 +30,6 @@ const ProjectPhaseForm = ({ currentProjectPhaseIds, onSubmitCallback, }) => { - console.log("TODO: CHECK SEED DATA!"); - console.log("TODO: METADATA PRESET?"); const isNewPhase = !phase.project_phase_id; const defaultValues = useDefaultValues(phase); @@ -240,12 +237,6 @@ const ProjectPhaseForm = ({ - {/* */} - Date: Wed, 10 Jan 2024 12:23:25 -0500 Subject: [PATCH 31/53] update migra timestamps, add missing down migra, and leave placeholder for views --- .../1702674851260_phase_timestamps/down.sql | 0 .../1702674851260_phase_timestamps/up.sql | 407 ------------------ .../down.sql | 23 + .../up.sql | 0 .../down.sql | 0 .../1704906960001_phase_timestamps/up.sql | 30 ++ 6 files changed, 53 insertions(+), 407 deletions(-) delete mode 100644 moped-database/migrations/1702674851260_phase_timestamps/down.sql delete mode 100644 moped-database/migrations/1702674851260_phase_timestamps/up.sql create mode 100644 moped-database/migrations/1704906960000_remove_phase_confirm_trigger/down.sql rename moped-database/migrations/{1702674851259_remove_phase_confirm_trigger => 1704906960000_remove_phase_confirm_trigger}/up.sql (100%) rename moped-database/migrations/{1702674851259_remove_phase_confirm_trigger => 1704906960001_phase_timestamps}/down.sql (100%) create mode 100644 moped-database/migrations/1704906960001_phase_timestamps/up.sql diff --git a/moped-database/migrations/1702674851260_phase_timestamps/down.sql b/moped-database/migrations/1702674851260_phase_timestamps/down.sql deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/moped-database/migrations/1702674851260_phase_timestamps/up.sql b/moped-database/migrations/1702674851260_phase_timestamps/up.sql deleted file mode 100644 index 4da779f08c..0000000000 --- a/moped-database/migrations/1702674851260_phase_timestamps/up.sql +++ /dev/null @@ -1,407 +0,0 @@ -DROP VIEW IF EXISTS component_arcgis_online_view; - -DROP VIEW IF EXISTS project_list_view; - - -ALTER TABLE moped_proj_phases ALTER COLUMN phase_start TYPE timestamp WITH time zone, -ALTER COLUMN phase_end TYPE timestamp WITH time zone; - -SET session_replication_role = replica; -UPDATE - moped_proj_phases -SET - phase_start = subquery.phase_start, - phase_end = subquery.phase_end -FROM ( - SELECT - project_id, - phase_start AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_start, - phase_end AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_end - FROM - moped_proj_phases) AS subquery -WHERE - moped_proj_phases.project_id = subquery.project_id; - --- latest version 1698786868086_add_component__subtype_to_view -CREATE OR REPLACE VIEW public.project_list_view -AS WITH project_person_list_lookup AS ( - SELECT - mpp.project_id, - string_agg(DISTINCT concat(mu.first_name, ' ', mu.last_name, ':', mpr.project_role_name), ','::text) AS project_team_members - FROM moped_proj_personnel mpp - JOIN moped_users mu ON mpp.user_id = mu.user_id - JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id - JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id - WHERE mpp.is_deleted = false - AND mppr.is_deleted = false - GROUP BY mpp.project_id - ), funding_sources_lookup AS ( - SELECT - mpf_1.project_id, - string_agg(mfs.funding_source_name, ', '::text) AS funding_source_name - FROM moped_proj_funding mpf_1 - LEFT JOIN moped_fund_sources mfs ON mpf_1.funding_source_id = mfs.funding_source_id - WHERE mpf_1.is_deleted = false - GROUP BY mpf_1.project_id - ), project_type_lookup AS ( - SELECT - mpt.project_id, - string_agg(mt.type_name, ', '::text) AS type_name - FROM moped_project_types mpt - LEFT JOIN moped_types mt ON mpt.project_type_id = mt.type_id AND mpt.is_deleted = false - GROUP BY mpt.project_id - ), child_project_lookup AS ( - SELECT jsonb_agg(children.project_id) AS children_project_ids, - children.parent_project_id AS parent_id - FROM moped_project AS children - JOIN moped_project AS parent ON (parent.project_id = children.parent_project_id) - WHERE children.is_deleted = false - GROUP BY parent_id - ), work_activities AS ( - SELECT - project_id, - string_agg(task_order_objects.task_order_object ->> 'display_name'::text, - ', '::text) AS task_order_names, - string_agg(task_order_objects.task_order_object ->> 'task_order'::text, - ', '::text) AS task_order_names_short, - jsonb_agg(task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, - string_agg(DISTINCT mpwa.contractor, - ', '::text) AS contractors, - string_agg(mpwa.contract_number, - ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa - LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 - AND mpwa.is_deleted = FALSE - GROUP BY - mpwa.project_id - ), moped_proj_components_subtypes AS ( - SELECT - mpc.project_id, - string_agg(DISTINCT mc.component_name_full, ', '::text) AS components - FROM moped_proj_components mpc - LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id - GROUP BY mpc.project_id - ) - SELECT - mp.project_id, - mp.project_name, - mp.project_description, - mp.ecapris_subproject_id, - mp.date_added, - mp.is_deleted, - mp.updated_at, - current_phase.phase_name as current_phase, - current_phase.phase_key as current_phase_key, - current_phase.phase_name_simple as current_phase_simple, - ppll.project_team_members, - me.entity_name AS project_sponsor, - mel.entity_name AS project_lead, - mpps.name AS public_process_status, - mp.interim_project_id, - mp.parent_project_id, - mp.knack_project_id, - proj_notes.project_note, - proj_notes.date_created as project_note_date_created, - work_activities.contractors, - work_activities.contract_numbers, - work_activities.task_order_names, - work_activities.task_order_names_short, - work_activities.task_orders, - (SELECT project_name - FROM moped_project - WHERE project_id = mp.parent_project_id - ) as parent_project_name, - cpl.children_project_ids, - string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partner, - (SELECT JSON_AGG(json_build_object('signal_id', feature_signals.signal_id, 'knack_id', feature_signals.knack_id, 'location_name', feature_signals.location_name, 'signal_type', feature_signals.signal_type, 'id', feature_signals.id)) - FROM moped_proj_components components - LEFT JOIN feature_signals - ON (feature_signals.component_id = components.project_component_id) - WHERE TRUE - AND components.is_deleted = false - AND components.project_id = mp.project_id - AND feature_signals.signal_id is not null - AND feature_signals.is_deleted = false - ) as project_feature, - fsl.funding_source_name, - ptl.type_name, - ( -- get the date of the construction phase with the earliest start date - SELECT min(phases.phase_start) - FROM moped_proj_phases phases - WHERE true - AND phases.project_id = mp.project_id - AND phases.phase_id = 9 -- phase_id 9 is construction - AND phases.is_deleted = false - ) AS construction_start_date, - ( -- get the date of the completion phase with the latest end date - SELECT max(phases.phase_end) - FROM moped_proj_phases phases - WHERE true - AND phases.project_id = mp.project_id - AND phases.phase_id = 11 -- phase_id 11 is complete - AND phases.is_deleted = false - ) AS completion_end_date, - ( -- get me a list of the inspectors for this project - SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg - FROM moped_proj_personnel mpp - JOIN moped_users users ON mpp.user_id = users.user_id - JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id - JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id - WHERE 1 = 1 - AND mpr.project_role_name = 'Inspector'::text - AND mpp.is_deleted = false - AND mppr.is_deleted = false - AND mpp.project_id = mp.project_id - GROUP BY mpp.project_id) AS project_inspector, - ( -- get me a list of the designers for this project - SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg - FROM moped_proj_personnel mpp - JOIN moped_users users ON mpp.user_id = users.user_id - JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id - JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id - WHERE 1 = 1 - AND mpr.project_role_name = 'Designer'::text - AND mpp.is_deleted = false - AND mppr.is_deleted = false - AND mpp.project_id = mp.project_id - GROUP BY mpp.project_id) AS project_designer, - ( -- get me all of the tags added to a project - SELECT string_agg(tags.name, ', '::text) AS string_agg - FROM moped_proj_tags ptags - JOIN moped_tags tags ON ptags.tag_id = tags.id - WHERE 1 = 1 - AND ptags.is_deleted = false - AND ptags.project_id = mp.project_id - GROUP BY ptags.project_id) AS project_tags, - concat(added_by_user.first_name, ' ', added_by_user.last_name) AS added_by, - mpcs.components - FROM moped_project mp - LEFT JOIN project_person_list_lookup ppll ON mp.project_id = ppll.project_id - LEFT JOIN funding_sources_lookup fsl ON fsl.project_id = mp.project_id - LEFT JOIN project_type_lookup ptl ON ptl.project_id = mp.project_id - LEFT JOIN moped_entity me ON me.entity_id = mp.project_sponsor - LEFT JOIN moped_entity mel ON mel.entity_id = mp.project_lead_id - LEFT JOIN moped_proj_partners mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false - LEFT JOIN moped_entity me2 ON mpp2.entity_id = me2.entity_id - LEFT JOIN work_activities on work_activities.project_id = mp.project_id - LEFT JOIN moped_users added_by_user ON mp.added_by = added_by_user.user_id - LEFT JOIN current_phase_view current_phase on mp.project_id = current_phase.project_id - LEFT JOIN moped_public_process_statuses mpps ON mpps.id = mp.public_process_status_id - LEFT JOIN child_project_lookup cpl on cpl.parent_id = mp.project_id - LEFT JOIN moped_proj_components_subtypes mpcs on mpcs.project_id = mp.project_id - LEFT JOIN LATERAL - ( - SELECT mpn.project_note, mpn.date_created - FROM moped_proj_notes mpn - WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false - ORDER BY mpn.date_created DESC - LIMIT 1 - ) as proj_notes on true - WHERE - mp.is_deleted = false - GROUP BY - mp.project_id, - mp.project_name, - mp.project_description, - ppll.project_team_members, - mp.ecapris_subproject_id, - mp.date_added, - mp.is_deleted, - me.entity_name, - mel.entity_name, - mp.updated_at, - mp.interim_project_id, - mp.parent_project_id, - mp.knack_project_id, - current_phase.phase_name, - current_phase.phase_key, - current_phase.phase_name_simple, - ptl.type_name, - mpcs.components, - fsl.funding_source_name, - added_by_user.first_name, - added_by_user.last_name, - mpps.name, - cpl.children_project_ids, - proj_notes.project_note, - proj_notes.date_created, - work_activities.contractors, - work_activities.contract_numbers, - work_activities.task_order_names, - work_activities.task_order_names_short, - work_activities.task_orders; - -CREATE OR REPLACE VIEW component_arcgis_online_view AS ( - SELECT - mpc.project_id, - comp_geography.project_component_id, - comp_geography.feature_ids, - mpc.component_id, - comp_geography.geometry, - comp_geography.signal_ids, - council_districts.council_districts, - comp_geography.length_feet_total, - mc.component_name AS component_name, - mc.component_subtype AS component_subtype, - mc.component_name || ' - ' || mc.component_subtype AS component_name_full, - subcomponents.subcomponents, - work_types.work_types, - component_tags.component_tags, - mpc.description AS component_description, - mpc.is_deleted is_project_component_deleted, - plv.is_deleted is_project_deleted, - mpc.interim_project_component_id, - mpc.completion_date, - mpc.srts_id, - mpc.location_description, - plv.project_name, - plv.project_description, - plv.ecapris_subproject_id, - plv.updated_at, - mpc.phase_id AS component_phase_id, - mph.phase_name AS component_phase_name, - mph.phase_name_simple as component_phase_name_simple, - current_phase.phase_id AS project_phase_id, - current_phase.phase_name AS project_phase_name, - current_phase.phase_name_simple AS project_phase_name_simple, - COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, - COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, - plv.project_team_members, - plv.project_sponsor, - plv.project_lead, - plv.public_process_status, - plv.interim_project_id, - plv.project_partner, - plv.task_order_names, - plv.funding_source_name, - plv.type_name, - plv.project_note, - plv.project_note_date_created, - plv.construction_start_date, - plv.completion_end_date, - plv.project_inspector, - plv.project_designer, - plv.project_tags, - plv.contractors, - plv.contract_numbers, - plv.knack_project_id as knack_data_tracker_project_record_id, - 'https://mobility.austin.gov/moped/projects/' || plv.project_id::text as project_url, - 'https://mobility.austin.gov/moped/projects/' || plv.project_id::text || '?tab=map&project_component_id=' || mpc.project_component_id::text as component_url, - added_by - FROM - moped_proj_components mpc - LEFT JOIN ( - -- group feature properties by project component ID - SELECT - component_id AS project_component_id, - STRING_AGG(DISTINCT id::text, ', ') AS feature_ids, - ST_AsGeoJSON(ST_Union(ARRAY_AGG(geography)))::json AS "geometry", - STRING_AGG(DISTINCT signal_id::text, ', ') AS signal_ids, - SUM(length_feet) as length_feet_total - FROM ( - -- union all features - SELECT - id, - feature_signals.component_id, - feature_signals.geography::geometry, - feature_signals.signal_id, - NULL AS length_feet - FROM - feature_signals - WHERE - feature_signals.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_street_segments.component_id, - feature_street_segments.geography::geometry, - NULL AS signal_id, - length_feet - FROM - feature_street_segments - WHERE - feature_street_segments.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_intersections.component_id, - feature_intersections.geography::geometry, - NULL AS signal_id, - NULL AS length_feet - FROM - feature_intersections - WHERE - feature_intersections.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_drawn_points.component_id, - feature_drawn_points.geography::geometry, - NULL AS signal_id, - NULL AS length_feet - FROM - feature_drawn_points - WHERE - feature_drawn_points.is_deleted = FALSE - UNION ALL - SELECT - id, - feature_drawn_lines.component_id, - feature_drawn_lines.geography::geometry, - NULL AS signal_id, - length_feet - FROM - feature_drawn_lines - WHERE - feature_drawn_lines.is_deleted = FALSE) feature_union - GROUP BY - component_id) comp_geography ON comp_geography.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group council districts by project component id - SELECT - component_id AS project_component_id, - STRING_AGG(DISTINCT council_district_id::text, ', ') AS council_districts - FROM - features_council_districts - LEFT JOIN features ON features.id = features_council_districts.feature_id - WHERE - features.is_deleted = FALSE - GROUP BY - component_id) council_districts ON council_districts.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group subcomponents by project component id - SELECT - project_component_id, - string_agg(ms.subcomponent_name, ', ') subcomponents - FROM - moped_proj_components_subcomponents mpcs - LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id - GROUP BY - project_component_id) subcomponents ON subcomponents.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group work types by project component id - SELECT - project_component_id, - string_agg(mwt.name, ', ') work_types - FROM - moped_proj_component_work_types mpcwt - LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id - GROUP BY - project_component_id) work_types ON work_types.project_component_id = mpc.project_component_id - LEFT JOIN ( - -- group project component tags by project component id - SELECT - project_component_id, - string_agg(mct.type || ' - ' || mct.name, ', ') component_tags - FROM - moped_proj_component_tags mpct - LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id - GROUP BY - project_component_id) component_tags ON component_tags.project_component_id = mpc.project_component_id - LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id - LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id - LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id - LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id -WHERE - mpc.is_deleted = FALSE - AND plv.is_deleted = FALSE -); diff --git a/moped-database/migrations/1704906960000_remove_phase_confirm_trigger/down.sql b/moped-database/migrations/1704906960000_remove_phase_confirm_trigger/down.sql new file mode 100644 index 0000000000..2e7d94e978 --- /dev/null +++ b/moped-database/migrations/1704906960000_remove_phase_confirm_trigger/down.sql @@ -0,0 +1,23 @@ +-- revert to 1697679713991_confirmed_date_trigger +CREATE OR REPLACE FUNCTION moped_proj_phases_confirmed_dates() RETURNS trigger + LANGUAGE plpgsql + AS $$ + BEGIN + IF NEW.phase_start <= current_date AND NEW.is_phase_start_confirmed != TRUE THEN + new.is_phase_start_confirmed := true; + END IF; + IF NEW.phase_end <= current_date AND NEW.is_phase_end_confirmed != TRUE THEN + new.is_phase_end_confirmed := true; + END IF; + IF NEW.phase_start > current_date THEN + new.is_phase_start_confirmed := false; + END IF; + IF NEW.phase_end > current_date THEN + new.is_phase_end_confirmed := false; + END IF; + RETURN NEW; + END; +$$; + +CREATE TRIGGER set_moped_proj_phases_confirmed_dates_trigger BEFORE INSERT OR UPDATE ON public.moped_proj_phases + FOR EACH ROW EXECUTE FUNCTION public.moped_proj_phases_confirmed_dates(); diff --git a/moped-database/migrations/1702674851259_remove_phase_confirm_trigger/up.sql b/moped-database/migrations/1704906960000_remove_phase_confirm_trigger/up.sql similarity index 100% rename from moped-database/migrations/1702674851259_remove_phase_confirm_trigger/up.sql rename to moped-database/migrations/1704906960000_remove_phase_confirm_trigger/up.sql diff --git a/moped-database/migrations/1702674851259_remove_phase_confirm_trigger/down.sql b/moped-database/migrations/1704906960001_phase_timestamps/down.sql similarity index 100% rename from moped-database/migrations/1702674851259_remove_phase_confirm_trigger/down.sql rename to moped-database/migrations/1704906960001_phase_timestamps/down.sql diff --git a/moped-database/migrations/1704906960001_phase_timestamps/up.sql b/moped-database/migrations/1704906960001_phase_timestamps/up.sql new file mode 100644 index 0000000000..aa340cec98 --- /dev/null +++ b/moped-database/migrations/1704906960001_phase_timestamps/up.sql @@ -0,0 +1,30 @@ +-- this migration makes the `moped_proj_phases` timezone aware and corrects the existing date +-- values which are in central time but stored as UTC + +-- we must drop dependent views to change the column types :/ +DROP VIEW IF EXISTS component_arcgis_online_view; +DROP VIEW IF EXISTS project_list_view; + +-- change column types to be timezone aware +ALTER TABLE moped_proj_phases + ALTER COLUMN phase_start TYPE timestamp WITH time zone, + ALTER COLUMN phase_end TYPE timestamp WITH time zone; + +-- disable event triggers and assign timezone to all existing dates +SET session_replication_role = replica; +UPDATE + moped_proj_phases +SET + phase_start = dates_to_fix.phase_start, + phase_end = dates_to_fix.phase_end +FROM ( + SELECT + project_id, + phase_start AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_start, + phase_end AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_end + FROM + moped_proj_phases) AS dates_to_fix +WHERE + moped_proj_phases.project_id = subquery.project_id; + +-- rebuild dropped views From 6b4417651ef9b073e51cd55e2ddae12f769f1580 Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 12:57:51 -0500 Subject: [PATCH 32/53] fix error state props --- moped-editor/src/components/forms/ControlledDateField.js | 5 +++-- .../src/views/projects/projectView/ProjectPhaseForm.js | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/moped-editor/src/components/forms/ControlledDateField.js b/moped-editor/src/components/forms/ControlledDateField.js index 1ebaebad73..e35fbf86f5 100644 --- a/moped-editor/src/components/forms/ControlledDateField.js +++ b/moped-editor/src/components/forms/ControlledDateField.js @@ -17,9 +17,10 @@ const isValidDateStringOrObject = (value) => { * @param {object} control - react-hook-form `control` object from useController - required * @param {string} name - unique field name which be used in react-hook-form data object * @param {string} label - the label to render next to the checkbox + * @param {bool} error - if the error state is active (triggers red outline around textfield) * @return {JSX.Element} */ -const ControlledDateField = ({ name, control, label }) => { +const ControlledDateField = ({ name, control, label, error }) => { return ( { size="small" label={label} slotProps={{ - textField: { size: "small" }, + textField: { size: "small", error }, field: { clearable: true }, }} value={value} diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js index c29e9d0789..f01d751f94 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -140,7 +140,7 @@ const ProjectPhaseForm = ({ option?.phase_id === selectedOption?.phase_id } getOptionLabel={(option) => option?.phase_name || ""} - error={formErrors?.phase_id} + error={!!formErrors?.phase_id} /> {formErrors?.phase_id && ( {formErrors.phase_id.message} @@ -167,7 +167,7 @@ const ProjectPhaseForm = ({ option?.subphase_id === selectedOption?.subphase_id } getOptionLabel={(option) => option?.subphase_name || ""} - error={formErrors?.subphase_id} + error={!!formErrors?.subphase_id} /> {formErrors?.subphase_id && ( {formErrors.subphase_id.message} @@ -180,6 +180,7 @@ const ProjectPhaseForm = ({ name="phase_start" label="Start" control={control} + error={!!formErrors?.phase_start} /> {formErrors?.phase_start && ( {formErrors?.phase_start.message} @@ -201,7 +202,7 @@ const ProjectPhaseForm = ({ name="phase_end" label="End" control={control} - errorMessage={formErrors?.phase_end?.message || ""} + error={!!formErrors?.phase_end} /> {formErrors?.phase_end && ( {formErrors.phase_end.message} From 2e64c3127291a2f48d700427caac390b5e0d3705 Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 12:58:11 -0500 Subject: [PATCH 33/53] make phase start conditionally required --- .../projects/projectView/ProjectPhase/helpers.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index 3fb0d156a4..7380517775 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -7,8 +7,16 @@ export const phaseValidationSchema = yup.object().shape({ .nullable() .required("Phase is required"), subphase_id: yup.number().nullable().optional(), - phase_start: yup.date().nullable().optional(), - phase_end: yup.date().nullable().optional(), + phase_start: yup + .date() + .nullable() + .optional() + .when("is_current_phase", { + is: true, + then: (schema) => schema.required("Start date is required when phase is current"), + }) + .typeError("Invalid Date"), + phase_end: yup.date().nullable().optional().typeError("Invalid Date"), is_current_phase: yup.boolean(), is_phase_start_confirmed: yup.boolean(), is_phase_end_confirmed: yup.boolean(), From 344a9ee039590ad32d1ad9da034af4eacbf6816c Mon Sep 17 00:00:00 2001 From: John Clary Date: Wed, 10 Jan 2024 13:08:43 -0500 Subject: [PATCH 34/53] dont default start date --- .../projects/projectView/ProjectPhase/helpers.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index 7380517775..1bdd4e2dbd 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -13,7 +13,8 @@ export const phaseValidationSchema = yup.object().shape({ .optional() .when("is_current_phase", { is: true, - then: (schema) => schema.required("Start date is required when phase is current"), + then: (schema) => + schema.required("Start date is required when phase is current"), }) .typeError("Invalid Date"), phase_end: yup.date().nullable().optional().typeError("Invalid Date"), @@ -33,9 +34,9 @@ const DEFAULT_FORM_VALUES = { phase_id: null, subphase_id: null, phase_start: null, - is_phase_start_confirmed: true, - is_phase_end_confirmed: false, + is_phase_start_confirmed: false, phase_end: null, + is_phase_end_confirmed: false, phase_description: null, is_current_phase: false, project_id: null, @@ -59,11 +60,6 @@ export const useDefaultValues = (phase) => Object.keys(DEFAULT_FORM_VALUES).forEach((key) => { defaultValues[key] = phase[key]; }); - } else { - // default the phase_start to midnight (local) on the current date - defaultValues.phase_start = new Date( - new Date().setHours(0, 0, 0, 0) - ).toISOString(); } return defaultValues; }, [phase]); From 0d7937197d640a469d05df6b86b59c568af7ab53 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 11 Jan 2024 11:54:26 -0500 Subject: [PATCH 35/53] more mockups for data confirmation feedback --- .../projects/projectView/ProjectPhases.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 18cc4584e6..1525db6d81 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -14,6 +14,10 @@ import { DeleteOutline as DeleteOutlineIcon, CheckCircleOutline, HelpOutline, + EditCalendar, + EventAvailable, + PendingActions + } from "@mui/icons-material"; import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; @@ -50,7 +54,7 @@ const John = ({ children, isEnabled }) => { sx={{ pointerEvents: "none", }} - open={isEnabled && open} + open={!!isEnabled && !!open} anchorEl={anchorEl} anchorOrigin={{ vertical: "bottom", @@ -105,14 +109,21 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => return ( - + {/* {showTentativeIcon && } + {!showTentativeIcon &&   } */} + {strToRender} + {/* {showTentativeIcon && (ETA)} */} {showTentativeIcon && ( - + )} {showConfirmedIcon && ( - + )} @@ -137,12 +148,12 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => return ( {strToRender} - {showTentativeIcon && ( + {/* {showTentativeIcon && ( )} {showConfirmedIcon && ( - )} + )} */} ); }, From 8fe49e2933a335b485409e124eba8ac1d131330b Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 11 Jan 2024 11:54:37 -0500 Subject: [PATCH 36/53] placeholder for views --- moped-database/migrations/1704906960001_phase_timestamps/up.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/moped-database/migrations/1704906960001_phase_timestamps/up.sql b/moped-database/migrations/1704906960001_phase_timestamps/up.sql index aa340cec98..02355facfc 100644 --- a/moped-database/migrations/1704906960001_phase_timestamps/up.sql +++ b/moped-database/migrations/1704906960001_phase_timestamps/up.sql @@ -28,3 +28,4 @@ WHERE moped_proj_phases.project_id = subquery.project_id; -- rebuild dropped views +-- ... \ No newline at end of file From 5b14ba2e0995ec154ab7ae4730483e71c46e507c Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 11 Jan 2024 14:17:10 -0500 Subject: [PATCH 37/53] add latest view migratins and add migration to fix completion_date --- .../1704906960001_phase_timestamps/down.sql | 493 ++++++++++++++++++ .../1704906960001_phase_timestamps/up.sql | 475 ++++++++++++++++- 2 files changed, 963 insertions(+), 5 deletions(-) diff --git a/moped-database/migrations/1704906960001_phase_timestamps/down.sql b/moped-database/migrations/1704906960001_phase_timestamps/down.sql index e69de29bb2..3871354056 100644 --- a/moped-database/migrations/1704906960001_phase_timestamps/down.sql +++ b/moped-database/migrations/1704906960001_phase_timestamps/down.sql @@ -0,0 +1,493 @@ +DROP VIEW IF EXISTS component_arcgis_online_view; +DROP VIEW IF EXISTS project_list_view; + +-- change column types to dates +ALTER TABLE moped_proj_phases + ALTER COLUMN phase_start TYPE date, + ALTER COLUMN phase_end TYPE date; + +-- disable event triggers and assign UTC to all existing dates +SET session_replication_role = replica; +UPDATE + moped_proj_phases +SET + phase_start = dates_to_fix.phase_start, + phase_end = dates_to_fix.phase_end +FROM ( + SELECT + project_id, + phase_start AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC' AS phase_start, + phase_end AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC' AS phase_end + FROM + moped_proj_phases) AS dates_to_fix +WHERE + moped_proj_phases.project_id = dates_to_fix.project_id; + +-- replace timezone of component completion dates which were imported from Access DB as UTC +-- there is no way to undo this operation: +-- UPDATE +-- moped_proj_components +-- SET +-- completion_date = dates_to_fix.completion_date +-- FROM ( +-- SELECT +-- project_component_id, +-- completion_date AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS completion_date +-- FROM +-- moped_proj_components +-- WHERE +-- -- if the timestamp is 00:00:00 it was imported incorrectly as UTC time +-- completion_date IS NOT NULL +-- AND extract(hour FROM completion_date) = 0) as dates_to_fix +-- WHERE +-- moped_proj_components.project_component_id = dates_to_fix.project_component_id; + +-- rebuild dropped views to version 1704744986000_substantial_completion_date +CREATE OR REPLACE VIEW public.project_list_view +AS WITH project_person_list_lookup AS ( + SELECT + mpp.project_id, + string_agg(DISTINCT concat(mu.first_name, ' ', mu.last_name, ':', mpr.project_role_name), ','::text) AS project_team_members + FROM moped_proj_personnel mpp + JOIN moped_users mu ON mpp.user_id = mu.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE mpp.is_deleted = false + AND mppr.is_deleted = false + GROUP BY mpp.project_id + ), funding_sources_lookup AS ( + SELECT + mpf_1.project_id, + string_agg(mfs.funding_source_name, ', '::text) AS funding_source_name + FROM moped_proj_funding mpf_1 + LEFT JOIN moped_fund_sources mfs ON mpf_1.funding_source_id = mfs.funding_source_id + WHERE mpf_1.is_deleted = false + GROUP BY mpf_1.project_id + ), project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types mpt + LEFT JOIN moped_types mt ON mpt.project_type_id = mt.type_id AND mpt.is_deleted = false + GROUP BY mpt.project_id + ), child_project_lookup AS ( + SELECT jsonb_agg(children.project_id) AS children_project_ids, + children.parent_project_id AS parent_id + FROM moped_project AS children + JOIN moped_project AS parent ON (parent.project_id = children.parent_project_id) + WHERE children.is_deleted = false + GROUP BY parent_id + ), work_activities AS ( + SELECT + project_id, + string_agg(task_order_objects.task_order_object ->> 'display_name'::text, + ', '::text) AS task_order_names, + string_agg(task_order_objects.task_order_object ->> 'task_order'::text, + ', '::text) AS task_order_names_short, + jsonb_agg(DISTINCT task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + string_agg(DISTINCT mpwa.workgroup_contractor, + ', '::text) AS workgroup_contractors, + string_agg(mpwa.contract_number, + ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 + AND mpwa.is_deleted = FALSE + GROUP BY + mpwa.project_id + ), moped_proj_components_subtypes AS ( + SELECT + mpc.project_id, + string_agg(DISTINCT mc.component_name_full, ', '::text) AS components + FROM moped_proj_components mpc + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + WHERE mpc.is_deleted = FALSE + GROUP BY mpc.project_id + ) + SELECT + mp.project_id, + mp.project_name, + mp.project_description, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + mp.updated_at, + current_phase.phase_name as current_phase, + current_phase.phase_key as current_phase_key, + current_phase.phase_name_simple as current_phase_simple, + ppll.project_team_members, + me.entity_name AS project_sponsor, + mel.entity_name AS project_lead, + mpps.name AS public_process_status, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + proj_notes.project_note, + proj_notes.date_created as project_note_date_created, + work_activities.workgroup_contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders, + (SELECT project_name + FROM moped_project + WHERE project_id = mp.parent_project_id + ) as parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partner, + (SELECT JSON_AGG(json_build_object('signal_id', feature_signals.signal_id, 'knack_id', feature_signals.knack_id, 'location_name', feature_signals.location_name, 'signal_type', feature_signals.signal_type, 'id', feature_signals.id)) + FROM moped_proj_components components + LEFT JOIN feature_signals + ON (feature_signals.component_id = components.project_component_id) + WHERE TRUE + AND components.is_deleted = false + AND components.project_id = mp.project_id + AND feature_signals.signal_id is not null + AND feature_signals.is_deleted = false + ) as project_feature, + fsl.funding_source_name, + ptl.type_name, + ( -- get the date of the construction phase with the earliest start date + SELECT min(phases.phase_start) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 9 -- phase_id 9 is construction + AND phases.is_deleted = false + ) AS construction_start_date, + ( -- get the date of the completion phase with the latest end date + SELECT max(phases.phase_end) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 11 -- phase_id 11 is complete + AND phases.is_deleted = false + ) AS completion_end_date, + ( -- get the earliest confirmed phase_start or phase_end with a simple phase of 'Complete' + SELECT + min(min_confirmed_date) + FROM ( + -- earliest confirmed phase start + SELECT + min(phases.phase_start) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_start IS NOT NULL + AND phases.is_phase_start_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + UNION ALL + -- earliest confirmed phase end + SELECT + min(phases.phase_end) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_end IS NOT NULL + AND phases.is_phase_end_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + ) min_confirmed_dates + ) AS substantial_completion_date, + ( -- get me a list of the inspectors for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Inspector'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_inspector, + ( -- get me a list of the designers for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Designer'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_designer, + ( -- get me all of the tags added to a project + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags ptags + JOIN moped_tags tags ON ptags.tag_id = tags.id + WHERE 1 = 1 + AND ptags.is_deleted = false + AND ptags.project_id = mp.project_id + GROUP BY ptags.project_id) AS project_tags, + concat(added_by_user.first_name, ' ', added_by_user.last_name) AS added_by, + mpcs.components + FROM moped_project mp + LEFT JOIN project_person_list_lookup ppll ON mp.project_id = ppll.project_id + LEFT JOIN funding_sources_lookup fsl ON fsl.project_id = mp.project_id + LEFT JOIN project_type_lookup ptl ON ptl.project_id = mp.project_id + LEFT JOIN moped_entity me ON me.entity_id = mp.project_sponsor + LEFT JOIN moped_entity mel ON mel.entity_id = mp.project_lead_id + LEFT JOIN moped_proj_partners mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false + LEFT JOIN moped_entity me2 ON mpp2.entity_id = me2.entity_id + LEFT JOIN work_activities on work_activities.project_id = mp.project_id + LEFT JOIN moped_users added_by_user ON mp.added_by = added_by_user.user_id + LEFT JOIN current_phase_view current_phase on mp.project_id = current_phase.project_id + LEFT JOIN moped_public_process_statuses mpps ON mpps.id = mp.public_process_status_id + LEFT JOIN child_project_lookup cpl on cpl.parent_id = mp.project_id + LEFT JOIN moped_proj_components_subtypes mpcs on mpcs.project_id = mp.project_id + LEFT JOIN LATERAL + ( + SELECT mpn.project_note, mpn.date_created + FROM moped_proj_notes mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.date_created DESC + LIMIT 1 + ) as proj_notes on true + WHERE + mp.is_deleted = false + GROUP BY + mp.project_id, + mp.project_name, + mp.project_description, + ppll.project_team_members, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + me.entity_name, + mel.entity_name, + mp.updated_at, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + current_phase.phase_name, + current_phase.phase_key, + current_phase.phase_name_simple, + ptl.type_name, + mpcs.components, + fsl.funding_source_name, + added_by_user.first_name, + added_by_user.last_name, + mpps.name, + cpl.children_project_ids, + proj_notes.project_note, + proj_notes.date_created, + work_activities.workgroup_contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders; + +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.line_geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + COALESCE(mpc.completion_date, substantial_completion_date) as substantial_completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.workgroup_contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(geography) + ) + ):: json AS "geometry", + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(line_geography) + ) + ):: json AS "line_geometry", + STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM + ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_signals.geography, 7):: geometry + ) AS line_geography, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography :: geometry, + feature_street_segments.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_intersections.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_drawn_points.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography :: geometry, + feature_drawn_lines.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE + ) feature_union + GROUP BY + component_id + ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG( + DISTINCT council_district_id :: text, + ', ' + ) AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id + ) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id + ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id + ) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id + ) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); diff --git a/moped-database/migrations/1704906960001_phase_timestamps/up.sql b/moped-database/migrations/1704906960001_phase_timestamps/up.sql index 02355facfc..f67f3f651f 100644 --- a/moped-database/migrations/1704906960001_phase_timestamps/up.sql +++ b/moped-database/migrations/1704906960001_phase_timestamps/up.sql @@ -5,7 +5,7 @@ DROP VIEW IF EXISTS component_arcgis_online_view; DROP VIEW IF EXISTS project_list_view; --- change column types to be timezone aware +-- change column types from dates to timestamps with timezones ALTER TABLE moped_proj_phases ALTER COLUMN phase_start TYPE timestamp WITH time zone, ALTER COLUMN phase_end TYPE timestamp WITH time zone; @@ -19,13 +19,478 @@ SET phase_end = dates_to_fix.phase_end FROM ( SELECT - project_id, + project_phase_id, phase_start AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_start, phase_end AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS phase_end FROM moped_proj_phases) AS dates_to_fix WHERE - moped_proj_phases.project_id = subquery.project_id; + moped_proj_phases.project_phase_id = dates_to_fix.project_phase_id; --- rebuild dropped views --- ... \ No newline at end of file +-- replace timezone of component completion dates which were imported from Access DB as UTC +UPDATE + moped_proj_components +SET + completion_date = dates_to_fix.completion_date +FROM ( + SELECT + project_component_id, + completion_date AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago' AS completion_date + FROM + moped_proj_components + WHERE + -- if the timestamp is 00:00:00 it was imported incorrectly as UTC time + completion_date IS NOT NULL + AND extract(hour FROM completion_date) = 0) as dates_to_fix +WHERE + moped_proj_components.project_component_id = dates_to_fix.project_component_id; + +-- rebuild dropped views to version 1704744986000_substantial_completion_date +CREATE OR REPLACE VIEW public.project_list_view +AS WITH project_person_list_lookup AS ( + SELECT + mpp.project_id, + string_agg(DISTINCT concat(mu.first_name, ' ', mu.last_name, ':', mpr.project_role_name), ','::text) AS project_team_members + FROM moped_proj_personnel mpp + JOIN moped_users mu ON mpp.user_id = mu.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE mpp.is_deleted = false + AND mppr.is_deleted = false + GROUP BY mpp.project_id + ), funding_sources_lookup AS ( + SELECT + mpf_1.project_id, + string_agg(mfs.funding_source_name, ', '::text) AS funding_source_name + FROM moped_proj_funding mpf_1 + LEFT JOIN moped_fund_sources mfs ON mpf_1.funding_source_id = mfs.funding_source_id + WHERE mpf_1.is_deleted = false + GROUP BY mpf_1.project_id + ), project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types mpt + LEFT JOIN moped_types mt ON mpt.project_type_id = mt.type_id AND mpt.is_deleted = false + GROUP BY mpt.project_id + ), child_project_lookup AS ( + SELECT jsonb_agg(children.project_id) AS children_project_ids, + children.parent_project_id AS parent_id + FROM moped_project AS children + JOIN moped_project AS parent ON (parent.project_id = children.parent_project_id) + WHERE children.is_deleted = false + GROUP BY parent_id + ), work_activities AS ( + SELECT + project_id, + string_agg(task_order_objects.task_order_object ->> 'display_name'::text, + ', '::text) AS task_order_names, + string_agg(task_order_objects.task_order_object ->> 'task_order'::text, + ', '::text) AS task_order_names_short, + jsonb_agg(DISTINCT task_order_objects.task_order_object) FILTER (WHERE task_order_objects.task_order_object IS NOT NULL) AS task_orders, + string_agg(DISTINCT mpwa.workgroup_contractor, + ', '::text) AS workgroup_contractors, + string_agg(mpwa.contract_number, + ', '::text) AS contract_numbers FROM moped_proj_work_activity mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) task_order_objects (task_order_object) ON TRUE WHERE 1 = 1 + AND mpwa.is_deleted = FALSE + GROUP BY + mpwa.project_id + ), moped_proj_components_subtypes AS ( + SELECT + mpc.project_id, + string_agg(DISTINCT mc.component_name_full, ', '::text) AS components + FROM moped_proj_components mpc + LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id + WHERE mpc.is_deleted = FALSE + GROUP BY mpc.project_id + ) + SELECT + mp.project_id, + mp.project_name, + mp.project_description, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + mp.updated_at, + current_phase.phase_name as current_phase, + current_phase.phase_key as current_phase_key, + current_phase.phase_name_simple as current_phase_simple, + ppll.project_team_members, + me.entity_name AS project_sponsor, + mel.entity_name AS project_lead, + mpps.name AS public_process_status, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + proj_notes.project_note, + proj_notes.date_created as project_note_date_created, + work_activities.workgroup_contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders, + (SELECT project_name + FROM moped_project + WHERE project_id = mp.parent_project_id + ) as parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partner, + (SELECT JSON_AGG(json_build_object('signal_id', feature_signals.signal_id, 'knack_id', feature_signals.knack_id, 'location_name', feature_signals.location_name, 'signal_type', feature_signals.signal_type, 'id', feature_signals.id)) + FROM moped_proj_components components + LEFT JOIN feature_signals + ON (feature_signals.component_id = components.project_component_id) + WHERE TRUE + AND components.is_deleted = false + AND components.project_id = mp.project_id + AND feature_signals.signal_id is not null + AND feature_signals.is_deleted = false + ) as project_feature, + fsl.funding_source_name, + ptl.type_name, + ( -- get the date of the construction phase with the earliest start date + SELECT min(phases.phase_start) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 9 -- phase_id 9 is construction + AND phases.is_deleted = false + ) AS construction_start_date, + ( -- get the date of the completion phase with the latest end date + SELECT max(phases.phase_end) + FROM moped_proj_phases phases + WHERE true + AND phases.project_id = mp.project_id + AND phases.phase_id = 11 -- phase_id 11 is complete + AND phases.is_deleted = false + ) AS completion_end_date, + ( -- get the earliest confirmed phase_start or phase_end with a simple phase of 'Complete' + SELECT + min(min_confirmed_date) + FROM ( + -- earliest confirmed phase start + SELECT + min(phases.phase_start) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_start IS NOT NULL + AND phases.is_phase_start_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + UNION ALL + -- earliest confirmed phase end + SELECT + min(phases.phase_end) AS min_confirmed_date + FROM + moped_proj_phases phases + LEFT JOIN moped_phases ON phases.phase_id = moped_phases.phase_id + WHERE + TRUE + AND phases.phase_end IS NOT NULL + AND phases.is_phase_end_confirmed = TRUE + AND phases.project_id = mp.project_id + AND moped_phases.phase_name_simple = 'Complete' + AND phases.is_deleted = FALSE + ) min_confirmed_dates + ) AS substantial_completion_date, + ( -- get me a list of the inspectors for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Inspector'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_inspector, + ( -- get me a list of the designers for this project + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel mpp + JOIN moped_users users ON mpp.user_id = users.user_id + JOIN moped_proj_personnel_roles mppr ON mpp.project_personnel_id = mppr.project_personnel_id + JOIN moped_project_roles mpr ON mppr.project_role_id = mpr.project_role_id + WHERE 1 = 1 + AND mpr.project_role_name = 'Designer'::text + AND mpp.is_deleted = false + AND mppr.is_deleted = false + AND mpp.project_id = mp.project_id + GROUP BY mpp.project_id) AS project_designer, + ( -- get me all of the tags added to a project + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags ptags + JOIN moped_tags tags ON ptags.tag_id = tags.id + WHERE 1 = 1 + AND ptags.is_deleted = false + AND ptags.project_id = mp.project_id + GROUP BY ptags.project_id) AS project_tags, + concat(added_by_user.first_name, ' ', added_by_user.last_name) AS added_by, + mpcs.components + FROM moped_project mp + LEFT JOIN project_person_list_lookup ppll ON mp.project_id = ppll.project_id + LEFT JOIN funding_sources_lookup fsl ON fsl.project_id = mp.project_id + LEFT JOIN project_type_lookup ptl ON ptl.project_id = mp.project_id + LEFT JOIN moped_entity me ON me.entity_id = mp.project_sponsor + LEFT JOIN moped_entity mel ON mel.entity_id = mp.project_lead_id + LEFT JOIN moped_proj_partners mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false + LEFT JOIN moped_entity me2 ON mpp2.entity_id = me2.entity_id + LEFT JOIN work_activities on work_activities.project_id = mp.project_id + LEFT JOIN moped_users added_by_user ON mp.added_by = added_by_user.user_id + LEFT JOIN current_phase_view current_phase on mp.project_id = current_phase.project_id + LEFT JOIN moped_public_process_statuses mpps ON mpps.id = mp.public_process_status_id + LEFT JOIN child_project_lookup cpl on cpl.parent_id = mp.project_id + LEFT JOIN moped_proj_components_subtypes mpcs on mpcs.project_id = mp.project_id + LEFT JOIN LATERAL + ( + SELECT mpn.project_note, mpn.date_created + FROM moped_proj_notes mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.date_created DESC + LIMIT 1 + ) as proj_notes on true + WHERE + mp.is_deleted = false + GROUP BY + mp.project_id, + mp.project_name, + mp.project_description, + ppll.project_team_members, + mp.ecapris_subproject_id, + mp.date_added, + mp.is_deleted, + me.entity_name, + mel.entity_name, + mp.updated_at, + mp.interim_project_id, + mp.parent_project_id, + mp.knack_project_id, + current_phase.phase_name, + current_phase.phase_key, + current_phase.phase_name_simple, + ptl.type_name, + mpcs.components, + fsl.funding_source_name, + added_by_user.first_name, + added_by_user.last_name, + mpps.name, + cpl.children_project_ids, + proj_notes.project_note, + proj_notes.date_created, + work_activities.workgroup_contractors, + work_activities.contract_numbers, + work_activities.task_order_names, + work_activities.task_order_names_short, + work_activities.task_orders; + +CREATE OR REPLACE VIEW component_arcgis_online_view AS ( + SELECT + mpc.project_id, + comp_geography.project_component_id, + comp_geography.feature_ids, + mpc.component_id, + comp_geography.geometry, + comp_geography.line_geometry, + comp_geography.signal_ids, + council_districts.council_districts, + comp_geography.length_feet_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + subcomponents.subcomponents, + work_types.work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.is_deleted is_project_component_deleted, + plv.is_deleted is_project_deleted, + mpc.interim_project_component_id, + mpc.completion_date, + COALESCE(mpc.completion_date, substantial_completion_date) as substantial_completion_date, + mpc.srts_id, + mpc.location_description, + plv.project_name, + plv.project_description, + plv.ecapris_subproject_id, + plv.updated_at, + mpc.phase_id AS component_phase_id, + mph.phase_name AS component_phase_name, + mph.phase_name_simple as component_phase_name_simple, + current_phase.phase_id AS project_phase_id, + current_phase.phase_name AS project_phase_name, + current_phase.phase_name_simple AS project_phase_name_simple, + COALESCE(mph.phase_name, current_phase.phase_name) AS current_phase_name, + COALESCE(mph.phase_name_simple, current_phase.phase_name_simple) AS current_phase_name_simple, + plv.project_team_members, + plv.project_sponsor, + plv.project_lead, + plv.public_process_status, + plv.interim_project_id, + plv.project_partner, + plv.task_order_names, + plv.funding_source_name, + plv.type_name, + plv.project_note, + plv.project_note_date_created, + plv.construction_start_date, + plv.completion_end_date, + plv.project_inspector, + plv.project_designer, + plv.project_tags, + plv.workgroup_contractors, + plv.contract_numbers, + plv.knack_project_id as knack_data_tracker_project_record_id, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text as project_url, + 'https://mobility.austin.gov/moped/projects/' || plv.project_id :: text || '?tab=map&project_component_id=' || mpc.project_component_id :: text as component_url, + added_by + FROM + moped_proj_components mpc + LEFT JOIN ( + -- group feature properties by project component ID + SELECT + component_id AS project_component_id, + STRING_AGG(DISTINCT id :: text, ', ') AS feature_ids, + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(geography) + ) + ):: json AS "geometry", + ST_AsGeoJSON( + ST_Union( + ARRAY_AGG(line_geography) + ) + ):: json AS "line_geometry", + STRING_AGG(DISTINCT signal_id :: text, ', ') AS signal_ids, + SUM(length_feet) as length_feet_total + FROM + ( + -- union all features + SELECT + id, + feature_signals.component_id, + feature_signals.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_signals.geography, 7):: geometry + ) AS line_geography, + feature_signals.signal_id, + NULL AS length_feet + FROM + feature_signals + WHERE + feature_signals.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_street_segments.component_id, + feature_street_segments.geography :: geometry, + feature_street_segments.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_street_segments + WHERE + feature_street_segments.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_intersections.component_id, + feature_intersections.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_intersections.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_intersections + WHERE + feature_intersections.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_points.component_id, + feature_drawn_points.geography :: geometry, + ST_ExteriorRing( + ST_Buffer(feature_drawn_points.geography, 7):: geometry + ) AS line_geography, + NULL AS signal_id, + NULL AS length_feet + FROM + feature_drawn_points + WHERE + feature_drawn_points.is_deleted = FALSE + UNION ALL + SELECT + id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography :: geometry, + feature_drawn_lines.geography :: geometry as line_geography, + NULL AS signal_id, + length_feet + FROM + feature_drawn_lines + WHERE + feature_drawn_lines.is_deleted = FALSE + ) feature_union + GROUP BY + component_id + ) comp_geography ON comp_geography.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group council districts by project component id + SELECT + component_id AS project_component_id, + STRING_AGG( + DISTINCT council_district_id :: text, + ', ' + ) AS council_districts + FROM + features_council_districts + LEFT JOIN features ON features.id = features_council_districts.feature_id + WHERE + features.is_deleted = FALSE + GROUP BY + component_id + ) council_districts ON council_districts.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group subcomponents by project component id + SELECT + project_component_id, + string_agg(ms.subcomponent_name, ', ') subcomponents + FROM + moped_proj_components_subcomponents mpcs + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + GROUP BY + project_component_id + ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group work types by project component id + SELECT + project_component_id, + string_agg(mwt.name, ', ') work_types + FROM + moped_proj_component_work_types mpcwt + LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + GROUP BY + project_component_id + ) work_types ON work_types.project_component_id = mpc.project_component_id + LEFT JOIN ( + -- group project component tags by project component id + SELECT + project_component_id, + string_agg(mct.type || ' - ' || mct.name, ', ') component_tags + FROM + moped_proj_component_tags mpct + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + GROUP BY + project_component_id + ) component_tags ON component_tags.project_component_id = mpc.project_component_id + LEFT JOIN project_list_view plv ON plv.project_id = mpc.project_id + LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.project_id + LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id + LEFT JOIN moped_components mc ON mc.component_id = mpc.component_id +WHERE + mpc.is_deleted = FALSE + AND plv.is_deleted = FALSE +); From 56f8f200a97914463eec21dac097635afa651f56 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 11 Jan 2024 15:05:21 -0500 Subject: [PATCH 38/53] just use a boring asterisk and the popover --- .../projects/projectView/ProjectPhases.js | 81 +++++++------------ 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 1525db6d81..707a0f161b 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -8,16 +8,11 @@ import { Typography, } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; -import { grey, green } from "@mui/material/colors"; +import { green } from "@mui/material/colors"; import { EditOutlined as EditOutlinedIcon, DeleteOutline as DeleteOutlineIcon, CheckCircleOutline, - HelpOutline, - EditCalendar, - EventAvailable, - PendingActions - } from "@mui/icons-material"; import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; @@ -29,7 +24,7 @@ import { useSubphaseNameLookup, } from "./ProjectPhase/helpers"; -const John = ({ children, isEnabled }) => { +const DateConfirmationPopover = ({ children, isEnabled, dateType }) => { const [anchorEl, setAnchorEl] = useState(null); const handlePopoverOpen = (event) => { @@ -57,17 +52,13 @@ const John = ({ children, isEnabled }) => { open={!!isEnabled && !!open} anchorEl={anchorEl} anchorOrigin={{ - vertical: "bottom", - horizontal: "left", - }} - transformOrigin={{ - vertical: "top", - horizontal: "left", + vertical: "center", + horizontal: "right", }} onClose={handlePopoverClose} disableRestoreFocus > - Estimated date + {`Estimated ${dateType} date`} ); @@ -98,35 +89,23 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => row.phase_start ? new Date(row.phase_start) : null, /** the renderCell function controls the react node rendered for this cell */ renderCell: ({ row }) => { - const strToRender = row.phase_start + let strToRender = row.phase_start ? new Date(row.phase_start).toLocaleDateString() : ""; - const showTentativeIcon = - !row.is_phase_start_confirmed && strToRender; - const showConfirmedIcon = row.is_phase_start_confirmed && strToRender; + const showNotConfirmedIndicator = + !row.is_phase_start_confirmed && strToRender; + strToRender = showNotConfirmedIndicator + ? `${strToRender}*` + : strToRender; return ( - - - {/* {showTentativeIcon && } - {!showTentativeIcon &&   } */} - - {strToRender} - - {/* {showTentativeIcon && (ETA)} */} - {showTentativeIcon && ( - - )} - {showConfirmedIcon && ( - - )} - - + + {strToRender} + ); }, minWidth: 150, @@ -140,21 +119,23 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => row.phase_end ? new Date(row.phase_end) : null, /** the renderCell function controls the react node rendered for this cell */ renderCell: ({ row }) => { - const strToRender = row.phase_end + let strToRender = row.phase_end ? new Date(row.phase_end).toLocaleDateString() : ""; - const showTentativeIcon = !row.is_phase_end_confirmed && strToRender; - const showConfirmedIcon = row.is_phase_end_confirmed && strToRender; + + const showNotConfirmedIndicator = + !row.is_phase_end_confirmed && strToRender; + + strToRender = showNotConfirmedIndicator + ? `${strToRender}*` + : strToRender; return ( - - {strToRender} - {/* {showTentativeIcon && ( - - )} - {showConfirmedIcon && ( - - )} */} - + + {strToRender} + ); }, minWidth: 150, From 7618883c62005edf19b404be0c071926fb69dc0a Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 11 Jan 2024 15:11:00 -0500 Subject: [PATCH 39/53] add comment about component view version --- .../1704744986000_substantial_completion_date/down.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/down.sql b/moped-database/migrations/1704744986000_substantial_completion_date/down.sql index 67a4428ef4..a657013843 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/down.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/down.sql @@ -1,4 +1,4 @@ ---revert to version 1700515730257_fix_delete_component_bug +-- revert to version 1700515730257_fix_delete_component_bug DROP VIEW public.project_list_view cascade; CREATE OR REPLACE VIEW public.project_list_view @@ -209,6 +209,7 @@ AS WITH project_person_list_lookup AS ( work_activities.task_order_names_short, work_activities.task_orders; +-- revert to version 1700515730257_fix_delete_component_bug CREATE OR REPLACE VIEW component_arcgis_online_view AS ( SELECT mpc.project_id, From 20fba7816ba75353944f5a98f8b7a42d35d98ed2 Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 12 Jan 2024 13:16:02 -0500 Subject: [PATCH 40/53] exclude deleted subcomponents and component tags --- .../1704744986000_substantial_completion_date/up.sql | 6 ++++-- moped-database/views/component_arcgis_online_view.sql | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql index 77342b7ad7..c11b23dab3 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql @@ -414,7 +414,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(ms.subcomponent_name, ', ') subcomponents FROM moped_proj_components_subcomponents mpcs - LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + WHERE mpcs.is_deleted = FALSE GROUP BY project_component_id ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id @@ -436,7 +437,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(mct.type || ' - ' || mct.name, ', ') component_tags FROM moped_proj_component_tags mpct - LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + WHERE mpct.is_deleted = FALSE GROUP BY project_component_id ) component_tags ON component_tags.project_component_id = mpc.project_component_id diff --git a/moped-database/views/component_arcgis_online_view.sql b/moped-database/views/component_arcgis_online_view.sql index 7bf63f91cd..adeabebf64 100644 --- a/moped-database/views/component_arcgis_online_view.sql +++ b/moped-database/views/component_arcgis_online_view.sql @@ -174,7 +174,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(ms.subcomponent_name, ', ') subcomponents FROM moped_proj_components_subcomponents mpcs - LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + WHERE mpcs.is_deleted = FALSE GROUP BY project_component_id ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id @@ -196,7 +197,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(mct.type || ' - ' || mct.name, ', ') component_tags FROM moped_proj_component_tags mpct - LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + WHERE mpct.is_deleted = FALSE GROUP BY project_component_id ) component_tags ON component_tags.project_component_id = mpc.project_component_id From 07324a144dccb9092d79cfe7c96588cceeb6b3b4 Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 12 Jan 2024 13:17:51 -0500 Subject: [PATCH 41/53] exclude deleted work types --- .../migrations/1704744986000_substantial_completion_date/up.sql | 1 + moped-database/views/component_arcgis_online_view.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql index c11b23dab3..bb11f65fc0 100644 --- a/moped-database/migrations/1704744986000_substantial_completion_date/up.sql +++ b/moped-database/migrations/1704744986000_substantial_completion_date/up.sql @@ -427,6 +427,7 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( FROM moped_proj_component_work_types mpcwt LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + WHERE mpcwt.is_deleted = FALSE GROUP BY project_component_id ) work_types ON work_types.project_component_id = mpc.project_component_id diff --git a/moped-database/views/component_arcgis_online_view.sql b/moped-database/views/component_arcgis_online_view.sql index adeabebf64..e6c8edc527 100644 --- a/moped-database/views/component_arcgis_online_view.sql +++ b/moped-database/views/component_arcgis_online_view.sql @@ -187,6 +187,7 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( FROM moped_proj_component_work_types mpcwt LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + WHERE mpcwt.is_deleted = FALSE GROUP BY project_component_id ) work_types ON work_types.project_component_id = mpc.project_component_id From 2807a6cb576eb6c490a4cc35374ce5f1506f04dd Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 12 Jan 2024 13:31:53 -0500 Subject: [PATCH 42/53] includes recent changes from substantial completion date PR --- .../migrations/1704906960001_phase_timestamps/down.sql | 8 ++++++-- .../migrations/1704906960001_phase_timestamps/up.sql | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/moped-database/migrations/1704906960001_phase_timestamps/down.sql b/moped-database/migrations/1704906960001_phase_timestamps/down.sql index 3871354056..ebc271f82b 100644 --- a/moped-database/migrations/1704906960001_phase_timestamps/down.sql +++ b/moped-database/migrations/1704906960001_phase_timestamps/down.sql @@ -284,6 +284,7 @@ AS WITH project_person_list_lookup AS ( work_activities.task_order_names_short, work_activities.task_orders; +-- add substantial_completion_date to agol view CREATE OR REPLACE VIEW component_arcgis_online_view AS ( SELECT mpc.project_id, @@ -457,7 +458,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(ms.subcomponent_name, ', ') subcomponents FROM moped_proj_components_subcomponents mpcs - LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + WHERE mpcs.is_deleted = FALSE GROUP BY project_component_id ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id @@ -469,6 +471,7 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( FROM moped_proj_component_work_types mpcwt LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + WHERE mpcwt.is_deleted = FALSE GROUP BY project_component_id ) work_types ON work_types.project_component_id = mpc.project_component_id @@ -479,7 +482,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(mct.type || ' - ' || mct.name, ', ') component_tags FROM moped_proj_component_tags mpct - LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + WHERE mpct.is_deleted = FALSE GROUP BY project_component_id ) component_tags ON component_tags.project_component_id = mpc.project_component_id diff --git a/moped-database/migrations/1704906960001_phase_timestamps/up.sql b/moped-database/migrations/1704906960001_phase_timestamps/up.sql index f67f3f651f..c7c63cb2b1 100644 --- a/moped-database/migrations/1704906960001_phase_timestamps/up.sql +++ b/moped-database/migrations/1704906960001_phase_timestamps/up.sql @@ -287,6 +287,7 @@ AS WITH project_person_list_lookup AS ( work_activities.task_order_names_short, work_activities.task_orders; +-- add substantial_completion_date to agol view CREATE OR REPLACE VIEW component_arcgis_online_view AS ( SELECT mpc.project_id, @@ -460,7 +461,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(ms.subcomponent_name, ', ') subcomponents FROM moped_proj_components_subcomponents mpcs - LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + LEFT JOIN moped_subcomponents ms ON mpcs.subcomponent_id = ms.subcomponent_id + WHERE mpcs.is_deleted = FALSE GROUP BY project_component_id ) subcomponents ON subcomponents.project_component_id = mpc.project_component_id @@ -472,6 +474,7 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( FROM moped_proj_component_work_types mpcwt LEFT JOIN moped_work_types mwt ON mpcwt.work_type_id = mwt.id + WHERE mpcwt.is_deleted = FALSE GROUP BY project_component_id ) work_types ON work_types.project_component_id = mpc.project_component_id @@ -482,7 +485,8 @@ CREATE OR REPLACE VIEW component_arcgis_online_view AS ( string_agg(mct.type || ' - ' || mct.name, ', ') component_tags FROM moped_proj_component_tags mpct - LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + LEFT JOIN moped_component_tags mct ON mpct.component_tag_id = mct.id + WHERE mpct.is_deleted = FALSE GROUP BY project_component_id ) component_tags ON component_tags.project_component_id = mpc.project_component_id From 070c7e171b0c98eb11199c09d33778ad781048b7 Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 12 Jan 2024 13:40:48 -0500 Subject: [PATCH 43/53] move popover to separate component --- .../ProjectPhaseDateConfirmationPopover.js | 49 ++++++++++++++++ .../projects/projectView/ProjectPhases.js | 57 ++----------------- 2 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 moped-editor/src/views/projects/projectView/ProjectPhaseDateConfirmationPopover.js diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseDateConfirmationPopover.js b/moped-editor/src/views/projects/projectView/ProjectPhaseDateConfirmationPopover.js new file mode 100644 index 0000000000..67eaa341a1 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseDateConfirmationPopover.js @@ -0,0 +1,49 @@ +import { useState } from "react"; +import { Popover, Typography } from "@mui/material"; + +/** + * Shows a popover indicating if a date is "estimated" + * @param {boolean} isEnabled - if the popover should be enabled/active + * @param {string} dataType - the date type that we be included in popover text. expecting `start` or `end` + */ +const DateConfirmationPopover = ({ children, isEnabled, dateType }) => { + const [anchorEl, setAnchorEl] = useState(null); + + const handlePopoverOpen = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handlePopoverClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + return ( +
+ {children} + + {`Estimated ${dateType} date`} + +
+ ); +}; + +export default DateConfirmationPopover; diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 707a0f161b..08cd050a04 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -1,12 +1,6 @@ import { useCallback, useMemo, useState } from "react"; import { useMutation } from "@apollo/client"; -import { - CircularProgress, - Box, - IconButton, - Popover, - Typography, -} from "@mui/material"; +import { CircularProgress, Box, IconButton } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; import { green } from "@mui/material/colors"; import { @@ -17,6 +11,7 @@ import { import ProjectPhaseToolbar from "./ProjectPhaseToolbar"; import PhaseTemplateModal from "./PhaseTemplateModal"; import ProjectPhaseDialog from "./ProjectPhaseDialog"; +import ProjectPhaseDateConfirmationPopover from "./ProjectPhaseDateConfirmationPopover"; import { DELETE_PROJECT_PHASE } from "src/queries/project"; import { useCurrentProjectPhaseIDs, @@ -24,46 +19,6 @@ import { useSubphaseNameLookup, } from "./ProjectPhase/helpers"; -const DateConfirmationPopover = ({ children, isEnabled, dateType }) => { - const [anchorEl, setAnchorEl] = useState(null); - - const handlePopoverOpen = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handlePopoverClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - return ( -
- {children} - - {`Estimated ${dateType} date`} - -
- ); -}; - /** Hook that provides memoized column settings */ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => useMemo(() => { @@ -100,12 +55,12 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => ? `${strToRender}*` : strToRender; return ( - {strToRender} - + ); }, minWidth: 150, @@ -130,12 +85,12 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => ? `${strToRender}*` : strToRender; return ( - {strToRender} - + ); }, minWidth: 150, From cd775874b7558e70dc5b382b68ded2ec17443216 Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 12 Jan 2024 13:52:30 -0500 Subject: [PATCH 44/53] update new project mutation --- .../src/views/projects/newProjectView/NewProjectView.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/moped-editor/src/views/projects/newProjectView/NewProjectView.js b/moped-editor/src/views/projects/newProjectView/NewProjectView.js index 14dc97d91f..47f4f3d0fd 100644 --- a/moped-editor/src/views/projects/newProjectView/NewProjectView.js +++ b/moped-editor/src/views/projects/newProjectView/NewProjectView.js @@ -7,7 +7,7 @@ import { Card, CardContent, } from "@mui/material"; -import makeStyles from '@mui/styles/makeStyles'; +import makeStyles from "@mui/styles/makeStyles"; import { format } from "date-fns"; import DefineProjectForm from "./DefineProjectForm"; import Page from "src/components/Page"; @@ -145,7 +145,8 @@ const NewProjectView = () => { { phase_id: 1, is_current_phase: true, - phase_start: format(Date.now(), "yyyy-MM-dd"), + phase_start: new Date(new Date().setHours(0, 0, 0, 0)), + is_phase_start_confirmed: true, }, ], }, From 98892dc964d8fa5630c69c7e62df5536056e4f8c Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 12 Jan 2024 14:15:13 -0500 Subject: [PATCH 45/53] fix default date confirmation effects --- .../projects/projectView/ProjectPhaseForm.js | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js index f01d751f94..92a2187ecb 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhaseForm.js @@ -79,9 +79,20 @@ const ProjectPhaseForm = ({ phase_start && new Date(phase_start).getTime() < new Date().getTime() ) { + // date is in the past, so default to confirmed setValue("is_phase_start_confirmed", true); + } else if ( + phase_start && + new Date(phase_start).getTime() > new Date().getTime() + ) { + // date is in the future, so default to not confirmed + setValue("is_phase_start_confirmed", false); } } + // clear confirmed status as needed + if (phase_start === null) { + setValue("is_phase_start_confirmed", false); + } }, [phase_start, defaultValues, setValue]); /** @@ -90,10 +101,21 @@ const ProjectPhaseForm = ({ useEffect(() => { if (phase_end !== defaultValues.phase_end) { // phase end has been edited - if (phase_end && new Date(phase_end).getTime() < new Date().getTime()) { + if (phase_end && new Date(phase_end).getTime() <= new Date().getTime()) { + // date is in the past, so default to confirmed setValue("is_phase_end_confirmed", true); + } else if ( + phase_end && + new Date(phase_end).getTime() > new Date().getTime() + ) { + // date is in the future, so default to not confirmed + setValue("is_phase_end_confirmed", false); } } + // clear confirmed status as needed + if (phase_end === null) { + setValue("is_phase_end_confirmed", false); + } }, [phase_end, defaultValues, setValue]); if (mutationState.error) { From 4198ea317efe33696e4db3dcef70075f1c809c02 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:12:58 -0500 Subject: [PATCH 46/53] add support for MUI props --- .../src/components/forms/ControlledCheckbox.js | 4 +++- .../src/components/forms/ControlledDateField.js | 10 +++++++++- moped-editor/src/components/forms/ControlledSwitch.js | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/moped-editor/src/components/forms/ControlledCheckbox.js b/moped-editor/src/components/forms/ControlledCheckbox.js index 0d66b9bd6c..34d14e3e71 100644 --- a/moped-editor/src/components/forms/ControlledCheckbox.js +++ b/moped-editor/src/components/forms/ControlledCheckbox.js @@ -8,9 +8,10 @@ import { Controller } from "react-hook-form"; * @param {object} control - react-hook-form `control` object from useController - required * @param {string} name - unique field name which be used in react-hook-form data object * @param {string} label - the label to render next to the checkbox + * @param {object} checkboxProps additional optional MUI checkbox props such as `sx` or `icon` * @return {JSX.Element} */ -const ControlledCheckbox = ({ name, control, label }) => { +const ControlledCheckbox = ({ name, control, label, ...checkBoxProps }) => { return ( { field.onChange(e.target.checked)} + {...checkBoxProps} /> } /> diff --git a/moped-editor/src/components/forms/ControlledDateField.js b/moped-editor/src/components/forms/ControlledDateField.js index e35fbf86f5..7f4b6cf257 100644 --- a/moped-editor/src/components/forms/ControlledDateField.js +++ b/moped-editor/src/components/forms/ControlledDateField.js @@ -18,9 +18,16 @@ const isValidDateStringOrObject = (value) => { * @param {string} name - unique field name which be used in react-hook-form data object * @param {string} label - the label to render next to the checkbox * @param {bool} error - if the error state is active (triggers red outline around textfield) + * @param {object} datePickerProps additional optional MUI date picker props * @return {JSX.Element} */ -const ControlledDateField = ({ name, control, label, error }) => { +const ControlledDateField = ({ + name, + control, + label, + error, + ...datePickerProps +}) => { return ( { : newValue; field.onChange(valueToStore); }} + {...datePickerProps} /> ); }} diff --git a/moped-editor/src/components/forms/ControlledSwitch.js b/moped-editor/src/components/forms/ControlledSwitch.js index eed0be6581..c49d4dc618 100644 --- a/moped-editor/src/components/forms/ControlledSwitch.js +++ b/moped-editor/src/components/forms/ControlledSwitch.js @@ -7,9 +7,10 @@ import FormControlLabel from "@mui/material/FormControlLabel"; * @param {object} control - react-hook-form `control` object from useController - required * @param {string} name - unique field name which be used in react-hook-form data object * @param {string} label - the label to render next to the checkbox + * @param {object} switchProps additional optional MUI switch props * @return {JSX.Element} */ -const ControlledSwitch = ({ name, control, label }) => { +const ControlledSwitch = ({ name, control, label, ...switchProps }) => { return ( { onChange={(e) => field.onChange(e.target.checked)} color="primary" inputProps={{ "aria-label": "primary checkbox" }} + {...switchProps} /> } /> From 1450c036df652adf56022eb25b5ad34a51f16c8e Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:13:28 -0500 Subject: [PATCH 47/53] set default value for current_phase_ids_to_clear variable --- moped-editor/src/queries/project.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moped-editor/src/queries/project.js b/moped-editor/src/queries/project.js index f0ec954912..32c9c5e8f0 100644 --- a/moped-editor/src/queries/project.js +++ b/moped-editor/src/queries/project.js @@ -328,7 +328,7 @@ export const TIMELINE_QUERY = gql` export const ADD_PROJECT_PHASE = gql` mutation AddProjectPhase( $objects: [moped_proj_phases_insert_input!]! - $current_phase_ids_to_clear: [Int!]! + $current_phase_ids_to_clear: [Int!] = [] ) { insert_moped_proj_phases(objects: $objects) { returning { @@ -355,7 +355,7 @@ export const UPDATE_PROJECT_PHASE = gql` mutation ProjectPhasesMutation( $project_phase_id: Int! $object: moped_proj_phases_set_input! - $current_phase_ids_to_clear: [Int!]! + $current_phase_ids_to_clear: [Int!] = [] ) { update_moped_proj_phases_by_pk( pk_columns: { project_phase_id: $project_phase_id } From 49951fbca7986fe176120671adbfd4fa160dd528 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:17:46 -0500 Subject: [PATCH 48/53] tweak yup schema: use phase_description and remove useless error message --- .../src/views/projects/projectView/ProjectPhase/helpers.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index 1bdd4e2dbd..47ca158019 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -2,10 +2,7 @@ import { useMemo } from "react"; import * as yup from "yup"; export const phaseValidationSchema = yup.object().shape({ - phase_id: yup - .number("Phase is required") - .nullable() - .required("Phase is required"), + phase_id: yup.number().nullable().required("Phase is required"), subphase_id: yup.number().nullable().optional(), phase_start: yup .date() @@ -21,7 +18,7 @@ export const phaseValidationSchema = yup.object().shape({ is_current_phase: yup.boolean(), is_phase_start_confirmed: yup.boolean(), is_phase_end_confirmed: yup.boolean(), - description: yup + phase_description: yup .string() .max(500, "Must be less than 500 characters") .nullable(), From bf086f9b0ee2aa28da2c4f99f2d6c5f1e484c1eb Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:27:16 -0500 Subject: [PATCH 49/53] nix the column menu thingy --- moped-editor/src/views/projects/projectView/ProjectPhases.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhases.js b/moped-editor/src/views/projects/projectView/ProjectPhases.js index 08cd050a04..1e01a286c5 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhases.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhases.js @@ -116,8 +116,6 @@ const useColumns = ({ deleteInProgress, onDeletePhase, setEditPhase }) => { headerName: "", field: "_edit", - hideable: false, - filterable: false, sortable: false, renderCell: ({ row }) => { return deleteInProgress ? ( @@ -200,6 +198,7 @@ const ProjectPhases = ({ projectId, data, refetch }) => { density="comfortable" getRowId={(row) => row.project_phase_id} disableRowSelectionOnClick + disableColumnMenu getRowHeight={() => "auto"} hideFooterPagination={true} localeText={{ noRowsLabel: "No phases" }} From 67b0ce74e9d3acdb9157336ed304dec8c50a123d Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:28:20 -0500 Subject: [PATCH 50/53] fix docstring typo --- .../src/views/projects/projectView/ProjectPhase/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index 47ca158019..cfb696553e 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -109,7 +109,7 @@ export const useSubphaseNameLookup = (subphases) => ); /** - * Hoolk which returns an array of project_phase_ids of the project's current phase(s). + * Hook which returns an array of project_phase_ids of the project's current phase(s). * Although only one phase should ever be current, we handle the possibilty that there * are multiple * @param {Array} projectPhases - array of this project's moped_proj_phases From 5ebea86f5c48a479e87d90db0634a63779e3de0e Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:31:16 -0500 Subject: [PATCH 51/53] destructure instead of delete --- .../src/views/projects/projectView/ProjectPhase/helpers.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js index cfb696553e..2bbf909812 100644 --- a/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js +++ b/moped-editor/src/views/projects/projectView/ProjectPhase/helpers.js @@ -156,8 +156,7 @@ export const onSubmitPhase = ({ currentPhaseIdsToClear, onSubmitCallback, }) => { - const { project_phase_id } = data; - delete data.project_phase_id; + const { project_phase_id, ...formData } = data; const variables = { current_phase_ids_to_clear: currentPhaseIdsToClear, @@ -167,10 +166,10 @@ export const onSubmitPhase = ({ // inserting a new mutation - which has a slightly different // variable shape bc the mutation supports multiple inserts // via the phase template feature - variables.objects = [data]; + variables.objects = [formData]; } else { variables.project_phase_id = project_phase_id; - variables.object = data; + variables.object = formData; } mutate({ From 15ec91dbcd897cbd7df41c1733aafda92c425c50 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 12:49:17 -0500 Subject: [PATCH 52/53] remove unused datefns import --- moped-editor/src/views/projects/newProjectView/NewProjectView.js | 1 - 1 file changed, 1 deletion(-) diff --git a/moped-editor/src/views/projects/newProjectView/NewProjectView.js b/moped-editor/src/views/projects/newProjectView/NewProjectView.js index 47f4f3d0fd..30dbe43493 100644 --- a/moped-editor/src/views/projects/newProjectView/NewProjectView.js +++ b/moped-editor/src/views/projects/newProjectView/NewProjectView.js @@ -8,7 +8,6 @@ import { CardContent, } from "@mui/material"; import makeStyles from "@mui/styles/makeStyles"; -import { format } from "date-fns"; import DefineProjectForm from "./DefineProjectForm"; import Page from "src/components/Page"; import { useQuery, useMutation } from "@apollo/client"; From 4ebdacd9ff13da7dee63f669a2ad53e5c2c30300 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 18 Jan 2024 13:23:09 -0500 Subject: [PATCH 53/53] add substantial_completion_date to AGOL ETL query --- moped-etl/arcgis/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moped-etl/arcgis/settings.py b/moped-etl/arcgis/settings.py index 6ce300b452..e1a4484e7c 100644 --- a/moped-etl/arcgis/settings.py +++ b/moped-etl/arcgis/settings.py @@ -56,6 +56,7 @@ signal_ids srts_id subcomponents + substantial_completion_date task_order_names type_name updated_at