diff --git a/api/src/paths/v2/activities.ts b/api/src/paths/v2/activities.ts index 3c7a36078..6df410a3e 100644 --- a/api/src/paths/v2/activities.ts +++ b/api/src/paths/v2/activities.ts @@ -100,8 +100,7 @@ function sanitizeActivityFilterObject(filterObject: any, req: any) { } if (!isAuth) { sanitizedSearchCriteria.serverSideNamedFilters.hideEditedByFields = true; - } - else { + } else { sanitizedSearchCriteria.serverSideNamedFilters.hideEditedByFields = false; } @@ -122,12 +121,12 @@ function sanitizeActivityFilterObject(filterObject: any, req: any) { break; /* NOTE: any payload columns need this: */ case 'project_code': - if(!selectColumns.includes('activity_payload')){ + if (!selectColumns.includes('activity_payload')) { selectColumns.push('activity_payload'); } break; case 'activity_date': - if(!selectColumns.includes('activity_payload')){ + if (!selectColumns.includes('activity_payload')) { selectColumns.push('activity_payload'); } break; @@ -151,41 +150,61 @@ function sanitizeActivityFilterObject(filterObject: any, req: any) { offset = filterObject.page * filterObject.limit; } - sanitizedSearchCriteria.limit = limit; sanitizedSearchCriteria.offset = offset; sanitizedSearchCriteria.selectColumns = selectColumns; let sanitizedTableFilters = []; + //sanitize serverFilterGeometries + let serverFilterGeometries = []; + + //sanitize clientFilterGeometries + let clientFilterGeometries = []; if (filterObject?.tableFilters?.length > 0) { filterObject.tableFilters.forEach((filter) => { - switch (filter.field) { - case 'created_by': - if (!sanitizedSearchCriteria.serverSideNamedFilters.hideEditedByFields) { - sanitizedTableFilters.push(filter); + switch (filter.filterType) { + case 'tableFilter': + switch (filter.field) { + case 'created_by': + if (!sanitizedSearchCriteria.serverSideNamedFilters.hideEditedByFields) { + sanitizedTableFilters.push(filter); + } + break; + case 'updated_by': + if (!sanitizedSearchCriteria.serverSideNamedFilters.hideEditedByFields) { + sanitizedTableFilters.push(filter); + } + break; + default: + sanitizedTableFilters.push(filter); + break; } break; - case 'updated_by': - if (!sanitizedSearchCriteria.serverSideNamedFilters.hideEditedByFields) { - sanitizedTableFilters.push(filter); + case 'spatialFilterDrawn': + if (filter.filter) { + clientFilterGeometries.push(filter.filter?.geometry); + } + break; + case 'spatialFilterUploaded': + if (!isNaN(parseInt(filter?.filter))) { + serverFilterGeometries.push(parseInt(filter.filter)); } break; default: - sanitizedTableFilters.push(filter); break; } }); } - - + sanitizedSearchCriteria.serverFilterGeometries = serverFilterGeometries; + sanitizedSearchCriteria.clientFilterGeometries = clientFilterGeometries; sanitizedSearchCriteria.clientReqTableFilters = sanitizedTableFilters; defaultLog.debug({ label: 'getActivitiesBySearchFilterCriteria', message: 'sanitizedObject', - body: sanitizedSearchCriteria + body: JSON.stringify(sanitizedSearchCriteria, null, 2) }); return sanitizedSearchCriteria; @@ -243,6 +262,9 @@ function getActivitiesBySearchFilterCriteria(): RequestHandler { } function getActivitiesSQLv2(filterObject: any) { + try + { + let sqlStatement: SQLStatement = SQL``; sqlStatement = initialWithStatement(sqlStatement); sqlStatement = additionalCTEStatements(sqlStatement, filterObject); @@ -252,10 +274,16 @@ function getActivitiesSQLv2(filterObject: any) { sqlStatement = groupByStatement(sqlStatement, filterObject); sqlStatement = orderByStatement(sqlStatement, filterObject); sqlStatement = limitStatement(sqlStatement, filterObject); - sqlStatement = offSetStatement(sqlStatement, filterObject) + sqlStatement = offSetStatement(sqlStatement, filterObject); defaultLog.debug({ label: 'getActivitiesBySearchFilterCriteria', message: 'sql', body: sqlStatement }); return sqlStatement; + } + + catch(e) { + defaultLog.debug({ label: 'getActivitiesBySearchFilterCriteria', message: 'error', body: e.message }); + throw e + } } function initialWithStatement(sqlStatement: SQLStatement) { @@ -285,20 +313,120 @@ CurrentNegativeObservations AS ( invasivesbc.current_negative_observations cno GROUP BY cno.activity_incoming_data_id), +`); + + if (filterObject?.serverFilterGeometries?.length > 0) { + sqlStatement.append(` + + serverFilterGeometryIDs as ( + + select unnest(array[${filterObject?.serverFilterGeometries.join(',')}]) as id + + ), + serverFilterGeometries AS ( + select a.id, title, st_subdivide(geog::geometry) as geo + from invasivesbc.admin_defined_shapes a + inner join serverFilterGeometryIDs b on a.id = b.id + ), + + serverFilterGeometriesIntersecting as ( + + select a.activity_incoming_data_id, b.id + from not_deleted_activities a + inner join serverFilterGeometries b on st_intersects((a.geog::geometry), b.geo) + group by a.activity_incoming_data_id, b.id + + + ), + serverFilterGeometriesIntersectingAll as ( + + select a.activity_incoming_data_id, count(*) + from not_deleted_activities a + inner join serverFilterGeometriesIntersecting b on a.activity_incoming_data_id = b.activity_incoming_data_id + group by a.activity_incoming_data_id + + having count(*) = (select count(*) from serverFilterGeometryIDs) + ), + `); + } + if (filterObject?.clientFilterGeometries?.length > 0) { + sqlStatement.append(` + clientFilterGeometries AS ( + SELECT + unnest(array[${filterObject.clientFilterGeometries + .map((geometry) => `st_setsrid(st_geomfromgeojson(${geometry?.geometry}, 4326)`) + .join(',')}]) AS geojson + ), + + clientFilterGeometriesIntersecting as ( + + select a.activity_incoming_data_id + from not_deleted_activities a + left join clientFilterGeometries on st_intersects((a.geog::geometry), geojson) + + ), + clientFilterGeometriesIntersectingAll as ( + + select a.activity_incoming_data_id, count(*) + from not_deleted_activities a + inner join clientFilterGeometriesIntersecting b on a.activity_incoming_data_id = b.activity_incoming_data_id + group by a.activity_incoming_data_id + + having count(*) = (select count(*) from clientFilterGeometries) + ), + `); + } + + sqlStatement.append(` activities as ( - select not_deleted_activities.*, CurrentPositiveObservations.current_positive_species, CurrentNegativeObservations.current_negative_species, + select a.*, CurrentPositiveObservations.current_positive_species, CurrentNegativeObservations.current_negative_species, case when CurrentPositiveObservations.current_positive_species is null then false else true end as has_current_positive, case when CurrentNegativeObservations.current_negative_species is null then false else true end as has_current_negative - from not_deleted_activities - left join CurrentPositiveObservations on CurrentPositiveObservations.activity_incoming_data_id = not_deleted_activities.activity_incoming_data_id - left join CurrentNegativeObservations on CurrentNegativeObservations.activity_incoming_data_id = not_deleted_activities.activity_incoming_data_id -) `); + `); + + /*if (filterObject?.serverFilterGeometries?.length > 0) { + sqlStatement.append(` + ,case when ServerBoundariesToIntersect.geog is null then false else true end as intersects_server_boundary + `); + } + if (filterObject?.clientFilterGeometries?.length > 0) { + sqlStatement.append(` + ,case when ClientBoundariesToIntersect.geog is null then false else true end as intersects_client_boundary + `); + }*/ + + sqlStatement.append(` + from not_deleted_activities a + left join CurrentPositiveObservations on CurrentPositiveObservations.activity_incoming_data_id = a.activity_incoming_data_id + left join CurrentNegativeObservations on CurrentNegativeObservations.activity_incoming_data_id = a.activity_incoming_data_id + + `); + + if (filterObject?.serverFilterGeometries?.length > 0) { + sqlStatement.append(` + inner join serverFilterGeometriesIntersectingAll c on a.activity_incoming_data_id = c.activity_incoming_data_id + `); + } + + if (filterObject?.clientFilterGeometries?.length > 0) { + sqlStatement.append(` + inner join clientFilterGeometriesIntersectingAll d on a.activity_incoming_data_id = d.activity_incoming_data_id + `); + } + + sqlStatement.append(` + ) `); + + defaultLog.debug({ label: 'getActivitiesBySearchFilterCriteria', message: 'sql', body: sqlStatement }); + return cte; } function selectStatement(sqlStatement: SQLStatement, filterObject: any) { if (filterObject.selectColumns) { - const select = sqlStatement.append(`select ${filterObject.selectColumns.map((column) => `activities.${column}`).join(',')} `); + const select = sqlStatement.append( + `select ${filterObject.selectColumns.map((column) => `activities.${column}`).join(',')} ` + ); return select; } else { const select = sqlStatement.append(`select * `); @@ -320,72 +448,138 @@ function whereStatement(sqlStatement: SQLStatement, filterObject: any) { filterObject.clientReqTableFilters.forEach((filter) => { switch (filter.field) { case 'activity_id': - where.append(`and activities.activity_id ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.activity_id ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'short_id': - where.append(`and activities.short_id ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.short_id ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'activity_type': - where.append(`and activities.activity_type ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.activity_type ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'activity_subtype': - where.append(`and activities.activity_subtype ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.activity_subtype ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'activity_date': - where.append(`and substring((activities.activity_payload::json->'form_data'->'activity_data'->'activity_date_time'::text)::text, 2, 10) ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `) + where.append( + `and substring((activities.activity_payload::json->'form_data'->'activity_data'->'activity_date_time'::text)::text, 2, 10) ${ + filter.operator === 'CONTAINS' ? 'like' : 'not like' + } '%${filter.filter}%' ` + ); break; case 'project_code': where.append( - `and (activities.activity_payload::json->'form_data'->'activity_data'->'project_code'::text)::text ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' ` + `and (activities.activity_payload::json->'form_data'->'activity_data'->'project_code'::text)::text ${ + filter.operator === 'CONTAINS' ? 'like' : 'not like' + } '%${filter.filter}%' ` ); break; case 'jurisdiction_display': - where.append(`and activities.jurisdiction_display ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.jurisdiction_display ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'species_positive_full': - where.append(`and activities.species_positive_full ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.species_positive_full ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'species_negative_full': - where.append(`and activities.species_negative_full ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.species_negative_full ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'has_current_positive': - where.append(`and activities.current_positive_species ${filter.operator === 'CONTAINS'? 'is not': 'is'} null `); + where.append( + `and activities.current_positive_species ${filter.operator === 'CONTAINS' ? 'is not' : 'is'} null ` + ); break; case 'has_current_negative': - where.append(`and activities.current_negative_species ${filter.operator === 'CONTAINS'? 'is not': 'is'} null `); + where.append( + `and activities.current_negative_species ${filter.operator === 'CONTAINS' ? 'is not' : 'is'} null ` + ); break; case 'current_positive_species': - where.append(`and activities.current_positive_species ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.current_positive_species ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'current_negative_species': - where.append(`and activities.current_negative_species ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.current_negative_species ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'species_treated_full': - where.append(`and activities.species_treated_full ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.species_treated_full ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'created_by': - where.append(`and activities.created_by ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.created_by ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'updated_by': - where.append(`and activities.updated_by ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.updated_by ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'agency': - where.append(`and activities.agency ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.agency ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'regional_invasive_species_organization_areas': - where.append(`and activities.regional_invasive_species_organization_areas ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.regional_invasive_species_organization_areas ${ + filter.operator === 'CONTAINS' ? 'like' : 'not like' + } '%${filter.filter}%' ` + ); break; case 'regional_districts': - where.append(`and activities.regional_districts ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.regional_districts ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'biogeoclimatic_zones': - where.append(`and activities.biogeoclimatic_zones ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.biogeoclimatic_zones ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${ + filter.filter + }%' ` + ); break; case 'elevation': - where.append(`and activities.elevation ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.elevation ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; case 'batch_id': - where.append(`and activities.batch_id::text ${filter.operator === 'CONTAINS'? 'like': 'not like'} '%${filter.filter}%' `); + where.append( + `and activities.batch_id::text ${filter.operator === 'CONTAINS' ? 'like' : 'not like'} '%${filter.filter}%' ` + ); break; default: break; diff --git a/appv2/src/UI/Map/OnHoverActivity.tsx b/appv2/src/UI/Map/OnHoverActivity.tsx index 4c50acfc3..05eee2573 100644 --- a/appv2/src/UI/Map/OnHoverActivity.tsx +++ b/appv2/src/UI/Map/OnHoverActivity.tsx @@ -1,4 +1,4 @@ -import { GeoJSON } from 'react-leaflet'; +import { GeoJSON, useMap } from 'react-leaflet'; import React, { useEffect, useLayoutEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import center from '@turf/center'; @@ -10,6 +10,7 @@ export const OnHoverActivity = (props: any) => { const geometresForActivity = useSelector((state: any) => state.Map?.userRecordOnHoverRecordRow?.geometry); const [centerPointGeometry, setCenterPointGeometry] = useState(null); const popupRef = React.useRef(null); + const map = useMap(); useEffect(() => { try { @@ -45,5 +46,12 @@ export const OnHoverActivity = (props: any) => { } }, [centerPointGeometry]); + + map.on('popupopen', function(e) { + var px = map.project(e.target._popup._latlng); // find the pixel location on the map where the popup anchor is + px.y += (e.target._popup._container.clientHeight * 6 ) + map.panTo(map.unproject(px),{animate: true}); // pan to new center +}); + return centerPointGeometry !== null ? : null; }; diff --git a/appv2/src/UI/Overlay/Records/RecordSet.css b/appv2/src/UI/Overlay/Records/RecordSet.css index d65b8a0fb..bc38de8b4 100644 --- a/appv2/src/UI/Overlay/Records/RecordSet.css +++ b/appv2/src/UI/Overlay/Records/RecordSet.css @@ -105,7 +105,8 @@ td { } .filterSelect { - width: 100px; + /*width: 100px;*/ + max-width: 150px; } .recordSet_footer { diff --git a/appv2/src/UI/Overlay/Records/RecordSet.tsx b/appv2/src/UI/Overlay/Records/RecordSet.tsx index 76fbab02c..de5b584a9 100644 --- a/appv2/src/UI/Overlay/Records/RecordSet.tsx +++ b/appv2/src/UI/Overlay/Records/RecordSet.tsx @@ -77,11 +77,21 @@ export const RecordSet = (props) => {
@@ -111,12 +121,12 @@ export const RecordSet = (props) => { Filter type Operator - Field + Filter On Value {userSettingsState?.recordSets?.[props.setID]?.tableFilters.map((filter: any, i) => { - return ; + return ; })} ) : ( @@ -184,6 +194,9 @@ const RecordSetFooter = (props) => { const Filter = (props) => { const userSettingsState = useSelector((state: any) => state.UserSettings); + const serverBoundariesToDisplay = useSelector((state: any) => state.Map.serverBoundaries)?.map((boundary) => { + return { label: boundary.title, value: boundary.id }; + }); console.dir(userSettingsState); const filterColumns = @@ -195,6 +208,10 @@ const Filter = (props) => { }); const dispatch = useDispatch(); + const filterTypeInState = userSettingsState?.recordSets?.[props.setID]?.tableFilters?.find( + (filter) => filter.id === props.id + )?.filterType; + const valueInState = userSettingsState?.recordSets?.[props.setID]?.tableFilters?.find( (filter) => filter.id === props.id )?.filter; @@ -222,33 +239,142 @@ const Filter = (props) => { }); }; + let input = null; + switch (filterTypeInState) { + case 'tableFilter': + input = ( + { + console.log('it changed'); + debouncedUpdate(e.target.value); + }} + type="text" + value={valueInState} + //defaultValue={valueInState} + /> + ); + break; + case 'spatialFilterUploaded': + input = ( + + ); + + break; + default: + null; + } + return ( - Data - { + console.dir(e.target.value); - dispatch({ - type: RECORDSET_UPDATE_FILTER, - payload: { - filterType: 'tableFilter', - setID: props.setID, - filterID: props.id, - operator: e.target.value - } - }); - }}> - - - + dispatch({ + type: RECORDSET_UPDATE_FILTER, + payload: { + filterType: e.target.value, + setID: props.setID, + filterID: props.id + //operator: e.target.value + } + }); + }}> + + + + + + + + - {props.type === 'data' ? ( - - { - console.log('it changed'); - debouncedUpdate(e.target.value); - }} - type="text" - value={valueInState} - //defaultValue={valueInState} - /> - - ) : ( - <> - )} + + {input} +