From fa5cb9aeb596776d5139d0df1d6846906c4333b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20P=C3=A9rez=20Sampayo?= Date: Thu, 21 Mar 2024 11:29:36 +0100 Subject: [PATCH 1/3] Context Layer based on related table Related Field on general context layer form WIP Filter related table data Clean django code Clean django code Clean django code Clean django code Clean django code --- .../ContextLayer/Form/RelatedTableFields.jsx | 114 ++++++++++++++++++ .../pages/Admin/ContextLayer/Form/index.jsx | 49 ++++++-- .../Admin/ContextLayer/StyleConfig/Map.jsx | 2 +- .../Admin/ContextLayer/StyleConfig/index.jsx | 104 ++++++++++------ .../LeftPanel/ContextLayers/Layer.jsx | 16 +++ .../MapLibre/LayerType/RelatedTable.js | 74 ++++++++++++ .../MapLibre/Layers/ContextLayers/index.jsx | 19 ++- .../frontend/src/utils/relatedTable.js | 29 +++++ .../geosight/data/forms/context_layer.py | 20 +++ .../migrations/0104_auto_20240321_1429.py | 69 +++++++++++ .../geosight/data/models/context_layer.py | 42 ++++++- .../geosight/data/serializer/context_layer.py | 15 +-- 12 files changed, 492 insertions(+), 61 deletions(-) create mode 100644 django_project/frontend/src/pages/Admin/ContextLayer/Form/RelatedTableFields.jsx create mode 100644 django_project/frontend/src/pages/Dashboard/MapLibre/LayerType/RelatedTable.js create mode 100644 django_project/geosight/data/migrations/0104_auto_20240321_1429.py diff --git a/django_project/frontend/src/pages/Admin/ContextLayer/Form/RelatedTableFields.jsx b/django_project/frontend/src/pages/Admin/ContextLayer/Form/RelatedTableFields.jsx new file mode 100644 index 000000000..63a90a304 --- /dev/null +++ b/django_project/frontend/src/pages/Admin/ContextLayer/Form/RelatedTableFields.jsx @@ -0,0 +1,114 @@ +/** + * 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__ = 'francisco.perez@geomati.co' + * __date__ = '20/03/2024' + * __copyright__ = ('Copyright 2024, Unicef') + */ + +import React, { Fragment, useEffect, useState } from 'react'; + + +import { SelectWithSearch } from "../../../../components/Input/SelectWithSearch"; +import WhereInputModal from "../../../../components/SqlQueryGenerator/WhereInputModal"; +import { getRelatedTableFields } from "../../../../utils/relatedTable"; +import { fetchingData } from "../../../../Requests"; +import { dictDeepCopy } from "../../../../utils/main"; + + +/** + * Indicator Form App + * @param {dict} data Data of context layer. + * @param {boolean} checkConfig Checking config. + */ +export default function RelatedTableFields( + { + data, + onSetData + } +) { + const [relatedTableInfo, setRelatedTableInfo] = useState(null) + const [relatedTableData, setRelatedTableData] = useState(null) + + // Loading data + useEffect(() => { + if (!open) { + return + } + if (data.related_table) { + const params = {} + const url_info = `/api/related-table/${data.related_table}` + const url_data = `/api/related-table/${data.related_table}/data` + setRelatedTableInfo(null) + setRelatedTableData(null) + fetchingData( + url_data, params, {}, function (response, error) { + setRelatedTableData(dictDeepCopy(response)) + } + ) + fetchingData( + url_info, params, {}, function (response, error) { + setRelatedTableInfo(dictDeepCopy(response)) + } + ) + } + }, [data.related_table]) + + const relatedFields = relatedTableInfo && relatedTableData ? getRelatedTableFields(relatedTableInfo, relatedTableData) : [] + return ( +
+
+
Latitude Field
+
+ { + onSetData({ ...data, latitude: evt }) + }} + options={relatedFields.filter(rf => rf.type === 'number').map(rf => rf.name)} + className='FilterInput' /> +
+
+
+
Longitude Field
+
+ { + onSetData({ ...data, longitude: evt }) + }} + options={relatedFields.filter(rf => rf.type === 'number').map(rf => rf.name)} + className='FilterInput' /> +
+
+
+
Datetime field
+
+ { + onSetData({ ...data, datetime: evt }) + }} + options={relatedFields.filter(rf => rf.type === 'date').map(rf => rf.name)} + className='FilterInput' /> +
+
+ + { + onSetData({ ...data, query: evt }) + }} + title={"Filter the Data"} + /> +
+ ) +} \ No newline at end of file diff --git a/django_project/frontend/src/pages/Admin/ContextLayer/Form/index.jsx b/django_project/frontend/src/pages/Admin/ContextLayer/Form/index.jsx index 489a0d4b0..492c29859 100644 --- a/django_project/frontend/src/pages/Admin/ContextLayer/Form/index.jsx +++ b/django_project/frontend/src/pages/Admin/ContextLayer/Form/index.jsx @@ -21,8 +21,9 @@ import { render } from '../../../../app'; import { store } from '../../../../store/admin'; import { SaveButton } from "../../../../components/Elements/Button"; import Admin, { pageNames } from '../../index'; -import { AdminForm } from '../../Components/AdminForm' -import StyleConfig from '../StyleConfig' +import { AdminForm } from '../../Components/AdminForm'; +import StyleConfig from '../StyleConfig'; +import RelatedTableFields from './RelatedTableFields'; import DjangoTemplateForm from "../../Components/AdminForm/DjangoTemplateForm"; import { resourceActions } from "../List"; import { dictDeepCopy } from "../../../../utils/main"; @@ -30,6 +31,7 @@ import { dictDeepCopy } from "../../../../utils/main"; import './style.scss'; let currentArcGis = null +let currentRelatedTable = null let init = false /** * Context Layer Form App @@ -87,21 +89,36 @@ export default function ContextLayerForm() { $('*[name="label_styles"]').val(JSON.stringify(newData['label_styles'])) $('*[name="data_fields"]').val(JSON.stringify(newData['data_fields'])) $('*[name="styles"]').val(JSON.stringify(newData['styles'])) + $('*[name="latitude"]').val(newData['latitude']) + $('*[name="longitude"]').val(newData['longitude']) + $('*[name="query"]').val(newData['query']) + $('*[name="datetime"]').val(newData['datetime']) } } const typeChange = (value) => { if (value === 'ARCGIS') { $('div[data-wrapper-name="arcgis_config"]').show() - } else { + $('div[data-wrapper-name="related_table"]').hide() + } else if (value === 'Related Table') { $('div[data-wrapper-name="arcgis_config"]').hide() + $('div[data-wrapper-name="token"]').hide() + $('div[data-wrapper-name="username"]').hide() + $('div[data-wrapper-name="password"]').hide() + $('div[data-wrapper-name="url"]').hide() + $('div[data-wrapper-name="related_table"]').show() + } + else { + $('div[data-wrapper-name="arcgis_config"]').hide() + $('div[data-wrapper-name="related_table"]').hide() + $('div[data-wrapper-name="url"]').show() } setData({ ...data, layer_type: value }) } const arcGisConfigChange = (value) => { currentArcGis = value - if (!value) { + if (!value && data.layer_type === 'ARCGIS') { $('div[data-wrapper-name="token"]').show() $('div[data-wrapper-name="username"]').show() $('div[data-wrapper-name="password"]').show() @@ -116,6 +133,16 @@ export default function ContextLayerForm() { }) } } + + const relatedTableConfigChange = (value) => { + currentRelatedTable = value + if (value) { + setData({ + ...data, + related_table: value + }) + } + } return ( @@ -181,6 +208,12 @@ export default function ContextLayerForm() { onChange={evt => { }} /> + {data.layer_type === 'Related Table' ? + : undefined + } ), 'Preview': ( @@ -192,8 +225,8 @@ export default function ContextLayerForm() { useOverrideLabel={false} /> ), - 'Fields':
, - 'Label':
, + 'Fields':
, + 'Label':
, }} /> diff --git a/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/Map.jsx b/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/Map.jsx index 15d94eade..e34ea2d8f 100644 --- a/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/Map.jsx +++ b/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/Map.jsx @@ -86,7 +86,7 @@ export default function MapConfig({ data, layerInput }) { // When layer input changed, remove from map useEffect(() => { if (map) { - const id = 'Context-Layer' + const id = data.id ? `context-layer-${data.id}` : 'context-layer' contextLayerRendering(id, data, layerInput, map) } }, [map, layerInput]); diff --git a/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/index.jsx b/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/index.jsx index 2e06c711e..b8334d701 100644 --- a/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/index.jsx +++ b/django_project/frontend/src/pages/Admin/ContextLayer/StyleConfig/index.jsx @@ -21,6 +21,48 @@ import { getLayer } from '../../../Dashboard/LeftPanel/ContextLayers/Layer' import './style.scss'; +const defaultVectorTyleStyle = [ + { + id: "country-line", + type: "line", + source: "source", + "source-layer": "countries", + filter: [ + "==", + "$type", + "Polygon" + ], + paint: { + "line-width": 1, + "line-color": "#AAAAAA" + } + }, + { + id: "country-fill", + type: "fill", + source: "source", + "source-layer": "countries", + filter: [ + "==", + "$type", + "Polygon" + ], + paint: { + "fill-opacity": 0 + } + } +]; + +const defaultPointStyle = [{ + id: 'pointLayer', + type: 'circle', + source: 'source', + paint: { + 'circle-color': '#ff7800', + 'circle-opacity': 0.6 + }, + 'filter': ['==', '$type', 'Point'] +}]; /** * Indicator Form App @@ -58,6 +100,16 @@ export default function StyleConfig( getLayer(data, setLayer, setLegend, setError, dispatch, setLayerDataClass); }, [data, tab]); + useEffect(() => { + if (!data.styles && data.layer_type === 'Related Table') { + setData({ + ...data, + styles: JSON.stringify(defaultPointStyle, null, 4), + override_style: true + }) + } + }, [data]); + useEffect(() => { if (layerDataClass) { setLayerData(layerDataClass) @@ -125,15 +177,18 @@ export default function StyleConfig( }
- +
: "" } { - data.layer_type === 'Vector Tile' ? <> + data.layer_type === 'Vector Tile' || data.layer_type === 'Related Table' ? <>
Layers
@@ -147,37 +202,10 @@ export default function StyleConfig(