diff --git a/moped-database/metadata/tables.yaml b/moped-database/metadata/tables.yaml index 2947ab0dab..1f60ea359e 100644 --- a/moped-database/metadata/tables.yaml +++ b/moped-database/metadata/tables.yaml @@ -5303,14 +5303,6 @@ - project_council_districts - project_description - project_designer - - project_development_status - - project_development_status_date - - project_development_status_date_calendar_year - - project_development_status_date_calendar_year_month - - project_development_status_date_calendar_year_month_numeric - - project_development_status_date_calendar_year_quarter - - project_development_status_date_fiscal_year - - project_development_status_date_fiscal_year_quarter - project_feature - project_id - project_inspector @@ -5363,14 +5355,6 @@ - project_council_districts - project_description - project_designer - - project_development_status - - project_development_status_date - - project_development_status_date_calendar_year - - project_development_status_date_calendar_year_month - - project_development_status_date_calendar_year_month_numeric - - project_development_status_date_calendar_year_quarter - - project_development_status_date_fiscal_year - - project_development_status_date_fiscal_year_quarter - project_feature - project_id - project_inspector @@ -5423,14 +5407,6 @@ - project_council_districts - project_description - project_designer - - project_development_status - - project_development_status_date - - project_development_status_date_calendar_year - - project_development_status_date_calendar_year_month - - project_development_status_date_calendar_year_month_numeric - - project_development_status_date_calendar_year_quarter - - project_development_status_date_fiscal_year - - project_development_status_date_fiscal_year_quarter - project_feature - project_id - project_inspector diff --git a/moped-database/migrations/1719872865109_add_work_plan_category/down.sql b/moped-database/migrations/1719872865109_add_work_plan_category/down.sql new file mode 100644 index 0000000000..5a0f2e92d8 --- /dev/null +++ b/moped-database/migrations/1719872865109_add_work_plan_category/down.sql @@ -0,0 +1,526 @@ +DROP VIEW IF EXISTS component_arcgis_online_view; +DROP VIEW IF EXISTS project_list_view; + +DROP FUNCTION IF EXISTS get_project_development_status; +DROP FUNCTION IF EXISTS get_project_development_status_date; + +CREATE OR REPLACE VIEW 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 AS mpp + INNER JOIN moped_users AS mu ON mpp.user_id = mu.user_id + INNER JOIN moped_proj_personnel_roles AS mppr ON mpp.project_personnel_id = mppr.project_personnel_id + INNER JOIN moped_project_roles AS 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.project_id, + string_agg(DISTINCT mfs.funding_source_name, ', '::text ORDER BY mfs.funding_source_name) AS funding_source_name, + string_agg( + DISTINCT + CASE + WHEN mfs.funding_source_name IS NOT null AND mfp.funding_program_name IS NOT null THEN concat(mfs.funding_source_name, ' - ', mfp.funding_program_name) + WHEN mfs.funding_source_name IS NOT null THEN mfs.funding_source_name + WHEN mfp.funding_program_name IS NOT null THEN mfp.funding_program_name + ELSE null::text + END, ', '::text ORDER BY ( + CASE + WHEN mfs.funding_source_name IS NOT null AND mfp.funding_program_name IS NOT null THEN concat(mfs.funding_source_name, ' - ', mfp.funding_program_name) + WHEN mfs.funding_source_name IS NOT null THEN mfs.funding_source_name + WHEN mfp.funding_program_name IS NOT null THEN mfp.funding_program_name + ELSE null::text + END + ) + ) AS funding_source_and_program_names + FROM moped_proj_funding AS mpf + LEFT JOIN moped_fund_sources AS mfs ON mpf.funding_source_id = mfs.funding_source_id + LEFT JOIN moped_fund_programs AS mfp ON mpf.funding_program_id = mfp.funding_program_id + WHERE mpf.is_deleted = false + GROUP BY mpf.project_id +), + +project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types AS mpt + LEFT JOIN moped_types AS 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 + INNER JOIN moped_project AS parent ON children.parent_project_id = parent.project_id + WHERE children.is_deleted = false + GROUP BY children.parent_project_id +), + +work_activities AS ( + SELECT + mpwa.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 AS mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) AS 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 AS mpc + LEFT JOIN moped_components AS mc ON mpc.component_id = mc.component_id + WHERE mpc.is_deleted = false + GROUP BY mpc.project_id +), + +project_district_association AS ( + WITH project_council_district_map AS ( + SELECT DISTINCT + moped_project.project_id, + features_council_districts.council_district_id + FROM moped_project + LEFT JOIN moped_proj_components ON moped_project.project_id = moped_proj_components.project_id + LEFT JOIN features ON moped_proj_components.project_component_id = features.component_id + LEFT JOIN features_council_districts ON features.id = features_council_districts.feature_id + WHERE features.is_deleted IS false AND moped_proj_components.is_deleted IS false + ), + + parent_child_project_map AS ( + SELECT + parent_projects.project_id, + unnest(ARRAY[parent_projects.project_id] || array_agg(child_projects.project_id)) AS self_and_children_project_ids + FROM moped_project AS parent_projects + LEFT JOIN moped_project AS child_projects ON parent_projects.project_id = child_projects.parent_project_id + GROUP BY parent_projects.project_id + ORDER BY parent_projects.project_id + ) + + SELECT + projects.project_id, + array_agg(DISTINCT project_districts.council_district_id) FILTER (WHERE project_districts.council_district_id IS NOT null) AS project_council_districts, + array_agg(DISTINCT project_and_children_districts.council_district_id) FILTER (WHERE project_and_children_districts.council_district_id IS NOT null) AS project_and_child_project_council_districts + FROM parent_child_project_map AS projects + LEFT JOIN project_council_district_map AS project_and_children_districts ON projects.self_and_children_project_ids = project_and_children_districts.project_id + LEFT JOIN project_council_district_map AS project_districts ON projects.project_id = project_districts.project_id + GROUP BY projects.project_id +), + +min_confirmed_phase_dates AS ( + WITH min_dates AS ( + SELECT + phases.project_id, + min(phases.phase_start) AS min_date + FROM moped_proj_phases AS 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 moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + UNION ALL + SELECT + phases.project_id, + min(phases.phase_end) AS min_date + FROM moped_proj_phases AS 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 moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + ) + + SELECT + min_dates.project_id, + min(min_dates.min_date) AS min_phase_date + FROM min_dates + GROUP BY min_dates.project_id +), + +min_estimated_phase_dates AS ( + WITH min_dates AS ( + SELECT + phases.project_id, + min(phases.phase_start) AS min_date + FROM moped_proj_phases AS 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 = false AND moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + UNION ALL + SELECT + phases.project_id, + min(phases.phase_end) AS min_date + FROM moped_proj_phases AS 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 = false AND moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + ) + + SELECT + min_dates.project_id, + min(min_dates.min_date) AS min_phase_date + FROM min_dates + GROUP BY min_dates.project_id +) + +SELECT + mp.project_id, + mp.project_name_full, + mp.project_name, + mp.project_name_secondary, + mp.project_description, + mp.ecapris_subproject_id, + mp.project_website, + 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, + 'https://mobility.austin.gov/moped/projects/'::text || mp.project_id::text AS project_url, + 'https://mobility.austin.gov/moped/projects/'::text || mp.parent_project_id::text AS parent_project_url, + proj_status_update.project_note AS project_status_update, + proj_status_update.date_created AS project_status_update_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, + 'placeholder text'::text AS project_development_status, + '2024-01-01T00:00:00-06:00'::text AS project_development_status_date, + 9999 AS project_development_status_date_calendar_year, + 'placeholder text'::text AS project_development_status_date_calendar_year_month, + 'placeholder text'::text AS project_development_status_date_calendar_year_month_numeric, + 'placeholder text'::text AS project_development_status_date_calendar_year_quarter, + 999 AS project_development_status_date_fiscal_year, + 'placeholder text'::text AS project_development_status_date_fiscal_year_quarter, + ( + SELECT moped_project.project_name_full + FROM moped_project + WHERE moped_project.project_id = mp.parent_project_id + ) AS parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partners, + ( + 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)) AS json_agg + FROM moped_proj_components AS components + LEFT JOIN feature_signals ON components.project_component_id = feature_signals.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, + fsl.funding_source_and_program_names, + ptl.type_name, + ( + SELECT min(phases.phase_start) AS min + FROM moped_proj_phases AS phases + WHERE true AND phases.project_id = mp.project_id AND phases.phase_id = 9 AND phases.is_deleted = false + ) AS construction_start_date, + ( + SELECT max(phases.phase_end) AS max + FROM moped_proj_phases AS phases + WHERE true AND phases.project_id = mp.project_id AND phases.phase_id = 11 AND phases.is_deleted = false + ) AS completion_end_date, + mcpd.min_phase_date AS substantial_completion_date, + CASE + WHEN mcpd.min_phase_date IS NOT null THEN null::timestamp with time zone + ELSE mepd.min_phase_date + END AS substantial_completion_date_estimated, + ( + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel AS mpp + INNER JOIN moped_users AS users ON mpp.user_id = users.user_id + INNER JOIN moped_proj_personnel_roles AS mppr ON mpp.project_personnel_id = mppr.project_personnel_id + INNER JOIN moped_project_roles AS 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, + ( + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel AS mpp + INNER JOIN moped_users AS users ON mpp.user_id = users.user_id + INNER JOIN moped_proj_personnel_roles AS mppr ON mpp.project_personnel_id = mppr.project_personnel_id + INNER JOIN moped_project_roles AS 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, + ( + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags AS ptags + INNER JOIN moped_tags AS 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, + districts.project_council_districts, + districts.project_and_child_project_council_districts +FROM moped_project AS mp +LEFT JOIN project_person_list_lookup AS ppll ON mp.project_id = ppll.project_id +LEFT JOIN funding_sources_lookup AS fsl ON mp.project_id = fsl.project_id +LEFT JOIN project_type_lookup AS ptl ON mp.project_id = ptl.project_id +LEFT JOIN moped_entity AS me ON mp.project_sponsor = me.entity_id +LEFT JOIN moped_entity AS mel ON mp.project_lead_id = mel.entity_id +LEFT JOIN moped_proj_partners AS mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false +LEFT JOIN moped_entity AS me2 ON mpp2.entity_id = me2.entity_id +LEFT JOIN work_activities ON mp.project_id = work_activities.project_id +LEFT JOIN moped_users AS added_by_user ON mp.added_by = added_by_user.user_id +LEFT JOIN current_phase_view AS current_phase ON mp.project_id = current_phase.project_id +LEFT JOIN moped_public_process_statuses AS mpps ON mp.public_process_status_id = mpps.id +LEFT JOIN child_project_lookup AS cpl ON mp.project_id = cpl.parent_id +LEFT JOIN moped_proj_components_subtypes AS mpcs ON mp.project_id = mpcs.project_id +LEFT JOIN project_district_association AS districts ON mp.project_id = districts.project_id +LEFT JOIN min_confirmed_phase_dates AS mcpd ON mp.project_id = mcpd.project_id +LEFT JOIN min_estimated_phase_dates AS mepd ON mp.project_id = mepd.project_id +LEFT JOIN LATERAL ( + SELECT + mpn.project_note, + mpn.created_at AS date_created + FROM moped_proj_notes AS mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.created_at DESC + LIMIT 1 +) AS proj_status_update 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, fsl.funding_source_and_program_names, added_by_user.first_name, added_by_user.last_name, mpps.name, cpl.children_project_ids, proj_status_update.project_note, proj_status_update.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, districts.project_council_districts, districts.project_and_child_project_council_districts, mepd.min_phase_date, mcpd.min_phase_date; + + +-- Most recent migration: moped-database/migrations/1719582711923_refine_funding_source/up.sql + +CREATE OR REPLACE VIEW component_arcgis_online_view AS WITH work_types AS ( + SELECT + mpcwt.project_component_id, + string_agg(mwt.name, ', '::text) AS work_types + FROM moped_proj_component_work_types AS mpcwt + LEFT JOIN moped_work_types AS mwt ON mpcwt.work_type_id = mwt.id + WHERE mpcwt.is_deleted = false + GROUP BY mpcwt.project_component_id +), + +council_districts AS ( + SELECT + features.component_id AS project_component_id, + string_agg(DISTINCT features_council_districts.council_district_id::text, ', '::text) AS council_districts, + string_agg(DISTINCT lpad(features_council_districts.council_district_id::text, 2, '0'::text), ', '::text) AS council_districts_searchable + FROM features_council_districts + LEFT JOIN features ON features_council_districts.feature_id = features.id + WHERE features.is_deleted = false + GROUP BY features.component_id +), + +comp_geography AS ( + SELECT + feature_union.component_id AS project_component_id, + string_agg(DISTINCT feature_union.id::text, ', '::text) AS feature_ids, + st_asgeojson(st_union(array_agg(feature_union.geography)))::json AS geometry, + st_asgeojson(st_union(array_agg(feature_union.line_geography)))::json AS line_geometry, + string_agg(DISTINCT feature_union.signal_id::text, ', '::text) AS signal_ids, + sum(feature_union.length_feet) AS length_feet_total + FROM ( + SELECT + feature_signals.id, + feature_signals.component_id, + feature_signals.geography::geometry AS geography, + st_exteriorring(st_buffer(feature_signals.geography, 7::double precision)::geometry) AS line_geography, + feature_signals.signal_id, + null::integer AS length_feet + FROM feature_signals + WHERE feature_signals.is_deleted = false + UNION ALL + SELECT + feature_street_segments.id, + feature_street_segments.component_id, + feature_street_segments.geography::geometry AS geography, + feature_street_segments.geography::geometry AS line_geography, + null::integer AS signal_id, + feature_street_segments.length_feet + FROM feature_street_segments + WHERE feature_street_segments.is_deleted = false + UNION ALL + SELECT + feature_intersections.id, + feature_intersections.component_id, + feature_intersections.geography::geometry AS geography, + st_exteriorring(st_buffer(feature_intersections.geography, 7::double precision)::geometry) AS line_geography, + null::integer AS signal_id, + null::integer AS length_feet + FROM feature_intersections + WHERE feature_intersections.is_deleted = false + UNION ALL + SELECT + feature_drawn_points.id, + feature_drawn_points.component_id, + feature_drawn_points.geography::geometry AS geography, + st_exteriorring(st_buffer(feature_drawn_points.geography, 7::double precision)::geometry) AS line_geography, + null::integer AS signal_id, + null::integer AS length_feet + FROM feature_drawn_points + WHERE feature_drawn_points.is_deleted = false + UNION ALL + SELECT + feature_drawn_lines.id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography::geometry AS geography, + feature_drawn_lines.geography::geometry AS line_geography, + null::integer AS signal_id, + feature_drawn_lines.length_feet + FROM feature_drawn_lines + WHERE feature_drawn_lines.is_deleted = false + ) AS feature_union + GROUP BY feature_union.component_id +), + +subcomponents AS ( + SELECT + mpcs.project_component_id, + string_agg(ms.subcomponent_name, ', '::text) AS subcomponents + FROM moped_proj_components_subcomponents AS mpcs + LEFT JOIN moped_subcomponents AS ms ON mpcs.subcomponent_id = ms.subcomponent_id + WHERE mpcs.is_deleted = false + GROUP BY mpcs.project_component_id +), + +component_tags AS ( + SELECT + mpct.project_component_id, + string_agg((mct.type || ' - '::text) || mct.name, ', '::text) AS component_tags + FROM moped_proj_component_tags AS mpct + LEFT JOIN moped_component_tags AS mct ON mpct.component_tag_id = mct.id + WHERE mpct.is_deleted = false + GROUP BY mpct.project_component_id +), + +related_projects AS ( + SELECT + pmp.project_id, + concat_ws(', '::text, pmp.project_id, string_agg(cmp.project_id::text, ', '::text)) AS related_project_ids_with_self, + concat_ws(', '::text, lpad(pmp.project_id::text, 5, '0'::text), string_agg(lpad(cmp.project_id::text, 5, '0'::text), ', '::text)) AS related_project_ids_searchable_with_self + FROM moped_project AS pmp + LEFT JOIN moped_project AS cmp ON pmp.project_id = cmp.parent_project_id + WHERE cmp.is_deleted = false + GROUP BY pmp.project_id +), + +min_phase_dates AS ( + WITH min_dates AS ( + SELECT + mpp.project_id, + min(mpp.phase_start) AS min_phase_start, + min(mpp.phase_end) AS min_phase_end + FROM moped_proj_phases AS mpp + LEFT JOIN moped_phases AS mp ON mpp.phase_id = mp.phase_id + WHERE mpp.is_phase_end_confirmed = false AND mpp.is_phase_start_confirmed = false AND mpp.is_deleted = false AND mp.phase_name_simple = 'Complete'::text + GROUP BY mpp.project_id + ) + + SELECT + min_dates.project_id, + least(min_dates.min_phase_start, min_dates.min_phase_end) AS min_phase_date + FROM min_dates +) + +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, + council_districts.council_districts_searchable, + NOT coalesce(council_districts.council_districts IS null OR council_districts.council_districts = ''::text, false) AS is_within_city_limits, + comp_geography.length_feet_total, + round(comp_geography.length_feet_total::numeric / 5280::numeric, 2) AS length_miles_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + 'placeholder text'::text AS component_categories, + subcomponents.subcomponents AS component_subcomponents, + work_types.work_types AS component_work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.interim_project_component_id, + mpc.completion_date, + coalesce(mpc.completion_date, plv.substantial_completion_date) AS substantial_completion_date, + plv.substantial_completion_date_estimated, + mpc.srts_id, + mpc.location_description AS component_location_description, + plv.project_name, + plv.project_name_secondary, + plv.project_name_full, + plv.project_description, + plv.ecapris_subproject_id, + plv.project_website, + plv.updated_at AS project_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_partners, + plv.task_order_names, + plv.funding_source_name, + plv.funding_source_and_program_names AS funding_sources, + plv.type_name, + plv.project_status_update, + plv.project_status_update_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.parent_project_id, + plv.parent_project_name, + plv.parent_project_url, + plv.parent_project_name AS parent_project_name_full, + rp.related_project_ids_with_self AS related_project_ids, + rp.related_project_ids_searchable_with_self AS related_project_ids_searchable, + plv.knack_project_id AS knack_data_tracker_project_record_id, + plv.project_url, + (plv.project_url || '?tab=map&project_component_id='::text) || mpc.project_component_id::text AS component_url, + plv.project_development_status, + plv.project_development_status_date, + plv.project_development_status_date_calendar_year, + plv.project_development_status_date_calendar_year_month, + plv.project_development_status_date_calendar_year_month_numeric, + plv.project_development_status_date_calendar_year_quarter, + plv.project_development_status_date_fiscal_year, + plv.project_development_status_date_fiscal_year_quarter, + plv.added_by AS project_added_by +FROM moped_proj_components AS mpc +LEFT JOIN comp_geography ON mpc.project_component_id = comp_geography.project_component_id +LEFT JOIN council_districts ON mpc.project_component_id = council_districts.project_component_id +LEFT JOIN subcomponents ON mpc.project_component_id = subcomponents.project_component_id +LEFT JOIN work_types ON mpc.project_component_id = work_types.project_component_id +LEFT JOIN component_tags ON mpc.project_component_id = component_tags.project_component_id +LEFT JOIN project_list_view AS plv ON mpc.project_id = plv.project_id +LEFT JOIN current_phase_view AS current_phase ON mpc.project_id = current_phase.project_id +LEFT JOIN moped_phases AS mph ON mpc.phase_id = mph.phase_id +LEFT JOIN moped_components AS mc ON mpc.component_id = mc.component_id +LEFT JOIN related_projects AS rp ON mpc.project_id = rp.project_id +LEFT JOIN min_phase_dates AS mpd ON mpc.project_id = mpd.project_id +WHERE mpc.is_deleted = false AND plv.is_deleted = false; diff --git a/moped-database/migrations/1719872865109_add_work_plan_category/up.sql b/moped-database/migrations/1719872865109_add_work_plan_category/up.sql new file mode 100644 index 0000000000..dc6df428b5 --- /dev/null +++ b/moped-database/migrations/1719872865109_add_work_plan_category/up.sql @@ -0,0 +1,562 @@ +DROP VIEW IF EXISTS component_arcgis_online_view; +DROP VIEW IF EXISTS project_list_view; + +-- Create view again without project_development_status columns that are now moved to the component level in component_arcgis_online_view +CREATE OR REPLACE VIEW 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 AS mpp + INNER JOIN moped_users AS mu ON mpp.user_id = mu.user_id + INNER JOIN moped_proj_personnel_roles AS mppr ON mpp.project_personnel_id = mppr.project_personnel_id + INNER JOIN moped_project_roles AS 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.project_id, + string_agg(DISTINCT mfs.funding_source_name, ', '::text ORDER BY mfs.funding_source_name) AS funding_source_name, + string_agg( + DISTINCT + CASE + WHEN mfs.funding_source_name IS NOT null AND mfp.funding_program_name IS NOT null THEN concat(mfs.funding_source_name, ' - ', mfp.funding_program_name) + WHEN mfs.funding_source_name IS NOT null THEN mfs.funding_source_name + WHEN mfp.funding_program_name IS NOT null THEN mfp.funding_program_name + ELSE null::text + END, ', '::text ORDER BY ( + CASE + WHEN mfs.funding_source_name IS NOT null AND mfp.funding_program_name IS NOT null THEN concat(mfs.funding_source_name, ' - ', mfp.funding_program_name) + WHEN mfs.funding_source_name IS NOT null THEN mfs.funding_source_name + WHEN mfp.funding_program_name IS NOT null THEN mfp.funding_program_name + ELSE null::text + END + ) + ) AS funding_source_and_program_names + FROM moped_proj_funding AS mpf + LEFT JOIN moped_fund_sources AS mfs ON mpf.funding_source_id = mfs.funding_source_id + LEFT JOIN moped_fund_programs AS mfp ON mpf.funding_program_id = mfp.funding_program_id + WHERE mpf.is_deleted = false + GROUP BY mpf.project_id +), + +project_type_lookup AS ( + SELECT + mpt.project_id, + string_agg(mt.type_name, ', '::text) AS type_name + FROM moped_project_types AS mpt + LEFT JOIN moped_types AS 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 + INNER JOIN moped_project AS parent ON children.parent_project_id = parent.project_id + WHERE children.is_deleted = false + GROUP BY children.parent_project_id +), + +work_activities AS ( + SELECT + mpwa.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 AS mpwa + LEFT JOIN LATERAL jsonb_array_elements(mpwa.task_orders) AS 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 AS mpc + LEFT JOIN moped_components AS mc ON mpc.component_id = mc.component_id + WHERE mpc.is_deleted = false + GROUP BY mpc.project_id +), + +project_district_association AS ( + WITH project_council_district_map AS ( + SELECT DISTINCT + moped_project.project_id, + features_council_districts.council_district_id + FROM moped_project + LEFT JOIN moped_proj_components ON moped_project.project_id = moped_proj_components.project_id + LEFT JOIN features ON moped_proj_components.project_component_id = features.component_id + LEFT JOIN features_council_districts ON features.id = features_council_districts.feature_id + WHERE features.is_deleted IS false AND moped_proj_components.is_deleted IS false + ), + + parent_child_project_map AS ( + SELECT + parent_projects.project_id, + unnest(ARRAY[parent_projects.project_id] || array_agg(child_projects.project_id)) AS self_and_children_project_ids + FROM moped_project AS parent_projects + LEFT JOIN moped_project AS child_projects ON parent_projects.project_id = child_projects.parent_project_id + GROUP BY parent_projects.project_id + ORDER BY parent_projects.project_id + ) + + SELECT + projects.project_id, + array_agg(DISTINCT project_districts.council_district_id) FILTER (WHERE project_districts.council_district_id IS NOT null) AS project_council_districts, + array_agg(DISTINCT project_and_children_districts.council_district_id) FILTER (WHERE project_and_children_districts.council_district_id IS NOT null) AS project_and_child_project_council_districts + FROM parent_child_project_map AS projects + LEFT JOIN project_council_district_map AS project_and_children_districts ON projects.self_and_children_project_ids = project_and_children_districts.project_id + LEFT JOIN project_council_district_map AS project_districts ON projects.project_id = project_districts.project_id + GROUP BY projects.project_id +), + +min_confirmed_phase_dates AS ( + WITH min_dates AS ( + SELECT + phases.project_id, + min(phases.phase_start) AS min_date + FROM moped_proj_phases AS 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 moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + UNION ALL + SELECT + phases.project_id, + min(phases.phase_end) AS min_date + FROM moped_proj_phases AS 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 moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + ) + + SELECT + min_dates.project_id, + min(min_dates.min_date) AS min_phase_date + FROM min_dates + GROUP BY min_dates.project_id +), + +min_estimated_phase_dates AS ( + WITH min_dates AS ( + SELECT + phases.project_id, + min(phases.phase_start) AS min_date + FROM moped_proj_phases AS 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 = false AND moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + UNION ALL + SELECT + phases.project_id, + min(phases.phase_end) AS min_date + FROM moped_proj_phases AS 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 = false AND moped_phases.phase_name_simple = 'Complete'::text AND phases.is_deleted = false + GROUP BY phases.project_id + ) + + SELECT + min_dates.project_id, + min(min_dates.min_date) AS min_phase_date + FROM min_dates + GROUP BY min_dates.project_id +) + +SELECT + mp.project_id, + mp.project_name_full, + mp.project_name, + mp.project_name_secondary, + mp.project_description, + mp.ecapris_subproject_id, + mp.project_website, + 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, + 'https://mobility.austin.gov/moped/projects/'::text || mp.project_id::text AS project_url, + 'https://mobility.austin.gov/moped/projects/'::text || mp.parent_project_id::text AS parent_project_url, + proj_status_update.project_note AS project_status_update, + proj_status_update.date_created AS project_status_update_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 moped_project.project_name_full + FROM moped_project + WHERE moped_project.project_id = mp.parent_project_id + ) AS parent_project_name, + cpl.children_project_ids, + string_agg(DISTINCT me2.entity_name, ', '::text) AS project_partners, + ( + 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)) AS json_agg + FROM moped_proj_components AS components + LEFT JOIN feature_signals ON components.project_component_id = feature_signals.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, + fsl.funding_source_and_program_names, + ptl.type_name, + ( + SELECT min(phases.phase_start) AS min + FROM moped_proj_phases AS phases + WHERE true AND phases.project_id = mp.project_id AND phases.phase_id = 9 AND phases.is_deleted = false + ) AS construction_start_date, + ( + SELECT max(phases.phase_end) AS max + FROM moped_proj_phases AS phases + WHERE true AND phases.project_id = mp.project_id AND phases.phase_id = 11 AND phases.is_deleted = false + ) AS completion_end_date, + mcpd.min_phase_date AS substantial_completion_date, + CASE + WHEN mcpd.min_phase_date IS NOT null THEN null::timestamp with time zone + ELSE mepd.min_phase_date + END AS substantial_completion_date_estimated, + ( + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel AS mpp + INNER JOIN moped_users AS users ON mpp.user_id = users.user_id + INNER JOIN moped_proj_personnel_roles AS mppr ON mpp.project_personnel_id = mppr.project_personnel_id + INNER JOIN moped_project_roles AS 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, + ( + SELECT string_agg(concat(users.first_name, ' ', users.last_name), ', '::text) AS string_agg + FROM moped_proj_personnel AS mpp + INNER JOIN moped_users AS users ON mpp.user_id = users.user_id + INNER JOIN moped_proj_personnel_roles AS mppr ON mpp.project_personnel_id = mppr.project_personnel_id + INNER JOIN moped_project_roles AS 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, + ( + SELECT string_agg(tags.name, ', '::text) AS string_agg + FROM moped_proj_tags AS ptags + INNER JOIN moped_tags AS 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, + districts.project_council_districts, + districts.project_and_child_project_council_districts +FROM moped_project AS mp +LEFT JOIN project_person_list_lookup AS ppll ON mp.project_id = ppll.project_id +LEFT JOIN funding_sources_lookup AS fsl ON mp.project_id = fsl.project_id +LEFT JOIN project_type_lookup AS ptl ON mp.project_id = ptl.project_id +LEFT JOIN moped_entity AS me ON mp.project_sponsor = me.entity_id +LEFT JOIN moped_entity AS mel ON mp.project_lead_id = mel.entity_id +LEFT JOIN moped_proj_partners AS mpp2 ON mp.project_id = mpp2.project_id AND mpp2.is_deleted = false +LEFT JOIN moped_entity AS me2 ON mpp2.entity_id = me2.entity_id +LEFT JOIN work_activities ON mp.project_id = work_activities.project_id +LEFT JOIN moped_users AS added_by_user ON mp.added_by = added_by_user.user_id +LEFT JOIN current_phase_view AS current_phase ON mp.project_id = current_phase.project_id +LEFT JOIN moped_public_process_statuses AS mpps ON mp.public_process_status_id = mpps.id +LEFT JOIN child_project_lookup AS cpl ON mp.project_id = cpl.parent_id +LEFT JOIN moped_proj_components_subtypes AS mpcs ON mp.project_id = mpcs.project_id +LEFT JOIN project_district_association AS districts ON mp.project_id = districts.project_id +LEFT JOIN min_confirmed_phase_dates AS mcpd ON mp.project_id = mcpd.project_id +LEFT JOIN min_estimated_phase_dates AS mepd ON mp.project_id = mepd.project_id +LEFT JOIN LATERAL ( + SELECT + mpn.project_note, + mpn.created_at AS date_created + FROM moped_proj_notes AS mpn + WHERE mpn.project_id = mp.project_id AND mpn.project_note_type = 2 AND mpn.is_deleted = false + ORDER BY mpn.created_at DESC + LIMIT 1 +) AS proj_status_update 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, fsl.funding_source_and_program_names, added_by_user.first_name, added_by_user.last_name, mpps.name, cpl.children_project_ids, proj_status_update.project_note, proj_status_update.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, districts.project_council_districts, districts.project_and_child_project_council_districts, mepd.min_phase_date, mcpd.min_phase_date; + +-- Create function to determine project development status date and reuse for other date formats in view +CREATE OR REPLACE FUNCTION public.get_project_development_status_date(latest_public_meeting_date timestamptz, earliest_active_or_construction_phase_date timestamptz, substantial_completion_date timestamptz, substantial_completion_date_estimated timestamptz, current_phase_simple text) +RETURNS timestamptz +LANGUAGE plpgsql +AS $function$ +BEGIN + IF lower(current_phase_simple) = 'complete' THEN + RETURN substantial_completion_date; + ELSIF substantial_completion_date_estimated IS NOT null THEN + RETURN substantial_completion_date_estimated; + ELSIF latest_public_meeting_date IS NOT null THEN + RETURN latest_public_meeting_date; + ELSIF earliest_active_or_construction_phase_date IS NOT null THEN + RETURN earliest_active_or_construction_phase_date; + ELSE + RETURN null; + END IF; +END; +$function$; + +-- Create function to determine project development status and reuse for other date formats in view +CREATE OR REPLACE FUNCTION public.get_project_development_status(latest_public_meeting_date timestamptz, earliest_active_or_construction_phase_date timestamptz, substantial_completion_date timestamptz, substantial_completion_date_estimated timestamptz, current_phase_simple text) +RETURNS text +LANGUAGE plpgsql +AS $function$ +BEGIN + IF lower(current_phase_simple) = 'complete' THEN + RETURN 'Complete'; + ELSIF substantial_completion_date_estimated IS NOT null THEN + IF lower(current_phase_simple) = 'construction' THEN + RETURN 'Estimated End Date (In Construction)'; + ELSE + RETURN 'Estimated End Date'; + END IF; + ELSIF latest_public_meeting_date IS NOT null THEN + RETURN 'Estimated Public Meeting Date'; + ELSIF earliest_active_or_construction_phase_date IS NOT null THEN + RETURN 'Estimated Start of Project Development'; + ELSE + RETURN null; + END IF; +END; +$function$; + +-- Create view again with project_development_status columns +CREATE OR REPLACE VIEW component_arcgis_online_view AS WITH work_types AS ( + SELECT + mpcwt.project_component_id, + string_agg(mwt.name, ', '::text) AS work_types + FROM moped_proj_component_work_types AS mpcwt + LEFT JOIN moped_work_types AS mwt ON mpcwt.work_type_id = mwt.id + WHERE mpcwt.is_deleted = false + GROUP BY mpcwt.project_component_id +), + +council_districts AS ( + SELECT + features.component_id AS project_component_id, + string_agg(DISTINCT features_council_districts.council_district_id::text, ', '::text) AS council_districts, + string_agg(DISTINCT lpad(features_council_districts.council_district_id::text, 2, '0'::text), ', '::text) AS council_districts_searchable + FROM features_council_districts + LEFT JOIN features ON features_council_districts.feature_id = features.id + WHERE features.is_deleted = false + GROUP BY features.component_id +), + +comp_geography AS ( + SELECT + feature_union.component_id AS project_component_id, + string_agg(DISTINCT feature_union.id::text, ', '::text) AS feature_ids, + st_asgeojson(st_union(array_agg(feature_union.geography)))::json AS geometry, + st_asgeojson(st_union(array_agg(feature_union.line_geography)))::json AS line_geometry, + string_agg(DISTINCT feature_union.signal_id::text, ', '::text) AS signal_ids, + sum(feature_union.length_feet) AS length_feet_total + FROM ( + SELECT + feature_signals.id, + feature_signals.component_id, + feature_signals.geography::geometry AS geography, + st_exteriorring(st_buffer(feature_signals.geography, 7::double precision)::geometry) AS line_geography, + feature_signals.signal_id, + null::integer AS length_feet + FROM feature_signals + WHERE feature_signals.is_deleted = false + UNION ALL + SELECT + feature_street_segments.id, + feature_street_segments.component_id, + feature_street_segments.geography::geometry AS geography, + feature_street_segments.geography::geometry AS line_geography, + null::integer AS signal_id, + feature_street_segments.length_feet + FROM feature_street_segments + WHERE feature_street_segments.is_deleted = false + UNION ALL + SELECT + feature_intersections.id, + feature_intersections.component_id, + feature_intersections.geography::geometry AS geography, + st_exteriorring(st_buffer(feature_intersections.geography, 7::double precision)::geometry) AS line_geography, + null::integer AS signal_id, + null::integer AS length_feet + FROM feature_intersections + WHERE feature_intersections.is_deleted = false + UNION ALL + SELECT + feature_drawn_points.id, + feature_drawn_points.component_id, + feature_drawn_points.geography::geometry AS geography, + st_exteriorring(st_buffer(feature_drawn_points.geography, 7::double precision)::geometry) AS line_geography, + null::integer AS signal_id, + null::integer AS length_feet + FROM feature_drawn_points + WHERE feature_drawn_points.is_deleted = false + UNION ALL + SELECT + feature_drawn_lines.id, + feature_drawn_lines.component_id, + feature_drawn_lines.geography::geometry AS geography, + feature_drawn_lines.geography::geometry AS line_geography, + null::integer AS signal_id, + feature_drawn_lines.length_feet + FROM feature_drawn_lines + WHERE feature_drawn_lines.is_deleted = false + ) AS feature_union + GROUP BY feature_union.component_id +), + +subcomponents AS ( + SELECT + mpcs.project_component_id, + string_agg(ms.subcomponent_name, ', '::text) AS subcomponents + FROM moped_proj_components_subcomponents AS mpcs + LEFT JOIN moped_subcomponents AS ms ON mpcs.subcomponent_id = ms.subcomponent_id + WHERE mpcs.is_deleted = false + GROUP BY mpcs.project_component_id +), + +component_tags AS ( + SELECT + mpct.project_component_id, + string_agg((mct.type || ' - '::text) || mct.name, ', '::text) AS component_tags + FROM moped_proj_component_tags AS mpct + LEFT JOIN moped_component_tags AS mct ON mpct.component_tag_id = mct.id + WHERE mpct.is_deleted = false + GROUP BY mpct.project_component_id +), + +related_projects AS ( + SELECT + pmp.project_id, + concat_ws(', '::text, pmp.project_id, string_agg(cmp.project_id::text, ', '::text)) AS related_project_ids_with_self, + concat_ws(', '::text, lpad(pmp.project_id::text, 5, '0'::text), string_agg(lpad(cmp.project_id::text, 5, '0'::text), ', '::text)) AS related_project_ids_searchable_with_self + FROM moped_project AS pmp + LEFT JOIN moped_project AS cmp ON pmp.project_id = cmp.parent_project_id + WHERE cmp.is_deleted = false + GROUP BY pmp.project_id +), + +-- find latest completed or estimated "Public meeting" milestone date +latest_public_meeting_date AS ( + SELECT + mpm.project_id, + coalesce(max(mpm.date_actual), max(mpm.date_estimate)) AS latest + FROM moped_proj_milestones AS mpm + WHERE mpm.milestone_id = 65 AND mpm.is_deleted = false + GROUP BY mpm.project_id +), + +-- earliest estimated or confirmed date of any phase with simple name that is “Active” or “Construction” +earliest_active_or_construction_phase_date AS ( + SELECT + mpp.project_id, + min(mpp.phase_start) AS earliest + FROM moped_proj_phases AS mpp + LEFT JOIN moped_phases AS mp ON mpp.phase_id = mp.phase_id + WHERE mp.phase_name_simple IN ('Active', 'Construction') AND mpp.is_deleted = false + GROUP BY mpp.project_id +) + +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, + council_districts.council_districts_searchable, + NOT coalesce(council_districts.council_districts IS null OR council_districts.council_districts = ''::text, false) AS is_within_city_limits, + comp_geography.length_feet_total, + round(comp_geography.length_feet_total::numeric / 5280::numeric, 2) AS length_miles_total, + mc.component_name, + mc.component_subtype, + mc.component_name_full, + 'placeholder text'::text AS component_categories, + subcomponents.subcomponents AS component_subcomponents, + work_types.work_types AS component_work_types, + component_tags.component_tags, + mpc.description AS component_description, + mpc.interim_project_component_id, + mpc.completion_date, + coalesce(mpc.completion_date, plv.substantial_completion_date) AS substantial_completion_date, + plv.substantial_completion_date_estimated, + mpc.srts_id, + mpc.location_description AS component_location_description, + plv.project_name, + plv.project_name_secondary, + plv.project_name_full, + plv.project_description, + plv.ecapris_subproject_id, + plv.project_website, + plv.updated_at AS project_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_partners, + plv.task_order_names, + plv.funding_source_name, + plv.funding_source_and_program_names AS funding_sources, + plv.type_name, + plv.project_status_update, + plv.project_status_update_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.parent_project_id, + plv.parent_project_name, + plv.parent_project_url, + plv.parent_project_name AS parent_project_name_full, + rp.related_project_ids_with_self AS related_project_ids, + rp.related_project_ids_searchable_with_self AS related_project_ids_searchable, + plv.knack_project_id AS knack_data_tracker_project_record_id, + plv.project_url, + (plv.project_url || '?tab=map&project_component_id='::text) || mpc.project_component_id::text AS component_url, + get_project_development_status(lpmd.latest, eaocpd.earliest, coalesce(mpc.completion_date, plv.substantial_completion_date), plv.substantial_completion_date_estimated, current_phase.phase_name_simple)::text AS project_development_status, + get_project_development_status_date(lpmd.latest, eaocpd.earliest, coalesce(mpc.completion_date, plv.substantial_completion_date), plv.substantial_completion_date_estimated, current_phase.phase_name_simple)::text AS project_development_status_date, + 9999 AS project_development_status_date_calendar_year, + 'placeholder text'::text AS project_development_status_date_calendar_year_month, + 'placeholder text'::text AS project_development_status_date_calendar_year_month_numeric, + 'placeholder text'::text AS project_development_status_date_calendar_year_quarter, + 999 AS project_development_status_date_fiscal_year, + 'placeholder text'::text AS project_development_status_date_fiscal_year_quarter, + plv.added_by AS project_added_by +FROM moped_proj_components AS mpc +LEFT JOIN comp_geography ON mpc.project_component_id = comp_geography.project_component_id +LEFT JOIN council_districts ON mpc.project_component_id = council_districts.project_component_id +LEFT JOIN subcomponents ON mpc.project_component_id = subcomponents.project_component_id +LEFT JOIN work_types ON mpc.project_component_id = work_types.project_component_id +LEFT JOIN component_tags ON mpc.project_component_id = component_tags.project_component_id +LEFT JOIN project_list_view AS plv ON mpc.project_id = plv.project_id +LEFT JOIN current_phase_view AS current_phase ON mpc.project_id = current_phase.project_id +LEFT JOIN moped_phases AS mph ON mpc.phase_id = mph.phase_id +LEFT JOIN moped_components AS mc ON mpc.component_id = mc.component_id +LEFT JOIN related_projects AS rp ON mpc.project_id = rp.project_id +LEFT JOIN latest_public_meeting_date AS lpmd ON mpc.project_id = lpmd.project_id +LEFT JOIN earliest_active_or_construction_phase_date AS eaocpd ON mpc.project_id = eaocpd.project_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 35ae46cac1..a1a96d19de 100644 --- a/moped-database/views/component_arcgis_online_view.sql +++ b/moped-database/views/component_arcgis_online_view.sql @@ -1,4 +1,4 @@ --- Most recent migration: moped-database/migrations/1719582711923_refine_funding_source/up.sql +-- Most recent migration: moped-database/migrations/1719872865109_add_work_plan_category/up.sql CREATE OR REPLACE VIEW component_arcgis_online_view AS WITH work_types AS ( SELECT @@ -114,22 +114,23 @@ related_projects AS ( GROUP BY pmp.project_id ), -min_phase_dates AS ( - WITH min_dates AS ( - SELECT - mpp.project_id, - min(mpp.phase_start) AS min_phase_start, - min(mpp.phase_end) AS min_phase_end - FROM moped_proj_phases mpp - LEFT JOIN moped_phases mp ON mpp.phase_id = mp.phase_id - WHERE mpp.is_phase_end_confirmed = false AND mpp.is_phase_start_confirmed = false AND mpp.is_deleted = false AND mp.phase_name_simple = 'Complete'::text - GROUP BY mpp.project_id - ) +latest_public_meeting_date AS ( + SELECT + mpm.project_id, + coalesce(max(mpm.date_actual), max(mpm.date_estimate)) AS latest + FROM moped_proj_milestones mpm + WHERE mpm.milestone_id = 65 AND mpm.is_deleted = false + GROUP BY mpm.project_id +), +earliest_active_or_construction_phase_date AS ( SELECT - min_dates.project_id, - least(min_dates.min_phase_start, min_dates.min_phase_end) AS min_phase_date - FROM min_dates + mpp.project_id, + min(mpp.phase_start) AS earliest + FROM moped_proj_phases mpp + LEFT JOIN moped_phases mp ON mpp.phase_id = mp.phase_id + WHERE (mp.phase_name_simple = any(ARRAY['Active'::text, 'Construction'::text])) AND mpp.is_deleted = false + GROUP BY mpp.project_id ) SELECT @@ -202,14 +203,14 @@ SELECT plv.knack_project_id AS knack_data_tracker_project_record_id, plv.project_url, (plv.project_url || '?tab=map&project_component_id='::text) || mpc.project_component_id::text AS component_url, - plv.project_development_status, - plv.project_development_status_date, - plv.project_development_status_date_calendar_year, - plv.project_development_status_date_calendar_year_month, - plv.project_development_status_date_calendar_year_month_numeric, - plv.project_development_status_date_calendar_year_quarter, - plv.project_development_status_date_fiscal_year, - plv.project_development_status_date_fiscal_year_quarter, + get_project_development_status(lpmd.latest::timestamp with time zone, eaocpd.earliest, coalesce(mpc.completion_date, plv.substantial_completion_date), plv.substantial_completion_date_estimated, current_phase.phase_name_simple) AS project_development_status, + get_project_development_status_date(lpmd.latest::timestamp with time zone, eaocpd.earliest, coalesce(mpc.completion_date, plv.substantial_completion_date), plv.substantial_completion_date_estimated, current_phase.phase_name_simple)::text AS project_development_status_date, + 9999 AS project_development_status_date_calendar_year, + 'placeholder text'::text AS project_development_status_date_calendar_year_month, + 'placeholder text'::text AS project_development_status_date_calendar_year_month_numeric, + 'placeholder text'::text AS project_development_status_date_calendar_year_quarter, + 999 AS project_development_status_date_fiscal_year, + 'placeholder text'::text AS project_development_status_date_fiscal_year_quarter, plv.added_by AS project_added_by FROM moped_proj_components mpc LEFT JOIN comp_geography ON mpc.project_component_id = comp_geography.project_component_id @@ -222,5 +223,6 @@ LEFT JOIN current_phase_view current_phase ON mpc.project_id = current_phase.pro LEFT JOIN moped_phases mph ON mpc.phase_id = mph.phase_id LEFT JOIN moped_components mc ON mpc.component_id = mc.component_id LEFT JOIN related_projects rp ON mpc.project_id = rp.project_id -LEFT JOIN min_phase_dates mpd ON mpc.project_id = mpd.project_id +LEFT JOIN latest_public_meeting_date lpmd ON mpc.project_id = lpmd.project_id +LEFT JOIN earliest_active_or_construction_phase_date eaocpd ON mpc.project_id = eaocpd.project_id WHERE mpc.is_deleted = false AND plv.is_deleted = false; diff --git a/moped-database/views/project_list_view.sql b/moped-database/views/project_list_view.sql index 365c65473c..177c2d6daa 100644 --- a/moped-database/views/project_list_view.sql +++ b/moped-database/views/project_list_view.sql @@ -1,4 +1,4 @@ --- Most recent migration: moped-database/migrations/1719582711923_refine_funding_source/up.sql +-- Most recent migration: moped-database/migrations/1719872865109_add_work_plan_category/up.sql CREATE OR REPLACE VIEW project_list_view AS WITH project_person_list_lookup AS ( SELECT @@ -196,14 +196,6 @@ SELECT work_activities.task_order_names, work_activities.task_order_names_short, work_activities.task_orders, - 'placeholder text'::text AS project_development_status, - '2024-01-01T00:00:00-06:00'::text AS project_development_status_date, - 9999 AS project_development_status_date_calendar_year, - 'placeholder text'::text AS project_development_status_date_calendar_year_month, - 'placeholder text'::text AS project_development_status_date_calendar_year_month_numeric, - 'placeholder text'::text AS project_development_status_date_calendar_year_quarter, - 999 AS project_development_status_date_fiscal_year, - 'placeholder text'::text AS project_development_status_date_fiscal_year_quarter, ( SELECT moped_project.project_name_full FROM moped_project diff --git a/moped-editor/package-lock.json b/moped-editor/package-lock.json index 06b149778a..b96a739616 100644 --- a/moped-editor/package-lock.json +++ b/moped-editor/package-lock.json @@ -1,12 +1,12 @@ { "name": "atd-moped-editor", - "version": "2.13.1", + "version": "2.15.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "atd-moped-editor", - "version": "2.13.1", + "version": "2.15.2", "license": "CC0-1.0", "dependencies": { "@apollo/client": "^3.9.1", @@ -61,7 +61,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.49.3", "react-map-gl": "^7.1.7", - "react-quill": "^2.0.0", + "react-quill-new": "^3.2.0", "react-router": "^6.3.0", "react-router-dom": "^6.21.2", "react-scripts": "^5.0.1", @@ -14785,14 +14785,6 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" }, - "node_modules/@types/quill": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", - "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", - "dependencies": { - "parchment": "^1.1.2" - } - }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -17385,14 +17377,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -19939,11 +19923,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -19966,9 +19945,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", - "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -26296,6 +26275,11 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -28372,11 +28356,6 @@ "tslib": "^2.0.3" } }, - "node_modules/parchment": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", - "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -30118,75 +30097,19 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, - "node_modules/quill": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", - "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", - "dependencies": { - "clone": "^2.1.1", - "deep-equal": "^1.0.1", - "eventemitter3": "^2.0.3", - "extend": "^3.0.2", - "parchment": "^1.1.4", - "quill-delta": "^3.6.2" - } - }, "node_modules/quill-delta": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", - "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", - "dependencies": { - "deep-equal": "^1.0.1", - "extend": "^3.0.2", - "fast-diff": "1.1.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/quill-delta/node_modules/deep-equal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", - "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", - "dependencies": { - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.5.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quill/node_modules/deep-equal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", - "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", "dependencies": { - "is-arguments": "^1.1.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.5.1" + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 12.0.0" } }, - "node_modules/quill/node_modules/eventemitter3": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", - "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==" - }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -30549,18 +30472,42 @@ "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz", "integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==" }, - "node_modules/react-quill": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz", - "integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==", + "node_modules/react-quill-new": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/react-quill-new/-/react-quill-new-3.2.1.tgz", + "integrity": "sha512-zPuoecg69o5DtajO3714B+mhJpWF7ZUONj+i8ME/SSmEtf0tdwvn9jJRSU4OOoZyUdByG3YcrHg3T1DcGEhsKg==", "dependencies": { - "@types/quill": "^1.3.10", - "lodash": "^4.17.4", - "quill": "^1.3.7" + "lodash-es": "^4.17.21", + "quill": "~2.0.2" }, "peerDependencies": { - "react": "^16 || ^17 || ^18", - "react-dom": "^16 || ^17 || ^18" + "quill-delta": "^5.1.0", + "react": "^16 || ^17 || ^18 || ^19", + "react-dom": "^16 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-quill-new/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/react-quill-new/node_modules/parchment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", + "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==" + }, + "node_modules/react-quill-new/node_modules/quill": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz", + "integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash-es": "^4.17.21", + "parchment": "^3.0.0", + "quill-delta": "^5.1.0" + }, + "engines": { + "npm": ">=8.2.3" } }, "node_modules/react-refresh": { diff --git a/moped-editor/package.json b/moped-editor/package.json index 12f262be35..835bb4efc9 100644 --- a/moped-editor/package.json +++ b/moped-editor/package.json @@ -2,7 +2,7 @@ "name": "atd-moped-editor", "author": "ATD Data & Technology Services", "license": "CC0-1.0", - "version": "2.15.2", + "version": "2.16.0", "homepage": "/moped", "private": false, "repository": { diff --git a/moped-editor/src/components/GridTable/Filters.js b/moped-editor/src/components/GridTable/Filters.js index 8934403f3d..a7ad14ff2c 100644 --- a/moped-editor/src/components/GridTable/Filters.js +++ b/moped-editor/src/components/GridTable/Filters.js @@ -1,7 +1,6 @@ import React, { useState } from "react"; import PropTypes from "prop-types"; import { useQuery } from "@apollo/client"; -import { useMakeFilterState } from "./helpers"; import { Button, @@ -38,6 +37,7 @@ import { handleApplyValidation, isFilterNullType, shouldRenderAutocompleteInput, + useMakeFilterState } from "./helpers"; import { FiltersCommonOperators } from "./FiltersCommonOperators"; @@ -189,7 +189,7 @@ const Filters = ({ /** * Handles the click event on the operator drop-down - * @param {string} filterIndex - filterParameters index to modify + * @param {Number} filterIndex - filterParameters index to modify * @param {Object} operator - The operator object being clicked * @param {string} lookupTable - The lookup table name * @param {Array} lookupOperators - operators set in filter config to show autocomplete @@ -235,7 +235,7 @@ const Filters = ({ /** * Deletes a filter from the state - * @param {string} filterIndex - The index of the filter to be deleted + * @param {Number} filterIndex - The index of the filter to be deleted */ const handleDeleteFilterButtonClick = (filterIndex) => { /* Clone the state, delete the filter index of the button clicked, and update filter state */ @@ -253,7 +253,7 @@ const Filters = ({ /** * The user will type a new search value - * @param {string} filterIndex - filterParameters index to modify + * @param {Number} filterIndex - filterParameters index to modify * @param {string} value - The value to assign to that filter */ const handleSearchValueChange = (filterIndex, value) => { diff --git a/moped-editor/src/components/GridTable/FiltersChips.js b/moped-editor/src/components/GridTable/FiltersChips.js index 00b99b5c81..2a00a9f5cb 100644 --- a/moped-editor/src/components/GridTable/FiltersChips.js +++ b/moped-editor/src/components/GridTable/FiltersChips.js @@ -1,6 +1,11 @@ import React from "react"; import { Box, Typography, Chip, Grid } from "@mui/material"; import makeStyles from "@mui/styles/makeStyles"; +import { + advancedSearchFilterParamName, + advancedSearchIsOrParamName, +} from "src/views/projects/projectsListView/useProjectListViewQuery/useAdvancedSearch"; +import { formatDateType } from "src/utils/dateAndTime"; const useStyles = makeStyles((theme) => ({ filtersList: { @@ -23,7 +28,14 @@ const useStyles = makeStyles((theme) => ({ * @return {JSX.Element} * @constructor */ -const FiltersChips = ({ filters, isOr, filtersConfig }) => { +const FiltersChips = ({ + filters, + setFilters, + isOr, + filtersConfig, + setSearchParams, + setIsOr, +}) => { const classes = useStyles(); const filtersCount = Object.keys(filters).length; @@ -33,19 +45,60 @@ const FiltersChips = ({ filters, isOr, filtersConfig }) => { (fieldConfig) => fieldConfig.name === filter.field ); const fieldOperatorConfig = filtersConfig.operators[filter.operator]; + const isDateType = fieldFilterConfig.type === "date"; return { filterLabel: fieldFilterConfig?.label, operatorLabel: fieldOperatorConfig?.label, - filterValue: filter.value, + filterValue: isDateType ? formatDateType(filter.value) : filter.value, }; }); + /** + * Triggered by Filter Chip delete click + * Removes specified filter from state and update the url params + * @param {number} filterIndex - The index of the filter to be deleted + */ + const handleDeleteButtonClick = (filterIndex) => { + const filtersNewState = [...filters]; + filtersNewState.splice(filterIndex, 1); + setFilters(filtersNewState); + + const remainingFiltersCount = filtersNewState.length; + + if (remainingFiltersCount === 1) { + // Reset isOr to false (all/and) if there is only one filter left + setIsOr(false); + // update params as well + setSearchParams((prevSearchParams) => { + prevSearchParams.set(advancedSearchIsOrParamName, false); + return prevSearchParams; + }); + } + + // Update search params in url + if (remainingFiltersCount > 0) { + setSearchParams((prevSearchParams) => { + const jsonParamString = JSON.stringify(filtersNewState); + prevSearchParams.set(advancedSearchFilterParamName, jsonParamString); + return prevSearchParams; + }); + } else { + // no filters left, clear search params + setSearchParams((prevSearchParams) => { + prevSearchParams.delete(advancedSearchFilterParamName); + prevSearchParams.delete(advancedSearchIsOrParamName); + + return prevSearchParams; + }); + } + }; + return ( - + {filtersCount > 1 && ( - + { )} {filtersLabels.map((filter, index) => ( - + handleDeleteButtonClick(index)} label={ <> diff --git a/moped-editor/src/components/GridTable/Search.js b/moped-editor/src/components/GridTable/Search.js index d7d7b8f374..6f58e724d3 100644 --- a/moped-editor/src/components/GridTable/Search.js +++ b/moped-editor/src/components/GridTable/Search.js @@ -167,6 +167,7 @@ const Search = ({ setSearchFieldValue={setSearchFieldValue} handleSearchSubmission={handleSearchSubmission} filters={filters} + setFilters={setFilters} toggleAdvancedSearch={toggleAdvancedSearch} advancedSearchAnchor={advancedSearchAnchor} queryConfig={queryConfig} @@ -174,6 +175,8 @@ const Search = ({ loading={loading} filtersConfig={filtersConfig} resetSimpleSearch={resetSimpleSearch} + setIsOr={setIsOr} + setSearchParams={setSearchParams} /> diff --git a/moped-editor/src/components/GridTable/SearchBar.js b/moped-editor/src/components/GridTable/SearchBar.js index 1cde231687..f6f2c036ac 100644 --- a/moped-editor/src/components/GridTable/SearchBar.js +++ b/moped-editor/src/components/GridTable/SearchBar.js @@ -58,6 +58,7 @@ const SearchBar = ({ searchFieldValue, setSearchFieldValue, filters, + setFilters, toggleAdvancedSearch, advancedSearchAnchor, handleSearchSubmission, @@ -66,6 +67,8 @@ const SearchBar = ({ loading, filtersConfig, resetSimpleSearch, + setSearchParams, + setIsOr, }) => { const classes = useStyles(); @@ -162,11 +165,14 @@ const SearchBar = ({ variant="outlined" value={searchFieldValue} /> - {filterStateActive && ( + {filterStateActive && !advancedSearchAnchor && ( )} diff --git a/moped-editor/src/queries/project.js b/moped-editor/src/queries/project.js index fba3e2d6b6..ca77099097 100644 --- a/moped-editor/src/queries/project.js +++ b/moped-editor/src/queries/project.js @@ -974,12 +974,9 @@ export const LOOKUP_TABLES_QUERY = gql` last_name user_id } - moped_phases( - order_by: { phase_name_simple: asc } - distinct_on: phase_name_simple - ) { + moped_phases(order_by: { phase_name: asc }) { phase_id - phase_name_simple + phase_name } } `; diff --git a/moped-editor/src/views/projects/projectsListView/ProjectsListViewExportConf.js b/moped-editor/src/views/projects/projectsListView/ProjectsListViewExportConf.js index a115b51978..c1e1f5a2ab 100644 --- a/moped-editor/src/views/projects/projectsListView/ProjectsListViewExportConf.js +++ b/moped-editor/src/views/projects/projectsListView/ProjectsListViewExportConf.js @@ -60,7 +60,7 @@ export const PROJECT_LIST_VIEW_EXPORT_CONFIG = { type_name: { label: "Type", }, - funding_source_name: { + funding_source_and_program_names: { label: "Funding", }, project_status_update: { diff --git a/moped-editor/src/views/projects/projectsListView/ProjectsListViewFiltersConf.js b/moped-editor/src/views/projects/projectsListView/ProjectsListViewFiltersConf.js index 6ef4e72c2d..d311eee92b 100644 --- a/moped-editor/src/views/projects/projectsListView/ProjectsListViewFiltersConf.js +++ b/moped-editor/src/views/projects/projectsListView/ProjectsListViewFiltersConf.js @@ -142,7 +142,7 @@ export const PROJECT_LIST_VIEW_FILTERS_CONFIG = { type: "string", lookup: { table_name: "moped_phases", - getOptionLabel: (option) => option.phase_name_simple, + getOptionLabel: (option) => option.phase_name, operators: ["string_equals_case_insensitive"], }, operators: [ diff --git a/moped-editor/src/views/projects/projectsListView/ProjectsListViewQueryConf.js b/moped-editor/src/views/projects/projectsListView/ProjectsListViewQueryConf.js index f1ac6823eb..8f17b85fd5 100644 --- a/moped-editor/src/views/projects/projectsListView/ProjectsListViewQueryConf.js +++ b/moped-editor/src/views/projects/projectsListView/ProjectsListViewQueryConf.js @@ -226,7 +226,7 @@ export const PROJECT_LIST_VIEW_QUERY_CONFIG = { defaultHidden: true, showInTable: true, }, - funding_source_name: { + funding_source_and_program_names: { type: "string", sortable: true, defaultHidden: true, @@ -342,7 +342,7 @@ export const SHOW_ALL_COLS = Object.entries( PROJECT_LIST_VIEW_QUERY_CONFIG.columns ).reduce((acc, [columnName, config]) => { if (config.showInTable === true) { - acc[columnName] = true + acc[columnName] = true; } return acc; }, {}); diff --git a/moped-editor/src/views/projects/projectsListView/helpers.js b/moped-editor/src/views/projects/projectsListView/helpers.js index 3d29df9421..e040cfd057 100644 --- a/moped-editor/src/views/projects/projectsListView/helpers.js +++ b/moped-editor/src/views/projects/projectsListView/helpers.js @@ -289,10 +289,10 @@ export const useColumns = ({ hiddenColumns }) => { }, { headerName: "Funding", - field: "funding_source_name", + field: "funding_source_and_program_names", cellStyle: { whiteSpace: "noWrap" }, renderCell: ({ row }) => - renderSplitListDisplayBlock(row, "funding_source_name"), + renderSplitListDisplayBlock(row, "funding_source_and_program_names"), width: COLUMN_WIDTHS.medium, }, {