diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d62ca3ccb..b2a44b334 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -17,7 +17,7 @@ on: push: branches: [ main ] pull_request: - branches: [ main, version-2.0.0 ] + branches: [ main, version-2.0.0, Filter-improvement ] jobs: flake8_py3: @@ -97,11 +97,11 @@ jobs: matrix: test_suite: [ { name: "Backend Tests", script: "make devweb-test" }, - { name: "E2E Tests: Project Creation", path: "tests/project_creation", workers: 2, script_load : "make load-test-data" }, - { name: "E2E Tests: Project View", path: "tests/project_view", workers: 3, script_load : "make load-test-data" }, - { name: "E2E Tests: Admin List", path: "tests/admin_list", workers: 3, script_load : "make load-test-data" }, - { name: "E2E Tests: Admin Edit", path: "tests/admin_edit", workers: 3, script_load : "make load-test-data" }, - { name: "E2E Tests: Admin List Filter", path: "tests/admin_filter", workers: 3, script_load : "make load-test-data-for-filter" } + { name: "E2E Tests: Project Creation", path: "tests/project_creation", workers: 2, script_load: "make load-test-data" }, + { name: "E2E Tests: Project View", path: "tests/project_view", workers: 3, script_load: "make load-test-data" }, + { name: "E2E Tests: Admin List", path: "tests/admin_list", workers: 3, script_load: "make load-test-data" }, + { name: "E2E Tests: Admin Edit", path: "tests/admin_edit", workers: 3, script_load: "make load-test-data" }, + { name: "E2E Tests: Admin List Filter", path: "tests/admin_filter", workers: 3, script_load: "make load-test-data-for-filter" } ] env: APP_IMAGE: kartoza/geosight diff --git a/django_project/frontend/package.json b/django_project/frontend/package.json index 0418f56fd..9ba9d7070 100644 --- a/django_project/frontend/package.json +++ b/django_project/frontend/package.json @@ -28,6 +28,7 @@ "@babel/preset-react": "^7.16.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.6", "@types/jquery": "^3.5.32", + "@types/lodash": "^4.17.14", "@types/mapbox__mapbox-gl-draw": "^1.4.8", "@types/mapbox-gl": "^3.4.1", "@types/pluralize": "^0.0.33", diff --git a/django_project/frontend/src/components/Map/Filter/FilterContent.tsx b/django_project/frontend/src/components/Map/Filter/FilterContent.tsx index 02b09e9f8..cf49b7077 100644 --- a/django_project/frontend/src/components/Map/Filter/FilterContent.tsx +++ b/django_project/frontend/src/components/Map/Filter/FilterContent.tsx @@ -12,13 +12,12 @@ * __date__ = '16/01/2025' * __copyright__ = ('Copyright 2023, Unicef') */ -import React, { memo } from "react"; +import React, { memo, useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; import { FilterGroupDataProps } from "./types.d"; -import FilterGroup from "./FilterGroup"; +import FilterGroup from "./Group"; import { INIT_DATA } from "../../../utils/queryExtraction"; import { Actions } from "../../../store/dashboard"; -import FilterControlFiltering from "./FilterControlFiltering"; import './style.scss'; @@ -42,6 +41,18 @@ const FilterControl = ({ isAdmin }: Props) => { Actions.Filters.update({ ...filter }) ) } + + // UpdateFilter callbacks + const updateFilter = useCallback((data: string[]) => { + if (data === undefined) { + dispatcher(Actions.FilteredGeometries.update(null)) + } else if (data === null) { + dispatcher(Actions.FilteredGeometries.update([])) + } else { + dispatcher(Actions.FilteredGeometries.update(data)) + } + }, []); + return { /* Is master */ isMaster={true} isAdmin={isAdmin} + updateFilter={updateFilter} /> }; @@ -66,7 +78,6 @@ const FilterContent = memo(({ isAdmin }: Props) => { - ; }); diff --git a/django_project/frontend/src/components/Map/Filter/FilterControlFiltering.tsx b/django_project/frontend/src/components/Map/Filter/FilterControlFiltering.tsx deleted file mode 100644 index a0467fd35..000000000 --- a/django_project/frontend/src/components/Map/Filter/FilterControlFiltering.tsx +++ /dev/null @@ -1,245 +0,0 @@ -/** - * GeoSight is UNICEF's geospatial web-based business intelligence platform. - * - * Contact : geosight-no-reply@unicef.org - * - * .. note:: This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * __author__ = 'irwan@kartoza.com' - * __date__ = '16/01/2025' - * __copyright__ = ('Copyright 2023, Unicef') - */ -import { useEffect, useRef } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { returnWhere } from "../../../utils/queryExtraction"; -import { Actions } from "../../../store/dashboard"; -import { allDataIsReady, filteredGeoms } from "../../../utils/indicators"; -import { dictDeepCopy } from "../../../utils/main"; - -import './style.scss'; - - -/** TODO: - * We will remove this after filter can be handle by input - * - * @constructor - */ -// @ts-ignore -const FilterControlFiltering = () => { - const { - filters, - indicatorLayers, - referenceLayer, - relatedTables - // @ts-ignore - } = useSelector(state => state.dashboard.data); - // @ts-ignore - const { referenceLayers } = useSelector(state => state.map) - // @ts-ignore - const referenceLayerData = useSelector(state => state.referenceLayerData) - // @ts-ignore - const indicatorsData = useSelector(state => state.indicatorsData) - // @ts-ignore - const relatedTableData = useSelector(state => state.relatedTableData) - // @ts-ignore - const selectedAdminLevel = useSelector(state => state.selectedAdminLevel) - // @ts-ignore - const datasetGeometries = useSelector(state => state.datasetGeometries); - // @ts-ignore - const geometries = useSelector(state => state.datasetGeometries[referenceLayer.identifier]); - // @ts-ignore - const datasets = useSelector(state => state.globalState['datasets']) - - // @ts-ignore - const dispatcher = useDispatch(); - const levels = referenceLayerData[referenceLayer.identifier]?.data?.dataset_levels - - // Set older filters - const prevState = useRef(); - - const filter = async (currentFilter: any) => { - // @ts-ignore - const where = returnWhere(currentFilter) - if (!levels) { - return; - } - - const usedData = [] - for (const [key, value] of Object.entries(indicatorsData)) { - const id = `indicator_${key}` - if (where?.includes(id)) { - usedData.push(value) - } - } - for (const [key, value] of Object.entries(relatedTableData)) { - const id = `related_table_${key}` - if (where?.includes(id)) { - usedData.push(value) - } - } - if (where && !allDataIsReady(usedData)) { - return - } - // @ts-ignore - const level = levels.find(level => level.level === selectedAdminLevel.level) - if (!level) { - return - } - const indicatorLayerConfig = {} - indicatorLayers.map((layer: any) => { - // @ts-ignore - indicatorLayerConfig[layer.id] = layer.config - }) - const reporting_level = level.level; - // Doing the filter if it is different filter - // PREPARE DATA LIST - let dataList = []; - - // --------------------------------------- - // Geometry data - // --------------------------------------- - const codes: any[] = [] - const data: { concept_uuid: any; ucode: any; name: any; }[] = [] - // @ts-ignore - datasets.map(identifier => { - const geometries = datasetGeometries[identifier] - const levels = referenceLayerData[identifier]?.data?.dataset_levels - if (!levels) { - return - } - // @ts-ignore - levels.map(level => { - if (geometries) { - const geoms = geometries[level.level] - if (geoms) { - for (const [key, geomData] of Object.entries(geoms)) { - // @ts-ignore - codes.push(geomData.code) - // @ts-ignore - if (where && where.includes('geometry_layer.') && geomData.members) { - // @ts-ignore - geomData.members.map((member: any) => { - data.push({ - // @ts-ignore - concept_uuid: geomData.concept_uuid, - ucode: member.ucode, - name: member.name, - }) - }) - } else { - data.push({ - // @ts-ignore - concept_uuid: geomData.concept_uuid, - // @ts-ignore - ucode: geomData.ucode, - // @ts-ignore - name: geomData.name, - }) - } - } - } - } - }) - levels.map((level: any) => { - dataList.push({ - id: identifier === referenceLayer?.identifier ? `geometry_layer` : `geometry_layer.${identifier}`, - reporting_level: level.level, - data: data - }) - }) - }) - - // ------------------------------------------------ - // Indicator data - // ------------------------------------------------ - for (const [key, indicatorDataRow] of Object.entries(indicatorsData)) { - const indicator = dictDeepCopy(indicatorDataRow) - if (!isNaN(indicator.id)) { - indicator.id = `indicator_${indicator.id}` - } - indicator.reporting_level = reporting_level - dataList.push(indicator) - if (indicator.data && Array.isArray(indicator.data)) { - const indicatorCodes = indicator.data.map((data: any) => data.concept_uuid) - const missingCodes = codes.filter(code => !indicatorCodes.includes(code)) - missingCodes.map(code => { - indicator.data.push({ - concept_uuid: code, - indicator_id: indicator.id - }) - }) - } - } - - // ------------------------------------------------ - // Related table data - // ------------------------------------------------ - relatedTables.map((relatedTable: any) => { - const objData = relatedTableData[relatedTable.id] - if (objData) { - const data = dictDeepCopy(objData) - data.id = `related_table_${relatedTable.id}` - data.reporting_level = reporting_level - dataList.push(data) - const codes = geometries && geometries[reporting_level] ? Object.keys(geometries[reporting_level]) : [] - if (data.data) { - // TODO : - // We need to update if the related table can receive other code - // We assign geometry code as the related table geography code field name - data.data.map((obj: any) => { - obj.geometry_code = obj[relatedTable.geography_code_field_name] - }) - const indicatorCodes = data.data.map((data: any) => data.concept_uuid) - const missingCodes = codes.filter(code => !indicatorCodes.includes(code)) - missingCodes.map(code => { - data.data.push({ - concept_uuid: code, - id: relatedTable.id - }) - }) - } - } - }) - - // DOING FILTERING - const filteredGeometries = filteredGeoms( - dataList, currentFilter, selectedAdminLevel.level - ) - if (filteredGeometries) { - const usedFilteredGeometry = Array.from(new Set(filteredGeometries)) - usedFilteredGeometry.sort() - // @ts-ignore - if (prevState.usedFilteredGeometry !== usedFilteredGeometry) { - dispatcher( - Actions.FilteredGeometries.update(usedFilteredGeometry) - ) - // @ts-ignore - prevState.usedFilteredGeometry = usedFilteredGeometry - } - } - // @ts-ignore - if (JSON.stringify(prevState.currentFilter) !== JSON.stringify(currentFilter)) { - dispatcher(Actions.FiltersData.update(currentFilter)); - // @ts-ignore - prevState.currentFilter = dictDeepCopy(currentFilter) - } - } - - // Apply the filters query - useEffect(() => { - if (referenceLayerData[referenceLayer.identifier]?.data && - referenceLayerData[referenceLayer.identifier].data.dataset_levels) { - // @ts-ignore - filter(filters) - } - }, [ - datasets, referenceLayers, - filters, indicatorsData, relatedTableData, geometries, selectedAdminLevel - ]); - - return null -}; -export default FilterControlFiltering; \ No newline at end of file diff --git a/django_project/frontend/src/components/Map/Filter/FilterGroup.tsx b/django_project/frontend/src/components/Map/Filter/Group/index.tsx similarity index 77% rename from django_project/frontend/src/components/Map/Filter/FilterGroup.tsx rename to django_project/frontend/src/components/Map/Filter/Group/index.tsx index 46da0498e..5d88beb4f 100644 --- a/django_project/frontend/src/components/Map/Filter/FilterGroup.tsx +++ b/django_project/frontend/src/components/Map/Filter/Group/index.tsx @@ -24,17 +24,20 @@ import { FilterGroupDataProps, FilterGroupElementProps, OPTIONS_TYPES, - TYPE -} from "./types.d"; + TYPE, + WHERE_OPERATOR +} from "../types.d"; +import _ from 'lodash'; import Checkbox from "@mui/material/Checkbox"; -import { SelectPlaceholder } from "../../Input"; -import DeleteFilter from "./FilterDelete"; +import { SelectPlaceholder } from "../../../Input"; +import DeleteFilter from "../FilterDelete"; import Tooltip from "@mui/material/Tooltip"; import AddCircleIcon from "@mui/icons-material/AddCircle"; import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder"; -import FilterEditor from "./FilterEditor"; -import { INIT_DATA } from "../../../utils/queryExtraction"; -import FilterInput from "./Input/Data/FilterInput"; +import FilterEditor from "../Input/FilterEditor"; +import { INIT_DATA } from "../../../../utils/queryExtraction"; +import FilterInput from "../Input"; +import CircularProgress from "@mui/material/CircularProgress"; /** Filter group component */ const FilterGroupElement = memo( @@ -58,7 +61,8 @@ const FilterGroupElement = memo( // Is master isMaster, - isAdmin + isAdmin, + isLoading }: FilterGroupElementProps) => { const modalRef = useRef(null); @@ -68,7 +72,7 @@ const FilterGroupElement = memo( const [ result, setResult ] = useState(null); - console.log(operator) + /** Render **/ return (
@@ -96,6 +100,12 @@ const FilterGroupElement = memo( :
{operator}
} + { + isLoading && +
+ +
+ }
{ isEnabled && <> @@ -151,6 +161,7 @@ const FilterGroup = ( /* Event on delete */ onDelete, + updateFilter, isAdmin }: FilterGroupDataProps @@ -158,6 +169,12 @@ const FilterGroup = ( const active = query.active; const prevActiveRef = useRef(); + // For member + // Null meaning isLoading + // Undefined meaning not being used + const [results, setResults] = useState([]); + const [isLoading, setIsLoading] = useState(false) + /** When active changed **/ useEffect(() => { if (prevActiveRef.current !== undefined && prevActiveRef.current !== active) { @@ -200,6 +217,32 @@ const FilterGroup = ( }, []); + // When field, operator, value changed, make geometries null + useEffect(() => { + const isLoading = !!results.includes(null); + setIsLoading(isLoading) + if (!isLoading) { + const _results = results.filter(row => row !== undefined); + if (!_results.length) { + updateFilter(undefined) + } else { + if (query.operator === WHERE_OPERATOR.AND) { + updateFilter(_.intersection(..._results)); + } else { + let _array: string[] = []; + _results.map(result => { + _array = _array.concat(result) + }) + updateFilter(Array.from(new Set(_array))); + } + } + } else { + updateFilter(null) + } + }, + [results, query.operator] + ); + // RENDER return
{ query.queries?.length > 0 ? @@ -249,6 +293,12 @@ const FilterGroup = ( updateQuery() }} isAdmin={isAdmin} + updateFilter={ + (data: string[]) => { + results[idx] = data + setResults([...results]) + } + } /> : { + results[idx] = data + setResults([...results]) + } + } /> ) diff --git a/django_project/frontend/src/components/Map/Filter/FilterEditor.tsx b/django_project/frontend/src/components/Map/Filter/Input/FilterEditor.tsx similarity index 92% rename from django_project/frontend/src/components/Map/Filter/FilterEditor.tsx rename to django_project/frontend/src/components/Map/Filter/Input/FilterEditor.tsx index ec8f50764..8c60e1c30 100644 --- a/django_project/frontend/src/components/Map/Filter/FilterEditor.tsx +++ b/django_project/frontend/src/components/Map/Filter/Input/FilterEditor.tsx @@ -18,20 +18,24 @@ import React, { useImperativeHandle, useState } from "react"; -import { INIT_DATA, IS_IN, IS_NOT_IN } from "../../../utils/queryExtraction"; -import Modal, { ModalContent, ModalHeader } from "../../Modal"; -import { FilterInputProps } from "./types.d"; import { Button, Checkbox, Input, InputLabel } from "@mui/material"; import FormControlLabel from "@mui/material/FormControlLabel"; -import { FilterFieldOperatorInput } from "./Input/FieldOperator"; -import { FilterInputData } from "./Input/Data/FilterInputData"; - -import './style.scss'; +import { + INIT_DATA, + IS_IN, + IS_NOT_IN +} from "../../../../utils/queryExtraction"; +import Modal, { ModalContent, ModalHeader } from "../../../Modal"; +import { FilterExpressionProps } from "../types.d"; +import { FilterFieldOperatorInput } from "./FieldOperator"; +import { FilterInputData } from "./FilterInputData"; + +import '../style.scss'; export interface FilterEditorModalProps { open: boolean; setOpen: (val: boolean) => void; - inputData: FilterInputProps; + inputData: FilterExpressionProps; onApply: (val: any) => void; } @@ -54,19 +58,16 @@ export function FilterEditorModal( // Field callbacks const setFieldCallback = (value: boolean) => { - console.log(data) setData({ ...data, field: value }) } // Operator callbacks const setOperatorCallback = (value: boolean) => { - console.log(data) setData({ ...data, operator: value }) } // Value callbacks const setValueCallback = (value: boolean) => { - console.log(data) setData({ ...data, value: value }) } diff --git a/django_project/frontend/src/components/Map/Filter/Input/Data/FilterInputData.tsx b/django_project/frontend/src/components/Map/Filter/Input/FilterInputData.tsx similarity index 65% rename from django_project/frontend/src/components/Map/Filter/Input/Data/FilterInputData.tsx rename to django_project/frontend/src/components/Map/Filter/Input/FilterInputData.tsx index f1834746f..640b192d4 100644 --- a/django_project/frontend/src/components/Map/Filter/Input/Data/FilterInputData.tsx +++ b/django_project/frontend/src/components/Map/Filter/Input/FilterInputData.tsx @@ -15,16 +15,18 @@ import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import { useSelector } from "react-redux"; -import { FilterInputProps } from "../../types.d"; +import { FilterExpressionProps } from "../types.d"; import { IS_NOT_NULL, IS_NULL, - OPERATOR -} from "../../../../../utils/queryExtraction"; -import { RequestState } from "../../../../../types"; + OPERATOR, + returnDataToExpression +} from "../../../../utils/queryExtraction"; import { WhereInputValue -} from "../../../../SqlQueryGenerator/WhereQueryGenerator/WhereInput"; +} from "../../../SqlQueryGenerator/WhereQueryGenerator/WhereInput"; +import alasql from "alasql"; +import { dictDeepCopy } from "../../../../utils/main"; export interface FetchSourceDetail { id: string; @@ -80,7 +82,7 @@ export const FetchSourceData = memo( useEffect(() => { const fetched = id + sourceKey + !!stateData?.fetched; if (prevFetchedRef.current !== fetched) { - receiveData(stateData) + receiveData(stateData?.data) } prevFetchedRef.current = fetched; }, [id, sourceKey, stateData]); @@ -91,7 +93,6 @@ export const FetchSourceData = memo( export const FetchSourceGeometryData = memo( ({ field, receiveData }: FetchGeometryData) => { - const key = 'datasetGeometries' // @ts-ignore const identifier = useSelector(state => state.dashboard?.data.referenceLayer?.identifier); let usedIdentifier = identifier @@ -101,29 +102,42 @@ export const FetchSourceGeometryData = memo( } // @ts-ignore - const referenceLayerData = useSelector((state) => { - let data = null - // @ts-ignore - if (state[key] && state[key][usedIdentifier] && state[key][usedIdentifier][level]) { - const data = [] - // @ts-ignore - for (const [_, value] of Object.entries(state[key][usedIdentifier][level])) { - data.push(value) - } - return { data: data } - } - return data; - }) + const referenceLayerData = useSelector((state) => state.datasetGeometries?.[usedIdentifier]) /** Fetch the data **/ useEffect(() => { - receiveData(referenceLayerData) + let data = referenceLayerData ? referenceLayerData[level] : null + if (data) { + const _data = [] + // @ts-ignore + for (const [_, value] of Object.entries(data)) { + _data.push(value) + } + data = _data + _data.map((row: any) => { + row.members.map((member: any) => { + data.push({ + concept_uuid: member.code, + ucode: row.ucode, + name: row.name, + }) + }) + }) + } + receiveData(data) }, [referenceLayerData]); return null } ) + +/** Props for input data **/ +export interface Props extends FilterExpressionProps { + active?: boolean; + onFiltered?: (data: string[]) => void; +} + /** Filter group component */ export const FilterInputData = memo( ( @@ -137,8 +151,13 @@ export const FilterInputData = memo( value, setValue, - isAdmin - }: FilterInputProps + // Is active or isAdmin + active, + isAdmin, + + // On Filtered + onFiltered + }: Props ) => { const isEnabled = isAdmin || allowModify; @@ -166,9 +185,10 @@ export const FilterInputData = memo( /** The state of element **/ const [source, setSource] = useState(null); - const [data, setData] = useState(null); + const [data, setData] = useState(null); + const [result, setResult] = useState(null); - // Receive source detail callbacks + /** Receive source detail callbacks **/ const receiveSource = useCallback((_data: any) => { let update = true if (update && JSON.stringify(_data) == JSON.stringify(data)) { @@ -179,10 +199,26 @@ export const FilterInputData = memo( } }, []); - // Receive data - const receiveData = useCallback((data: any) => { + /** Receive data **/ + const receiveData = useCallback((data: any[]) => { if (data) { - setData(data) + let usedData = data + switch (sourceDataKey) { + case 'indicatorsData': + usedData = data.map( + row => { + return { + admin_level: row.admin_level, + concept_uuid: row.concept_uuid, + geometry_code: row.geometry_code, + label: row.label, + value: row.value + } + } + ) + break; + } + setData(usedData) } else { setData(null) } @@ -192,7 +228,7 @@ export const FilterInputData = memo( // @ts-ignore const operatorName = OPERATOR[operator] - // Check the datatype + /** Check the datatype **/ let dataType = keyField === 'value' ? 'Number' : 'String'; if (sourceDataKey === 'indicatorsData') { dataType = keyField === 'value' ? source?.type : 'String' @@ -209,16 +245,54 @@ export const FilterInputData = memo( } let options: any[] = ['loading'] - if (data?.data) { + if (dataType && data) { try { - options = Array.from(new Set(data.data.map((row: any) => row[keyField]))) + options = Array.from(new Set(data.map((row: any) => row[keyField]))) } catch (err) { } } - if (data?.data == null || !dataType) { - options = ['loading'] - } + + // When field, operator, value changed, make geometries null + useEffect(() => { + setResult(null) + }, + [data, field, operator, value] + ); + + // When active and has data, calculate filter + useEffect(() => { + if (onFiltered) { + if (active && result === null) { + if (data) { + // Run the calculation + setResult(result) + const queryWhere = returnDataToExpression(`data.${keyField}`, operator, value) + const query = `SELECT ARRAY(concept_uuid) AS concept_uuids + FROM ? data + WHERE ${queryWhere} + ORDER BY concept_uuid` + const _result = alasql(query, [data]) + setResult(_result[0].concept_uuids) + } + } else if (result) { + onFiltered(result) + } + } + }, + [active, data] + ); + + // When data, field, operator, value changed + useEffect(() => { + if (onFiltered) { + // if result changed + onFiltered(result) + } + }, + [result] + ); + return <> { sourceDataKey ? <> @@ -246,7 +320,7 @@ export const FilterInputData = memo( } : null } -
+
{keyField} {operatorName}
diff --git a/django_project/frontend/src/components/Map/Filter/Input/Data/FilterInput.tsx b/django_project/frontend/src/components/Map/Filter/Input/index.tsx similarity index 76% rename from django_project/frontend/src/components/Map/Filter/Input/Data/FilterInput.tsx rename to django_project/frontend/src/components/Map/Filter/Input/index.tsx index a9ca9f5a7..8159c70a9 100644 --- a/django_project/frontend/src/components/Map/Filter/Input/Data/FilterInput.tsx +++ b/django_project/frontend/src/components/Map/Filter/Input/index.tsx @@ -13,18 +13,19 @@ * __copyright__ = ('Copyright 2023, Unicef') */ -import React, { memo, useCallback, useRef, useState } from "react"; +import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import Accordion from "@mui/material/Accordion"; import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import Checkbox from "@mui/material/Checkbox"; import { FilterInputData } from "./FilterInputData"; -import { FilterInputDataProps, FilterInputElementProps } from "../../types.d"; -import DeleteFilter from "../../FilterDelete"; -import { EditIcon } from "../../../../Icons"; -import { TYPE } from "../../../../../utils/queryExtraction"; -import FilterEditor from "../../FilterEditor"; +import { FilterInputElementProps, OnDeleteProps } from "../types.d"; +import DeleteFilter from "../FilterDelete"; +import { EditIcon } from "../../../Icons"; +import { TYPE } from "../../../../utils/queryExtraction"; +import FilterEditor from "./FilterEditor"; +import CircularProgress from "@mui/material/CircularProgress"; /** Filter group component */ const FilterInputElement = memo( @@ -47,9 +48,12 @@ const FilterInputElement = memo( onDelete, onEdited, + onFiltered, - isAdmin + isAdmin, + isLoading }: FilterInputElementProps) => { + const modalRef = useRef(null); const [expanded, setExpanded] = useState(false) @@ -66,7 +70,7 @@ const FilterInputElement = memo( >
{ setActive(!active) @@ -76,6 +80,12 @@ const FilterInputElement = memo( }} />
{name}
+ { + isLoading && +
+ +
+ }
{ isEnabled && <> @@ -121,6 +131,8 @@ const FilterInputElement = memo( setValue={setValue} isAdmin={isAdmin} + active={active} + onFiltered={onFiltered} /> { description &&
void; + updateFilter: (result: string[]) => void; +} + /** Filter group component */ const FilterInput = ( { @@ -142,10 +161,13 @@ const FilterInput = ( /* Event on delete */ onDelete, + updateFilter, isAdmin - }: FilterInputDataProps + }: Props ) => { + const [isLoading, setIsLoading] = useState(false) + const [result, setResult] = useState(null); // Active callbacks const setActive = useCallback((value: boolean) => { @@ -180,6 +202,24 @@ const FilterInput = ( updateQuery() }, []); + // result filter callback + const resultFilterCallback = useCallback((data: string[]) => { + setResult(data) + }, []); + + // When field, operator, value changed, make geometries null + useEffect(() => { + if (!query.active) { + setIsLoading(false) + updateFilter(undefined) + } else { + setIsLoading(!result) + updateFilter(result) + } + }, + [query.active, result] + ); + // RENDER return }; diff --git a/django_project/frontend/src/components/Map/Filter/style.scss b/django_project/frontend/src/components/Map/Filter/style.scss index 90e20fb46..80ab73231 100644 --- a/django_project/frontend/src/components/Map/Filter/style.scss +++ b/django_project/frontend/src/components/Map/Filter/style.scss @@ -61,6 +61,10 @@ flex-grow: 1; overflow-y: auto; + .Mui-disabled { + background-color: var(--gray-200); + } + .FilterControlInfo { font-weight: $base-font-medium; font-size: 12px; @@ -558,11 +562,13 @@ .MuiInputSliderWithInput { flex-grow: 1; display: flex; - border-radius: 4px; background-color: white; border: 1px solid rgba(0, 0, 0, 0.23); overflow: hidden; min-width: 300px; + height: 100%; + box-sizing: border-box; + margin-left: -1px; input { border: unset; diff --git a/django_project/frontend/src/components/Map/Filter/types.d.tsx b/django_project/frontend/src/components/Map/Filter/types.d.tsx index ed3156bdb..2cc9e068d 100644 --- a/django_project/frontend/src/components/Map/Filter/types.d.tsx +++ b/django_project/frontend/src/components/Map/Filter/types.d.tsx @@ -55,6 +55,7 @@ export interface FilterGroupElementProps extends BasicFilterElementProps { onCreateNewFilter?: (data: any) => void; onCreateNewGroup?: (data: any) => void; + isLoading: boolean; } export interface FilterGroupDataProps extends OnDeleteProps { @@ -64,12 +65,13 @@ export interface FilterGroupDataProps extends OnDeleteProps { // Is master, no delete isMaster?: boolean; + updateFilter: (result: string[]) => void; onCreateNewFilter?: (data: any) => void; onCreateNewGroup?: (data: any) => void; } -export interface FilterInputProps { +export interface FilterExpressionProps { operator: typeof WHERE_OPERATOR[keyof typeof WHERE_OPERATOR]; // For layout @@ -90,12 +92,8 @@ export interface FilterInputProps { isAdmin: boolean; } -export interface FilterInputElementProps extends FilterInputProps, BasicFilterElementProps { +export interface FilterInputElementProps extends FilterExpressionProps, BasicFilterElementProps { + onFiltered?: (data: string[]) => void; + isLoading: boolean; -} - -export interface FilterInputDataProps extends OnDeleteProps { - // Global data update - query: FilterInputElementProps; - updateQuery?: () => void; } \ No newline at end of file diff --git a/django_project/frontend/src/components/Widget/Summary/View.jsx b/django_project/frontend/src/components/Widget/Summary/View.jsx index d13cd966b..761b40dde 100644 --- a/django_project/frontend/src/components/Widget/Summary/View.jsx +++ b/django_project/frontend/src/components/Widget/Summary/View.jsx @@ -19,8 +19,6 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from "react-redux"; - -import { returnWhere } from "../../../utils/queryExtraction"; import { cleanLayerData } from "../../../utils/indicators" import { fetchingData } from "../../../Requests"; @@ -60,7 +58,6 @@ export default function SummaryWidgetView({ idx, data }) { const indicatorLayerData = useSelector(state => state.indicatorsData[layer_id]); const filteredGeometries = useSelector(state => state.filteredGeometries); const selectedAdminLevel = useSelector(state => state.selectedAdminLevel); - const filtersData = useSelector(state => state.filtersData); const [layerData, setLayerData] = useState({}); const date_filter_type = use_only_last_known_value ? 'No filter' : config.date_filter_type @@ -215,13 +212,12 @@ export default function SummaryWidgetView({ idx, data }) { )() }, [data, selectedAdminLevel, indicatorLayers, date_filter_type, referenceLayer]) - const where = returnWhere(filtersData ? filtersData : []) let indicatorData = null if (layerData) { indicatorData = Object.assign({}, layerData) if (indicatorData?.fetched && indicatorData?.data) { indicatorData.data = indicatorData.data.filter(row => { - return !filteredGeometries || !where || filteredGeometries.includes(row.concept_uuid) + return !filteredGeometries || filteredGeometries?.includes(row.concept_uuid) }) } } diff --git a/django_project/frontend/src/components/Widget/index.jsx b/django_project/frontend/src/components/Widget/index.jsx index b8b0f5533..d41d56ac8 100644 --- a/django_project/frontend/src/components/Widget/index.jsx +++ b/django_project/frontend/src/components/Widget/index.jsx @@ -1,17 +1,17 @@ /** -* GeoSight is UNICEF's geospatial web-based business intelligence platform. -* -* Contact : geosight-no-reply@unicef.org -* -* .. note:: This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* __author__ = 'irwan@kartoza.com' -* __date__ = '13/06/2023' -* __copyright__ = ('Copyright 2023, Unicef') -*/ + * GeoSight is UNICEF's geospatial web-based business intelligence platform. + * + * Contact : geosight-no-reply@unicef.org + * + * .. note:: This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * __author__ = 'irwan@kartoza.com' + * __date__ = '13/06/2023' + * __copyright__ = ('Copyright 2023, Unicef') + */ /* ========================================================================== WIDGET @@ -20,15 +20,11 @@ import React, { Fragment, useEffect, useState } from 'react'; import { useSelector } from "react-redux"; import CircularProgress from '@mui/material/CircularProgress'; - -import { returnWhere } from "../../utils/queryExtraction"; import { fetchingData } from "../../Requests"; import SummaryWidgetView from "./Summary/View"; import { InfoFillIcon } from '../Icons' // Widgets -import SummaryWidget from "./Summary/SummaryWidget" -import SummaryGroupWidget from "./Summary/SummaryGroupWidget" import TimeSeriesChartWidget from "./TimeSeriesChartWidget" import './style.scss'; @@ -57,7 +53,6 @@ export function Widget({ idx, data }) { const { indicators } = useSelector(state => state.dashboard.data); const indicatorLayerData = useSelector(state => state.indicatorsData[layer_id]); const filteredGeometries = useSelector(state => state.filteredGeometries); - const filtersData = useSelector(state => state.filtersData); const [showInfo, setShowInfo] = useState(false); const [layerData, setLayerData] = useState({}); @@ -116,13 +111,12 @@ export function Widget({ idx, data }) { } }, [data]) - const where = returnWhere(filtersData ? filtersData : []) let indicatorData = null if (layer) { indicatorData = Object.assign({}, layerData) if (indicatorData.fetched && indicatorData.data) { indicatorData.data = indicatorData.data.filter(indicator => { - return !filteredGeometries || !where || filteredGeometries.includes(indicator.concept_uuid) + return !filteredGeometries || filteredGeometries?.includes(indicator.concept_uuid) }) } } diff --git a/django_project/frontend/src/pages/Admin/Dashboard/Form/index.jsx b/django_project/frontend/src/pages/Admin/Dashboard/Form/index.jsx index f8ca9e4d2..6803ccfb9 100644 --- a/django_project/frontend/src/pages/Admin/Dashboard/Form/index.jsx +++ b/django_project/frontend/src/pages/Admin/Dashboard/Form/index.jsx @@ -301,7 +301,6 @@ export function DashboardSaveForm( tools } = useSelector(state => state.dashboard.data); const { data } = useSelector(state => state.dashboard); - const filtersData = useSelector(state => state.filtersData); const [submitted, setSubmitted] = useState(false); // Notification @@ -411,7 +410,7 @@ export function DashboardSaveForm( 'extent': extent, 'widgets': widgets, 'widgets_structure': widgetsStructure, - 'filters': filtersData ? filtersData : filters, + 'filters': filters, 'filters_allow_modify': filtersAllowModify, 'permission': permission, 'show_splash_first_open': splashScreen, diff --git a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/Control.jsx b/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/Control.jsx deleted file mode 100644 index 6a09cfb68..000000000 --- a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/Control.jsx +++ /dev/null @@ -1,418 +0,0 @@ -/** - * GeoSight is UNICEF's geospatial web-based business intelligence platform. - * - * Contact : geosight-no-reply@unicef.org - * - * .. note:: This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * __author__ = 'irwan@kartoza.com' - * __date__ = '13/06/2023' - * __copyright__ = ('Copyright 2023, Unicef') - */ - -/* ========================================================================== - Filters CONTROL - ========================================================================== */ - -import React, { Fragment, useEffect, useState } from 'react' -import { useDispatch } from "react-redux" -import Tooltip from '@mui/material/Tooltip' -import { styled } from '@mui/material/styles'; -import AddCircleIcon from '@mui/icons-material/AddCircle' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder' -import Accordion from '@mui/material/Accordion' -import AccordionSummary from '@mui/material/AccordionSummary' -import AccordionDetails from '@mui/material/AccordionDetails' -import Switch from '@mui/material/Switch'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Checkbox from "@mui/material/Checkbox"; -import CircularProgress from "@mui/material/CircularProgress"; -import { - WhereInputValue -} from "../../../../components/SqlQueryGenerator/WhereQueryGenerator/WhereInput"; -import { SelectPlaceholder } from "../../../../components/Input"; -import { - INIT_DATA, - IS_NOT_NULL, - IS_NULL, - OPERATOR, - TYPE, - WHERE_OPERATOR -} from "../../../../utils/queryExtraction" - -import { Actions } from '../../../../store/dashboard' -import { capitalize } from "../../../../utils/main"; -import FilterEditorModal from './Modal' -import { DeleteIcon, EditIcon } from '../../../../components/Icons' - -import './style.scss' - -const Switcher = styled(Switch)(({ theme }) => ({})); -const expandedByFilterField = {} - -export function OperatorSwitcher({ ...props }) { - return } - {...props} - /> -} - -/** - * Control All Filter. - * @param {dict} filtersData Filters of dashboard. - * @param {list} fields Data fields. - * @param {Function} filter Filter function. - * @param {bool} ableToModify Able to modify. - */ -export default function FilterControl( - { filtersData, fields, filter, ableToModify } -) { - const dispatcher = useDispatch() - const [filters, setFilters] = useState(filtersData) - - // Apply the filters query - useEffect(() => { - if (JSON.stringify(filters) !== JSON.stringify(filtersData)) { - setFilters(filtersData) - } - }, [filtersData]); - - /** - * Update Filter - */ - const updateFilter = (force) => { - filter(filters) - if (force) { - dispatcher( - Actions.Filters.update({ ...filters }) - ) - setFilters({ ...filters }); - } - } - - /** -------------------------------------------------- - ** Render filter group. - ** -------------------------------------------------- **/ - const FilterGroup = ({ where, upperWhere }) => { - const [operator, setOperator] = useState(where.operator) - const [open, setOpen] = useState(false) - const [data, setData] = useState(INIT_DATA.WHERE()) - const [addType, setAddType] = useState(null) - - const optionsTypes = [ - { id: WHERE_OPERATOR.AND, name: 'And' }, - { id: WHERE_OPERATOR.OR, name: 'Or' } - ] - - const switchWhere = (operator) => { - setOperator(operator); - where.operator = operator; - updateFilter(true) - } - - const add = (newData) => { - switch (addType) { - case TYPE.EXPRESSION: { - where.queries.push(newData); - break - } - case TYPE.GROUP: - where.queries.push({ - ...INIT_DATA.GROUP(), - queries: [newData] - }); - break - } - updateFilter(true) - } - - // Apply when group control changed - const groupCheckedChanged = (where, checked) => { - where.active = checked - if (where.queries) { - where.queries.map(query => { - groupCheckedChanged(query, checked) - }) - } - updateFilter(true) - } - - const hasMoreQuery = where.queries.length > 1 - - return
-
-
- { - hasMoreQuery ? - { - groupCheckedChanged(where, !where.active) - }} - /> : null - } - { - ableToModify ? - - { - switchWhere(value) - }} - /> -
-
- - { - setOpen(true) - setAddType(TYPE.EXPRESSION) - }}/> - - - { - setOpen(true) - setAddType(TYPE.GROUP) - } - }/> - - { - setOpen(opened) - setData(INIT_DATA.WHERE()); - }} - data={data} - fields={fields} - update={add}/> - { - upperWhere ? ( - - { - let isExecuted = confirm("Do you want to delete this group?"); - if (isExecuted) { - upperWhere.queries = [...upperWhere.queries.filter(query => { - return query !== where - })] - updateFilter(true) - } - } - }/> - - ) : ''} -
-
-
: -
{operator}
- } -
-
- { - where.queries.length > 0 ? - where.queries.map( - (query, idx) => ( - - ) - ) - : -
There is no filter for this group.
- } -
- } - /** -------------------------------------------------- - ** Render input of filter. - ** -------------------------------------------------- **/ - const FilterInput = ({ where, upperWhere }) => { - const [expanded, setExpanded] = useState( - expandedByFilterField[where.name] ? expandedByFilterField[where.name] : false - ) - const [open, setOpen] = useState(false) - const [active, setActive] = useState(where.active) - - const updateExpanded = () => { - setExpanded(!expanded) - expandedByFilterField[where.name] = !expanded - } - const updateActive = (active) => { - setActive(active) - where.active = active - updateFilter() - } - const update = (newWhere) => { - where.field = newWhere.field - where.operator = newWhere.operator - where.value = newWhere.value - where.name = newWhere.name - where.description = newWhere.description - where.allowModify = newWhere.allowModify - expandedByFilterField[where.name] = expanded - updateFilter(true) - } - const field = where.field - const operator = where.operator - const value = where.value - const fieldData = fields.filter((row) => { - return row.id === field - })[0] - // console.log(fieldData) - const fieldName = fieldData?.name - - // When fields changed, remove the where if no field found - // TODO: - // We need to fix geometry one - useEffect(() => { - if (!where.field.includes('geometry_')) { - if (!fields.find(field => field.id === where.field)) { - upperWhere.queries = [...upperWhere.queries.filter(query => { - return query !== where - })] - updateFilter(true) - } - } - }, [fields]); - - /** - * Return filter input - */ - const FilterInputElement = () => { - const [currentValue, setCurrentValue] = useState(value) - const updateValue = (value) => { - const cleanValue = !isNaN(value) ? (!Array.isArray(value) ? Number(value) : value) : value; - setCurrentValue(cleanValue) - where.value = cleanValue - updateFilter() - } - const needsValue = ![IS_NULL, IS_NOT_NULL].includes(operator) - return
- {fieldName ? fieldName : field} {OPERATOR[operator]} - { - needsValue ? -
- -
: "" - } - {where.description ? -
{where.description}
: '' - } -
- } - const ableToExpand = where.allowModify; - const isLoading = !fieldData?.data || fieldData?.data[0] === 'loading' - return - : ""} - > -
{ - updateExpanded() - }}> - { - updateActive(event.target.checked) - }} - onClick={(e) => { - e.stopPropagation() - }} - /> - { - isLoading ? -
-
: null - } - { - where.name ? -
{where.name}
: - fieldName ? -
{capitalize(fieldName.split('.')[1])}
: -
Loading
- } -
- {ableToModify ? - - { - event.stopPropagation() - setOpen(true) - }}/> - { - upperWhere ? ( - - { - let isExecuted = confirm("Do you want to delete this filter?"); - if (isExecuted) { - upperWhere.queries = [...upperWhere.queries.filter(query => { - return query !== where - })] - updateFilter(true) - } - e.stopPropagation() - } - }/> - - ) : '' - } - - - - : ""} -
- - {ableToExpand ? : ""} - -
- } - - /** -------------------------------------------------- - ** Render input of filter. - ** -------------------------------------------------- **/ - const FilterRender = ({ where, upperWhere }) => { - switch (where.type) { - case TYPE.GROUP: - return - case TYPE.EXPRESSION: - return - default: - return '' - } - } - return - - -} \ No newline at end of file diff --git a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/Modal.js b/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/Modal.js deleted file mode 100644 index f44e2a9a0..000000000 --- a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/Modal.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * GeoSight is UNICEF's geospatial web-based business intelligence platform. - * - * Contact : geosight-no-reply@unicef.org - * - * .. note:: This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * __author__ = 'irwan@kartoza.com' - * __date__ = '13/06/2023' - * __copyright__ = ('Copyright 2023, Unicef') - */ - -import React, { useEffect, useState } from "react"; -import { Button, FormControlLabel, Input, InputLabel } from "@mui/material"; -import Checkbox from '@mui/material/Checkbox'; -import { - IS_IN, - IS_NOT_IN, - NUMBER_OPERATORS, - STRING_OPERATORS -} from "../../../../utils/queryExtraction"; -import Modal, { - ModalContent, - ModalHeader -} from "../../../../components/Modal"; -import { SelectPlaceholder } from "../../../../components/Input"; -import { - WhereInputValue -} from "../../../../components/SqlQueryGenerator/WhereQueryGenerator/WhereInput"; - -/*** - * Return modal to edit filter - * @param {boolean} open Open. - * @param {function} setOpen Function to open/close. - * @param {dict} data Data of filter. - * @param {list} fields Fields data - * @param {function} update Function to update the data. - */ -export default function FilterEditorModal( - { open, setOpen, data, fields, update } -) { - /** Update value based on operator **/ - const updateValue = (value) => { - if ([IS_IN, IS_NOT_IN].includes(operator) && !Array.isArray(value)) { - return value ? [value] : [] - } else if (![IS_IN, IS_NOT_IN].includes(operator) && Array.isArray(value)) { - return value[0] ? value[0] : '' - } - return value - } - - const [field, setField] = useState(data.field ? data.field : '') - const [operator, setOperator] = useState(data.operator ? data.operator : '') - const [value, setValue] = useState(data.value ? data.value : '') - const [name, setName] = useState(data.name ? data.name : '') - const [description, setDescription] = useState(data.description ? data.description : '') - const [allowModify, setAllowModify] = useState(['true', true].includes(data.allowModify) ? true : false) - - let currentValue = updateValue(value); - - useEffect(() => { - setField(data.field ? data.field : '') - setOperator(data.operator ? data.operator : '') - setValue(data.value ? data.value : '') - }, [data]); - - /** When data saved */ - const onSave = () => { - let currentValue = updateValue(value); - if (field && operator) { - update({ - ...data, - field: field, - operator: operator, - value: currentValue, - name: name, - description: description, - allowModify: allowModify, - }) - } - } - const fieldData = fields.filter((row) => { - return row.id === field - })[0] - const OPERATOR = ['string', 'text'].includes(fieldData?.type?.toLowerCase()) ? STRING_OPERATORS : NUMBER_OPERATORS - return
{ - event.stopPropagation() - }}> - { - setOpen(false) - } - } - > - { - setOpen(false) - } - }> - {data.field ? "Updating" : "Creating"} new filter - - -
-
- Filter Name* - { - setName(event.target.value) - }}/> - Description - { - setDescription(event.target.value) - }}/> -
-
-
Create rule
-
- { - setField(value) - }}/> - { - return { id: key, name: OPERATOR[key] } - }) - } - initValue={operator} - onChangeFn={(value) => { - setOperator(value) - }}/> - { - field && operator ? : null - } -
-
-
-
- } - onChange={evt => { - setAllowModify(!allowModify) - }} - label={'Allow users to modify filter parameters (values)'}/> -
-
- -
-
-
-
-
-} diff --git a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/ValueInput.js b/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/ValueInput.js deleted file mode 100644 index 28ec3ffe8..000000000 --- a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/ValueInput.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * GeoSight is UNICEF's geospatial web-based business intelligence platform. - * - * Contact : geosight-no-reply@unicef.org - * - * .. note:: This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * __author__ = 'irwan@kartoza.com' - * __date__ = '13/06/2023' - * __copyright__ = ('Copyright 2023, Unicef') - */ - -import React, { Fragment, useEffect, useState } from "react"; -import { Input, } from "@mui/material"; -import Slider from '@mui/material/Slider'; -import { - MultipleSelectWithSearch, - SelectWithSearch, -} from "../../../../components/Input/SelectWithSearch"; -import { - IS_LIKE, - IS_NOT_LIKE, - IS_NOT_NULL, - IS_NULL -} from "../../../../utils/queryExtraction" - -/*** - * Filter Value Input - * @param {str} field Field of filter. - * @param {str} operator Operator of filter. - * @param value Value og filter. - * @param {dict} fieldData Field data that will be used. - * @param {bool} disabled Is filter disabled or not. - * @param {function} onChange When the filter change. - */ -export default function FilterValueInput( - { field, operator, value, fieldData, disabled = false, onChange } -) { - const [initValue, setInitValue] = useState(value); - - let min = null - let max = null - if (fieldData?.data) { - const data = fieldData.data.filter(row => { - return row !== undefined - }).map(row => { - return parseFloat(row) - }) - min = Math.min(...data) - max = Math.max(...data) - if (isNaN(min)) { - min = null - } - if (isNaN(max)) { - max = null - } - } - useEffect(() => { - setInitValue(value) - if (min && max && (initValue === undefined || initValue === '')) { - setInitValue(0) - } - }, [value]); - - const needsValue = ![IS_NULL, IS_NOT_NULL].includes(operator) - return - { - needsValue ? - ['IN', 'NOT IN'].includes(operator) ? - ( - fieldData ? : '' - ) : - ( - ['=', '<>'].includes(operator) ? - ( - fieldData ? - : '' - ) - : - ( - [ - '<', '<=', '>', '>=', IS_LIKE, IS_NOT_LIKE - ].includes(operator) && (min === null || max === null) ? - { - onChange(event.target.value); - }} - disabled={disabled} - /> : ( -
-
- = 5 ? 1 : max <= 1 ? 0.01 : 0.1} - min={min ? min : 0} - max={max ? max : 0} - onChange={(event) => { - setInitValue(event.target.value); - }} - track={['>', '>='].includes(operator) ? "inverted" : false} - onChangeCommitted={(e) => onChange(initValue)} - disabled={disabled} - /> -
- { - onChange(event.target.value); - }} - inputProps={{ - min: min, - max: max, - type: 'number', - }} - disabled={disabled} - /> -
- ) - ) - ) - : "" - } -
-} \ No newline at end of file diff --git a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/index.jsx b/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/index.jsx index 98f7c9b69..e840979be 100644 --- a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/index.jsx +++ b/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/index.jsx @@ -22,8 +22,6 @@ import Accordion from "@mui/material/Accordion"; import AccordionDetails from "@mui/material/AccordionDetails"; import FilterContent from "../../../../components/Map/Filter/FilterContent"; -import './style.scss'; - /** * Filters Accordion. diff --git a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/style.scss b/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/style.scss deleted file mode 100644 index 232d9eba5..000000000 --- a/django_project/frontend/src/pages/Dashboard/LeftPanel/Filters/style.scss +++ /dev/null @@ -1,630 +0,0 @@ -/** -* GeoSight is UNICEF's geospatial web-based business intelligence platform. -* -* Contact : geosight-no-reply@unicef.org -* -* .. note:: This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* __author__ = 'irwan@kartoza.com' -* __date__ = '13/06/2023' -* __copyright__ = ('Copyright 2023, Unicef') -*/ - -.FilterAccordion { - height: 100%; - - .MuiAccordionDetails-root { - height: 100%; - display: flex; - flex-direction: column; - padding: 0; - } -} - -.dashboard__filter { - margin-bottom: 1rem; - - .MuiSvgIcon-root { - cursor: pointer; - } - - .info__button { - height: 25px; - width: 25px; - - .MuiSvgIcon-root { - height: 100%; - width: 100%; - } - } -} - -.dashboard__filter__content { - padding-left: 1rem; -} - -/* ========================================================================== - FILTERS CONTROL COMPONENT - ========================================================================== */ -.FilterControl { - padding: 0.5rem; - font-size: 0.8rem; - flex-grow: 1; - overflow-y: auto; - - .FilterControlInfo { - font-weight: var(--base-font-medium); - font-size: 12px; - color: #7E7E7E; - } - - // GROUP SWITCHER - .GroupSwitcher { - margin-left: -.5rem; - margin-right: -.5rem; - - .Mui-checked { - .MuiSwitch-thumb { - background-color: var(--secondary-color); - } - } - } - - .OperatorIdentifier { - padding: 0.5rem; - } - - .ReactSelect__control { - background-color: #ADE0FF; - border-color: #ADE0FF; - } - - // OPERATOR SWITCHER - .OperatorSwitcher { - margin-left: -1px; - - .MuiSwitch-root { - height: 24px; - width: 73px; - padding: 4px; - } - - .MuiButtonBase-root { - padding: 4px; - align-items: flex-start; - - &.Mui-checked { - -webkit-transform: translateX(34px); - -moz-transform: translateX(34px); - -ms-transform: translateX(34px); - transform: translateX(34px); - - .MuiSwitch-thumb { - border-radius: 0 var(--border-radius) var(--border-radius) 0; - } - } - } - - .MuiSwitch-thumb { - box-shadow: none; - background-color: var(--secondary-color); - border-radius: var(--border-radius) 0 0 var(--border-radius); - width: 32px; - height: 16px; - } - - .MuiSwitch-track { - background-color: var(--anti-primary-color) !important; - border-radius: var(--border-radius); - opacity: 1 !important; - - &:before, &:after { - content: '""'; - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 16px; - height: 19px; - } - - &:before { - content: 'AND'; - left: 9px; - color: var(--primary-color); - } - - &:after { - content: 'OR'; - right: 13px; - color: var(--primary-color); - } - } - } - - .MuiAccordionSummary-content { - color: var(--base-black-color) !important; - font-size: 0.8rem !important; - } - - .FilterGroup { - padding: 10px 8px 10px 8px; - position: relative; - border-radius: var(--border-radius); - background-color: rgba(0, 0, 0, 0.03); - - & > .MuiPaper-root { - margin-top: 5px !important; - margin-bottom: 0 !important; - - & > .MuiButtonBase-root { - align-items: center !important; - height: 30px; - min-height: 30px; - - .MuiAccordionSummary-content { - margin: 0 !important; - height: 100%; - align-items: center; - } - - .FilterEdit { - color: var(--tab-active-color); - width: 12px; - margin-right: 5px; - } - - .FilterDelete { - color: var(--tab-active-color); - width: 9px; - } - } - } - - &.Hidden { - display: none; - } - - .FilterGroupHeader { - box-sizing: border-box; - left: 0; - width: 100%; - align-items: center; - - .FilterGroupOption { - background-color: #F8F9F9; - border-radius: var(--border-radius); - display: flex; - align-items: center; - min-height: 30px; - padding-left: 8px; - - .MuiButtonBase-root { - margin-top: -3px; - } - - .FilterGroupName { - flex-grow: 1; - } - - .FilterGroupEnd { - width: 5px; - } - - .MuiButtonLike { - color: white; - background: var(--primary-color); - height: 16px; - margin-top: 0.2rem; - margin-left: 0.2rem; - width: 18px; - - &:hover { - opacity: 0.8; - } - } - - .FilterOperatorToggler { - color: white; - padding: 2px 10px; - margin-left: 10px; - font-size: 12px; - background: var(--primary-color); - border-radius: 5px; - cursor: pointer; - } - - .FilterGroupAddExpression, - .FilterGroupAdd, - .FilterGroupDelete { - background-color: rgba(0, 0, 0, 0); - color: var(--primary-color); - } - - div.MuiInputBase-root { - height: 20px !important; - background-color: #ADE0FF; - margin-left: 0.5rem; - - .MuiSelect-select { - min-height: unset; - height: 100%; - padding-bottom: 0; - padding-top: 4px; - } - - &.Mui-disabled { - svg { - display: none; - } - } - - svg { - color: var(--primary-color); - } - } - } - } - } - - .FilterNote { - color: var(--base-black-color); - font-style: italic; - opacity: var(--base-hovered-opacity); - padding: 1rem; - } - - .MuiPaper-root { - &::before { - display: none; - } - } - - .FilterExpression { - background-color: #FFFFFF; - - &.Disabled { - cursor: not-allowed; - - .FilterExpressionName { - opacity: 0.5; - } - } - - .FilterInfo { - padding-top: 2px; - margin-right: 4px; - - svg { - margin: 0; - } - } - - .FilterExpressionName { - flex-grow: 1; - display: flex; - position: relative; - - .Throbber { - position: absolute; - left: 5px; - top: -2px; - } - - div { - margin-top: 2px; - } - } - - .FilterExpressionDescription { - margin-top: 0.5rem; - font-style: italic; - } - - .MuiAccordionSummary-root { - padding: 5px !important; - border: 1px solid #ddd; - margin-top: -1px; - - &.Mui-focusVisible { - background: #ddd !important; - } - - .MuiCheckbox-root { - padding: 0; - margin-right: 5px; - - svg { - background-color: white; - } - } - - .MuiSwitch-root { - margin-top: -3px; - margin-right: 3px; - } - - .FilterDelete { - color: var(--error); - margin-right: 5px; - } - } - - .MuiAccordionDetails-root { - & > div { - border: 1px solid #ddd; - padding: 1rem !important; - border-top: unset !important; - } - - input { - padding: 1rem 0.5rem; - } - - .MuiInputSlider { - padding-top: 3px; - } - } - } - - .FilterInputWrapperChanged { - .MuiSvgIcon-root { - color: green !important; - } - } - - .FilterInputWrapper { - display: flex; - margin-top: 5px; - width: 100%; - border: 1px solid var(--primary-color); - - .SelectWithSearchInput, - .MuiAutocomplete-root { - border: unset !important; - outline: unset !important; - width: 100%; - } - - .MuiSvgIcon-root { - color: #ddd; - margin: 7px 3px; - width: 16px; - } - - .MuiOutlinedInput-root { - .MuiSelect-select { - padding: 0; - } - - fieldset { - display: none; - } - - .MuiSvgIcon-root { - margin: 0; - } - } - - - .SelectWithSearchInput, - .MultipleSelectWithSearch, - .SelectWithSearch { - - .MuiInputBase-root { - padding-right: 22px !important; - min-width: 170px; - max-width: 100%; - height: 100%; - } - } - } - - .MuiInputSliderWithInput { - display: flex; - width: 100%; - - .MuiInputSlider { - flex-grow: 1; - padding: 0 14px; - } - - .MuiSlider-root { - display: block; - } - - .MuiInput-root { - border-left: 1px solid var(--primary-color); - padding: 5px 3px 0 3px; - width: 50px; - font-size: 0.8rem; - - input { - -moz-appearance: textfield; - } - - input::-webkit-outer-spin-button, - input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - &::before, - &::after { - display: none; - } - } - - .MuiInputBase-root:first-child { - border-left: unset !important; - border-right: 1px solid var(--primary-color) !important; - } - } -} - -.FilterEditModal { - .modal--box { - overflow: visible; - width: 50%; - max-width: unset; - } - - .MuiInputLabel-root { - color: black; - } - - .MuiFormControl-root { - margin-top: 0 !important; - } - - .MuiInput-root { - margin-bottom: 2rem; - width: 100%; - } - - .FilterEditModalQuery { - display: flex; - background: var(--base-alpha-color); - padding: 10px; - - .WhereConfigurationOperatorValue { - padding: 0 !important; - - input { - border: unset !important; - height: 100%; - } - } - - .SelectWithSearchInput, - .MuiAutocomplete-root { - height: 56px; - background-color: white; - } - - .FilterEditModalQueryField { - max-width: 40%; - } - - .DatePickerInput { - input { - height: 50px; - width: 100%; - min-width: 500px; - } - } - - .MuiOutlinedInput-root { - background-color: white; - } - - .MuiInput-root { - background-color: white; - border-radius: 4px; - border: 1px solid rgba(0, 0, 0, 0.23); - padding: 5px 14px; - margin-bottom: 0 !important; - - &::before { - display: none; - } - } - - .MuiAutocomplete-root { - flex-grow: 1; - } - - .MuiInputSliderWithInput { - flex-grow: 1; - display: flex; - border-radius: 4px; - background-color: white; - border: 1px solid rgba(0, 0, 0, 0.23); - overflow: hidden; - min-width: 300px; - - input { - border: unset; - } - - .MuiInputSlider { - flex-grow: 1; - padding: 12px 20px 0 20px; - border-left: 1px solid rgba(0, 0, 0, 0.23); - min-width: 100px; - } - - .MuiInput-root { - padding: 0; - width: 100px; - border-radius: 0; - border: none; - border-left: 1px solid rgba(0, 0, 0, 0.23); - - input { - -moz-appearance: textfield; - padding: 10px 5px; - height: 100%; - } - - input::-webkit-outer-spin-button, - input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - } - } - } - - div.button-div { - display: flex; - flex-direction: row-reverse; - - .MuiButton-root { - width: 15%; - } - } -} - -/* ========================================================================== - FILTERS CONTROL SUMMARY - ========================================================================== */ -.FilterControlSummary { - margin-top: 20px; - max-height: 5rem; - border-top: 1px solid #DDD; - overflow: auto; - font-size: 0.8rem; - padding: 10px; - - .FilterControlSummaryContent { - border: 1px solid var(--primary-color); - } -} - -.FilterDatePickerInput { - padding: 0 !important; - min-height: 2rem; - - .react-datepicker-wrapper, - .react-datepicker__input-container { - height: 100%; - width: 100%; - } - - input { - height: 100%; - width: 100%; - background: none; - border: none; - padding: 0 14px; - - &:hover, - &:focus, - &:focus-within { - outline: none; - } - } -} \ No newline at end of file diff --git a/django_project/frontend/src/pages/Dashboard/MapLibre/Layers/ReferenceLayer/index.jsx b/django_project/frontend/src/pages/Dashboard/MapLibre/Layers/ReferenceLayer/index.jsx index d97272f33..73336d54b 100644 --- a/django_project/frontend/src/pages/Dashboard/MapLibre/Layers/ReferenceLayer/index.jsx +++ b/django_project/frontend/src/pages/Dashboard/MapLibre/Layers/ReferenceLayer/index.jsx @@ -29,7 +29,6 @@ import { updateToken } from '../../../../../utils/georepo' import { allDataIsReady } from "../../../../../utils/indicators"; -import { returnWhere } from "../../../../../utils/queryExtraction"; import { returnStyle } from "../../../../../utils/referenceLayer"; import { dictDeepCopy, hexToRGBList } from '../../../../../utils/main' import { @@ -99,8 +98,7 @@ export function ReferenceLayer( const referenceLayerData = useSelector(state => state.referenceLayerData[referenceLayer?.identifier]); const indicatorsData = useSelector(state => state.indicatorsData); const relatedTableData = useSelector(state => state.relatedTableData); - const filtersData = useSelector(state => state.filtersData); - const filteredGeometriesState = useSelector(state => state.filteredGeometries); + const filteredGeometries = useSelector(state => state.filteredGeometries); const currentIndicatorLayer = useSelector(state => state.selectedIndicatorLayer); const currentIndicatorSecondLayer = useSelector(state => state.selectedIndicatorSecondLayer); const selectedAdminLevel = useSelector(state => state.selectedAdminLevel); @@ -114,13 +112,10 @@ export function ReferenceLayer( const geomFieldOnVectorTile = geoField === 'geometry_code' ? 'ucode' : geoField const compareOutlineSize = preferences.style_compare_mode_outline_size - const where = returnWhere(filtersData ? filtersData : []) - const isReady = () => { return map && hasLayer(map, FILL_LAYER_ID) && hasLayer(map, OUTLINE_LAYER_ID) } - const filteredGeometries = where ? filteredGeometriesState : null let levels = referenceLayerData?.data?.dataset_levels let currentLevel = selectedAdminLevel ? selectedAdminLevel.level : levels?.level @@ -169,14 +164,14 @@ export function ReferenceLayer( // Rerender if filter changed. useEffect(() => { - const whereStr = JSON.stringify(where) - const filteredGeometriesStr = JSON.stringify(filteredGeometries) - Logger.log('FILTERED_GEOM:', filteredGeometriesStr) - if (prevState.where !== whereStr || prevState.filteredGeometries !== filteredGeometriesStr) { - updateFilter() - prevState.where = whereStr - prevState.filteredGeometries = filteredGeometriesStr + if (IS_DEBUG) { + if (filteredGeometries) { + filteredGeometries.sort() + } + const filteredGeometriesStr = JSON.stringify(filteredGeometries) + Logger.log('FILTERED_GEOM:', filteredGeometriesStr) } + updateFilter() }, [filteredGeometries, layerCreated]); // Rerender when map changed. @@ -292,16 +287,8 @@ export function ReferenceLayer( * Check codes of geometries */ const checkCodes = () => { - let whereStr = null - if (where) { - whereStr = JSON.stringify(where) - } if (isReady()) { - if (whereStr && filteredGeometries) { - return filteredGeometries - } else { - return null - } + return filteredGeometries } return null } diff --git a/django_project/frontend/src/pages/Dashboard/MapLibre/ReferenceLayerCentroid/index.jsx b/django_project/frontend/src/pages/Dashboard/MapLibre/ReferenceLayerCentroid/index.jsx index 37907b11c..03a592b16 100644 --- a/django_project/frontend/src/pages/Dashboard/MapLibre/ReferenceLayerCentroid/index.jsx +++ b/django_project/frontend/src/pages/Dashboard/MapLibre/ReferenceLayerCentroid/index.jsx @@ -60,11 +60,9 @@ export default function ReferenceLayerCentroid({ map }) { const selectedIndicatorSecondLayer = useSelector(state => state.selectedIndicatorSecondLayer) const selectedAdminLevel = useSelector(state => state.selectedAdminLevel) const mapGeometryValue = useSelector(state => state.mapGeometryValue) - const filtersData = useSelector(state => state.filtersData); const [geometries, setGeometries] = useState({}); - const where = returnWhere(filtersData ? filtersData : []) // When reference layer changed, fetch features useEffect(() => { const currGeometries = {} @@ -251,7 +249,7 @@ export default function ReferenceLayerCentroid({ map }) { const features = [] let theGeometries = Object.keys(geometriesData) - if (where && usedFilteredGeometries) { + if (usedFilteredGeometries) { theGeometries = usedFilteredGeometries } diff --git a/django_project/frontend/src/pages/Dashboard/MiddlePanel/GlobalDateSelector/PlayControl/index.jsx b/django_project/frontend/src/pages/Dashboard/MiddlePanel/GlobalDateSelector/PlayControl/index.jsx index 178332013..1c5d91f11 100644 --- a/django_project/frontend/src/pages/Dashboard/MiddlePanel/GlobalDateSelector/PlayControl/index.jsx +++ b/django_project/frontend/src/pages/Dashboard/MiddlePanel/GlobalDateSelector/PlayControl/index.jsx @@ -39,10 +39,10 @@ export default function PlayControl( { dates, selectedDatePoint, setSelectedDatePoint, children } ) { const { + filters, referenceLayer, indicatorLayers } = useSelector(state => state.dashboard.data) - const filtersData = useSelector(state => state.filtersData); const indicatorsData = useSelector(state => state.indicatorsData) const relatedTableData = useSelector(state => state.relatedTableData) const currentIndicatorLayer = useSelector(state => state.selectedIndicatorLayer) @@ -86,7 +86,7 @@ export default function PlayControl( useEffect(() => { if (isPlay) { const speedTime = minSpeed * 1000 / speed; - const where = returnWhere(filtersData ? filtersData : []) + const where = returnWhere(filters ? filters : []) let layers = [currentIndicatorLayer, selectedIndicatorSecondLayer] if (where) { layers = indicatorLayers diff --git a/django_project/frontend/src/pages/Dashboard/MiddlePanel/MapLegend/index.jsx b/django_project/frontend/src/pages/Dashboard/MiddlePanel/MapLegend/index.jsx index 18f95e569..eaaa94dd5 100644 --- a/django_project/frontend/src/pages/Dashboard/MiddlePanel/MapLegend/index.jsx +++ b/django_project/frontend/src/pages/Dashboard/MiddlePanel/MapLegend/index.jsx @@ -29,7 +29,6 @@ import { import './style.scss' import { allDataIsReady } from "../../../../utils/indicators"; -import { returnWhere } from "../../../../utils/queryExtraction"; /** @@ -89,11 +88,7 @@ const RenderIndicatorLegend = ({ layer, name }) => { const selectedAdminLevel = useSelector(state => state.selectedAdminLevel) const indicatorsData = useSelector(state => state.indicatorsData); const relatedTableData = useSelector(state => state.relatedTableData); - const filteredGeometriesState = useSelector(state => state.filteredGeometries); - const filtersData = useSelector(state => state.filtersData); - - const where = returnWhere(filtersData ? filtersData : []) - const filteredGeometries = where ? filteredGeometriesState : null + const filteredGeometries = useSelector(state => state.filteredGeometries); if (layer.multi_indicator_mode === 'Pin') { return layer.indicators.map(indicator => { diff --git a/django_project/frontend/src/pages/Dashboard/Toolbars/Bookmark/index.jsx b/django_project/frontend/src/pages/Dashboard/Toolbars/Bookmark/index.jsx index 9310d1b53..d1c5c0cc4 100644 --- a/django_project/frontend/src/pages/Dashboard/Toolbars/Bookmark/index.jsx +++ b/django_project/frontend/src/pages/Dashboard/Toolbars/Bookmark/index.jsx @@ -50,6 +50,7 @@ import { import { compareFilters, filtersToFlatDict } from "../../../../utils/filters"; import './style.scss'; +import { INIT_DATA } from "../../../../utils/queryExtraction"; /** * Bookmark component. @@ -58,7 +59,6 @@ export default function Bookmark({ map }) { const dispatch = useDispatch(); const isEmbed = EmbedConfig().id; const dashboardData = useSelector(state => state.dashboard.data); - const filtersData = useSelector(state => state.filtersData); const selectedIndicatorLayer = useSelector(state => state.selectedIndicatorLayer); const selectedIndicatorSecondLayer = useSelector(state => state.selectedIndicatorSecondLayer); const selectedAdminLevel = useSelector(state => state.selectedAdminLevel); @@ -101,7 +101,7 @@ export default function Bookmark({ map }) { selectedBasemap: basemapLayer?.id, selectedIndicatorLayers: selectedIndicatorLayers, selectedContextLayers: Object.keys(contextLayers).map(id => parseInt(id)), - filters: filtersData, + filters: dashboardData.filters ? dashboardData.filters : INIT_DATA.GROUP(), extent: extent, indicatorShow: indicatorShow, contextLayersShow: contextLayersShow, @@ -200,6 +200,7 @@ export default function Bookmark({ map }) { * Save function based on url */ const save = (url) => { + console.log(data()) $.ajax({ url: url, data: { @@ -253,7 +254,7 @@ export default function Bookmark({ map }) { }} Button={
- + diff --git a/django_project/frontend/src/store/dashboard/index.js b/django_project/frontend/src/store/dashboard/index.js index cd8c220c4..0365426d8 100644 --- a/django_project/frontend/src/store/dashboard/index.js +++ b/django_project/frontend/src/store/dashboard/index.js @@ -24,7 +24,6 @@ import DashboardTool from './reducers/dashboardTool/actions' import DatasetGeometries from './reducers/datasetGeometries/actions' import Extent from './reducers/extent/actions' import Filters from './reducers/filters/actions' -import FiltersData from './reducers/filtersData/actions' import FilteredGeometries from './reducers/filteredGeometries/actions' import GlobalState from './reducers/globalState/actions' import IndicatorLayers from './reducers/indicatorLayers/actions' @@ -62,7 +61,6 @@ const Actions = { Extent, Filters, FilteredGeometries, - FiltersData, GlobalState, IndicatorLayers, Indicators, diff --git a/django_project/frontend/src/store/dashboard/reducers/filteredGeometries/index.jsx b/django_project/frontend/src/store/dashboard/reducers/filteredGeometries/index.jsx index f043d244c..c8387947f 100644 --- a/django_project/frontend/src/store/dashboard/reducers/filteredGeometries/index.jsx +++ b/django_project/frontend/src/store/dashboard/reducers/filteredGeometries/index.jsx @@ -1,17 +1,17 @@ /** -* GeoSight is UNICEF's geospatial web-based business intelligence platform. -* -* Contact : geosight-no-reply@unicef.org -* -* .. note:: This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* __author__ = 'irwan@kartoza.com' -* __date__ = '13/06/2023' -* __copyright__ = ('Copyright 2023, Unicef') -*/ + * GeoSight is UNICEF's geospatial web-based business intelligence platform. + * + * Contact : geosight-no-reply@unicef.org + * + * .. note:: This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * __author__ = 'irwan@kartoza.com' + * __date__ = '13/06/2023' + * __copyright__ = ('Copyright 2023, Unicef') + */ /** * FILTERED_GEOMETRIES_ACTION_NAME reducer @@ -26,7 +26,15 @@ export default function filteredGeometriesReducer(state = initialState, action) switch (action.type) { case FILTERED_GEOMETRIES_ACTION_TYPE_UPDATE: { if (action.payload) { - return [...action.payload.filter(geom => geom !== undefined)] + try { + if (JSON.stringify(state) !== JSON.stringify(action.payload)) { + return action.payload + } else { + return state + } + } catch (err) { + return action.payload + } } else { return null } diff --git a/django_project/frontend/src/store/dashboard/reducers/filtersData/actions.jsx b/django_project/frontend/src/store/dashboard/reducers/filtersData/actions.jsx deleted file mode 100644 index 6292711ac..000000000 --- a/django_project/frontend/src/store/dashboard/reducers/filtersData/actions.jsx +++ /dev/null @@ -1,36 +0,0 @@ -/** -* GeoSight is UNICEF's geospatial web-based business intelligence platform. -* -* Contact : geosight-no-reply@unicef.org -* -* .. note:: This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* __author__ = 'irwan@kartoza.com' -* __date__ = '13/06/2023' -* __copyright__ = ('Copyright 2023, Unicef') -*/ - -import { - FILTERS_QUERY_ACTION_NAME, - FILTERS_QUERY_ACTION_UPDATE -} from './index'; - -/** - * Change data of filter. - * @param {object} payload Filter data. - */ -export function update(payload) { - return { - name: FILTERS_QUERY_ACTION_NAME, - type: FILTERS_QUERY_ACTION_UPDATE, - payload: payload - }; -} - - -export default { - update -} \ No newline at end of file diff --git a/django_project/frontend/src/store/dashboard/reducers/filtersData/index.jsx b/django_project/frontend/src/store/dashboard/reducers/filtersData/index.jsx deleted file mode 100644 index c33850211..000000000 --- a/django_project/frontend/src/store/dashboard/reducers/filtersData/index.jsx +++ /dev/null @@ -1,37 +0,0 @@ -/** -* GeoSight is UNICEF's geospatial web-based business intelligence platform. -* -* Contact : geosight-no-reply@unicef.org -* -* .. note:: This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* __author__ = 'irwan@kartoza.com' -* __date__ = '13/06/2023' -* __copyright__ = ('Copyright 2023, Unicef') -*/ - -/** - * FILTERS reducer - */ - -export const FILTERS_QUERY_ACTION_NAME = 'FILTERS_QUERY'; -export const FILTERS_QUERY_ACTION_UPDATE = 'FILTERS_QUERY/UPDATE'; - -const initialState = null -export default function filtersDataReducer(state = initialState, action) { - if (action.name === FILTERS_QUERY_ACTION_NAME) { - switch (action.type) { - case FILTERS_QUERY_ACTION_UPDATE: - if (action.payload) { - return {...action.payload} - } - break; - default: - return state - } - } - return state -} \ No newline at end of file diff --git a/django_project/frontend/src/store/dashboard/reducers/index.tsx b/django_project/frontend/src/store/dashboard/reducers/index.tsx index 55e2301cf..cf0c8cb76 100644 --- a/django_project/frontend/src/store/dashboard/reducers/index.tsx +++ b/django_project/frontend/src/store/dashboard/reducers/index.tsx @@ -25,7 +25,6 @@ import indicatorsMetadataReducer from "./indicatorsMetadata"; import indicatorLayerMetadataReducer from "./indicatorLayerMetadata"; import relatedTableDataReducer from "./relatedTableData"; import ReferenceLayerDataReducer from "./referenceLayerData"; -import filtersDataReducer from "./filtersData"; import filteredGeometriesReducer from "./filteredGeometries"; import globalStateReducer from "./globalState"; import selectedIndicatorLayerReducer from "./selectedIndicatorLayer"; @@ -53,7 +52,6 @@ const rootReducer = combineReducers({ indicatorLayerMetadata: indicatorLayerMetadataReducer, referenceLayerData: ReferenceLayerDataReducer, relatedTableData: relatedTableDataReducer, - filtersData: filtersDataReducer, filteredGeometries: filteredGeometriesReducer, globalState: globalStateReducer, selectedAdminLevel: selectedAdminLevelReducer, diff --git a/django_project/frontend/src/store/dashboard/reducers/indicatorsData/index.jsx b/django_project/frontend/src/store/dashboard/reducers/indicatorsData/index.jsx index 5ca025adc..da9edbede 100644 --- a/django_project/frontend/src/store/dashboard/reducers/indicatorsData/index.jsx +++ b/django_project/frontend/src/store/dashboard/reducers/indicatorsData/index.jsx @@ -1,4 +1,4 @@ - /** +/** * GeoSight is UNICEF's geospatial web-based business intelligence platform. * * Contact : geosight-no-reply@unicef.org @@ -26,7 +26,7 @@ export default function IndicatorsDataReducer(state = initialState, action) { if (action.name === INDICATORS_DATA_ACTION_NAME) { switch (action.type) { default: { - const data = APIReducer(state, action, INDICATORS_DATA_ACTION_NAME) + const data = APIReducer(state, action, INDICATORS_DATA_ACTION_NAME, []) const { id } = action if (Object.keys(data).length !== 0) { data.id = id; diff --git a/django_project/frontend/src/utils/queryExtraction.jsx b/django_project/frontend/src/utils/queryExtraction.jsx index 47c590fc7..dc330d6e8 100644 --- a/django_project/frontend/src/utils/queryExtraction.jsx +++ b/django_project/frontend/src/utils/queryExtraction.jsx @@ -339,6 +339,10 @@ const cleanValueFn = (value, returnEmpty = false) => { * Return DATA in SQL */ export function returnDataToExpression(field, operator, value) { + // Fix field text + const needQuote = isNeedQuote(field) + field = needQuote ? `"${field}"`.replaceAll('""', '"') : field + const cleanOperator = operator let cleanValue = cleanValueFn(value)