From 004e740098cca9ea73095b05f3533ccb91adb0aa Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Tue, 24 Mar 2026 10:15:57 -0500 Subject: [PATCH 01/10] update docker yaml for no matching manifest for linux/arm64/v8 error --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index a743cce..1c541f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,7 @@ services: postgres: container_name: map-postgres image: postgis/postgis + platform: linux/amd64 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s From 86cfbc6d691fd232288e1c5bc73e8b9cde31f6be Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Tue, 24 Mar 2026 11:20:02 -0500 Subject: [PATCH 02/10] update py serializer to filter by year and shape data. Breakout resPerMap file to react components. update fetch with loading & error states. setup utils and skeletons for UI --- map/serializers.py | 32 ++--- map/static/js/RestaurantPermitMap.js | 126 ++++++++---------- map/static/js/components/Error.jsx | 5 + map/static/js/components/Loading.jsx | 5 + map/static/js/components/YearSelect.jsx | 30 +++++ map/static/js/components/YearlyPermitInfo.jsx | 13 ++ map/static/js/utils/getMapAreaColor.js | 22 +++ map/static/js/utils/getMaxNumberOfPermits.js | 18 +++ 8 files changed, 158 insertions(+), 93 deletions(-) create mode 100644 map/static/js/components/Error.jsx create mode 100644 map/static/js/components/Loading.jsx create mode 100644 map/static/js/components/YearSelect.jsx create mode 100644 map/static/js/components/YearlyPermitInfo.jsx create mode 100644 map/static/js/utils/getMapAreaColor.js create mode 100644 map/static/js/utils/getMaxNumberOfPermits.js diff --git a/map/serializers.py b/map/serializers.py index 03dd912..ee85857 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -6,29 +6,19 @@ class CommunityAreaSerializer(serializers.ModelSerializer): class Meta: model = CommunityArea - fields = ["name", "num_permits"] + fields = ["name", "num_permits", "area_id"] num_permits = serializers.SerializerMethodField() def get_num_permits(self, obj): - """ - TODO: supplement each community area object with the number - of permits issued in the given year. + year = self.context.get("year") + permits = RestaurantPermit.objects.filter(community_area_id=str(obj.area_id)) - e.g. The endpoint /map-data/?year=2017 should return something like: - [ - { - "ROGERS PARK": { - area_id: 17, - num_permits: 2 - }, - "BEVERLY": { - area_id: 72, - num_permits: 2 - }, - ... - } - ] - """ - - pass + if year: + permits = permits.filter(issue_date__year=year) + return permits.count() + + def to_representation(self, instance): + data = super().to_representation(instance) + name = data.pop("name") + return {name: data} diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 57f8ea0..717cbb7 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -1,97 +1,79 @@ -import React, { useEffect, useState } from "react" +import { useEffect, useState } from "react"; +import { MapContainer, TileLayer, GeoJSON } from "react-leaflet"; -import { MapContainer, TileLayer, GeoJSON } from "react-leaflet" +import YearSelect from "./components/YearSelect"; +import YearlyPermitInfo from "./components/YearlyPermitInfo"; +import getMaxNumberOfPermits from "./utils/getMaxNumberOfPermits"; -import "leaflet/dist/leaflet.css" +import "leaflet/dist/leaflet.css"; -import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson" - -function YearSelect({ setFilterVal }) { - // Filter by the permit issue year for each restaurant - const startYear = 2026 - const years = [...Array(11).keys()].map((increment) => { - return startYear - increment - }) - const options = years.map((year) => { - return ( - - ) - }) - - return ( - <> - - - - ) -} +import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson"; export default function RestaurantPermitMap() { - const communityAreaColors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"] - - const [currentYearData, setCurrentYearData] = useState([]) - const [year, setYear] = useState(2026) + const [isLoadingMap, setIsLoadingMap] = useState(true); + const [error, setIsError] = useState(false); + const [currentYearData, setCurrentYearData] = useState([]); + const [year, setYear] = useState(2026); - const yearlyDataEndpoint = `/map-data/?year=${year}` + const yearlyDataEndpoint = `/map-data/?year=${year}`; useEffect(() => { - fetch() - .then((res) => res.json()) - .then((data) => { - /** - * TODO: Fetch the data needed to supply to map with data - */ - }) - }, [yearlyDataEndpoint]) + const fetchMapData = async () => { + setIsError(false); + setIsLoadingMap(true); + try { + const response = await fetch(yearlyDataEndpoint); + if (!response.ok) { + throw new Error("Network response was NOT ok"); + } + const data = await response.json(); + setCurrentYearData(data); + } catch { + setIsError(true); + } finally { + setIsLoadingMap(false); + } + }; + fetchMapData(); + }, [yearlyDataEndpoint]); - function getColor(percentageOfPermits) { - /** - * TODO: Use this function in setAreaInteraction to set a community - * area's color using the communityAreaColors constant above - */ + if (isLoadingMap) { + //TODO loading component + return
Loading..
; + } + if (error) { + //TODO Error component + return ( +
+ +

Error

{" "} +
+ ); } + const maxNumPermits = getMaxNumberOfPermits(currentYearData); function setAreaInteraction(feature, layer) { /** * TODO: Use the methods below to: - * 1) Shade each community area according to what percentage of + * 1) Shade each community area according to what percentage of * permits were issued there in the selected year - * 2) On hover, display a popup with the community area's raw + * 2) On hover, display a popup with the community area's raw * permit count for the year */ - layer.setStyle() + layer.setStyle(); layer.on("", () => { - layer.bindPopup("") - layer.openPopup() - }) + layer.bindPopup(""); + layer.openPopup(); + }); } return ( <> - -

- Restaurant permits issued this year: {/* TODO: display this value */} -

-

- Maximum number of restaurant permits in a single area: - {/* TODO: display this value */} -

- + + {/* TODO create func to calculate permits*/} + + - ) + ); } diff --git a/map/static/js/components/Error.jsx b/map/static/js/components/Error.jsx new file mode 100644 index 0000000..d476790 --- /dev/null +++ b/map/static/js/components/Error.jsx @@ -0,0 +1,5 @@ +const Error = () => { + return
TODO Error
; +}; + +export default Error; diff --git a/map/static/js/components/Loading.jsx b/map/static/js/components/Loading.jsx new file mode 100644 index 0000000..0e65faf --- /dev/null +++ b/map/static/js/components/Loading.jsx @@ -0,0 +1,5 @@ +const Loading = () => { + return
TODO
; +}; + +export default Loading; diff --git a/map/static/js/components/YearSelect.jsx b/map/static/js/components/YearSelect.jsx new file mode 100644 index 0000000..d28f025 --- /dev/null +++ b/map/static/js/components/YearSelect.jsx @@ -0,0 +1,30 @@ +const YearSelect = ({ setYear }) => { + const startYear = 2026; + const years = [...Array(11).keys()].map((increment) => { + return startYear - increment; + }); + const options = years.map((year) => { + return ( + + ); + }); + + return ( + <> + + + + ); +}; + +export default YearSelect; diff --git a/map/static/js/components/YearlyPermitInfo.jsx b/map/static/js/components/YearlyPermitInfo.jsx new file mode 100644 index 0000000..3224ee3 --- /dev/null +++ b/map/static/js/components/YearlyPermitInfo.jsx @@ -0,0 +1,13 @@ +const YearlyPermitInfo = ({ totalPermits, maxPermits }) => { +
+

+ Restaurant permits issued this year: ${totalPermits || "N/A"} +

+

+ Maximum number of restaurant permits in a single area: + {maxPermits || "N/A"} +

+
; +}; + +export default YearlyPermitInfo; diff --git a/map/static/js/utils/getMapAreaColor.js b/map/static/js/utils/getMapAreaColor.js new file mode 100644 index 0000000..de013a3 --- /dev/null +++ b/map/static/js/utils/getMapAreaColor.js @@ -0,0 +1,22 @@ +const getMapAreaColor = (percentageOfPermits) => { + if (typeof percentageOfPermits !== "number") { + //TODO verify data type. Will it always be a num or are we passing in string + return "#f4722d"; + } + const communityAreaColors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"]; + let fillColor; + + if (percentageOfPermits < 25) { + fillColor = communityAreaColors[0]; + } else if (percentageOfPermits >= 25 && percentageOfPermits < 50) { + fillColor = communityAreaColors[1]; + } else if (percentageOfPermits >= 50 && percentageOfPermits < 75) { + fillColor = communityAreaColors[2]; + } else { + fillColor = communityAreaColors[3]; + } + + return fillColor; +}; + +export default getMapAreaColor; diff --git a/map/static/js/utils/getMaxNumberOfPermits.js b/map/static/js/utils/getMaxNumberOfPermits.js new file mode 100644 index 0000000..e5bcfe4 --- /dev/null +++ b/map/static/js/utils/getMaxNumberOfPermits.js @@ -0,0 +1,18 @@ +const getMaxNumberOfPermits = (currentYearMapData) => { + if (typeof currentYearMapData !== "object") { + return null; + } + let maxNumPermits = { max: 0, name: "" }; + + for (let data of currentYearMapData) { + const [key, value] = Object.entries(data)[0]; + console.log("this is the community", key); + if (value.num_permits > maxNumPermits.max) { + maxNumPermits = { max: value.num_permits, name: key }; + } + } + + return maxNumPermits; +}; + +export default getMaxNumberOfPermits; From a217a87fece7920d4c5c77394e674434d7248547 Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Tue, 24 Mar 2026 11:37:36 -0500 Subject: [PATCH 03/10] update webpack config to rm Uncaught ReferenceError: React is not defined --- map/static/js/RestaurantPermitMap.js | 6 ++--- map/static/js/components/YearlyPermitInfo.jsx | 20 +++++++------- webpack.config.js | 26 ++++++++++++------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 717cbb7..3b71915 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -52,7 +52,7 @@ export default function RestaurantPermitMap() { ); } const maxNumPermits = getMaxNumberOfPermits(currentYearData); - + // console.log(" this is the maxNum permits", maxNumPermits); function setAreaInteraction(feature, layer) { /** * TODO: Use the methods below to: @@ -72,7 +72,7 @@ export default function RestaurantPermitMap() { <> {/* TODO create func to calculate permits*/} - + ) : null} diff --git a/map/static/js/components/YearlyPermitInfo.jsx b/map/static/js/components/YearlyPermitInfo.jsx index 3224ee3..df7bad0 100644 --- a/map/static/js/components/YearlyPermitInfo.jsx +++ b/map/static/js/components/YearlyPermitInfo.jsx @@ -1,13 +1,15 @@ const YearlyPermitInfo = ({ totalPermits, maxPermits }) => { -
-

- Restaurant permits issued this year: ${totalPermits || "N/A"} -

-

- Maximum number of restaurant permits in a single area: - {maxPermits || "N/A"} -

-
; + return ( +
+

+ Restaurant permits issued this year: {totalPermits || "N/A"} +

+

+ Maximum number of restaurant permits in a single area: + {maxPermits || "N/A"} +

+
+ ); }; export default YearlyPermitInfo; diff --git a/webpack.config.js b/webpack.config.js index cb6c7bb..7c3f288 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,6 @@ -const path = require("path") -const webpack = require("webpack") // eslint-disable-line no-unused-vars -const BundleTracker = require("webpack-bundle-tracker") +const path = require("path"); +const webpack = require("webpack"); // eslint-disable-line no-unused-vars +const BundleTracker = require("webpack-bundle-tracker"); const config = { context: __dirname, @@ -46,7 +46,15 @@ const config = { exclude: /node_modules/, loader: "babel-loader", options: { - presets: ["@babel/preset-env", "@babel/preset-react"], + presets: [ + "@babel/preset-env", + [ + "@babel/preset-react", + { + runtime: "automatic", + }, + ], + ], }, }, { @@ -99,7 +107,7 @@ const config = { }, ], }, -} +}; module.exports = (env, argv) => { /* @@ -109,12 +117,12 @@ module.exports = (env, argv) => { * /app/static/bundles for bundles. */ if (argv.mode === "development") { - config.output.publicPath = "http://localhost:3000/static/bundles/" + config.output.publicPath = "http://localhost:3000/static/bundles/"; } if (argv.mode === "production") { - config.output.publicPath = "/static/bundles/" + config.output.publicPath = "/static/bundles/"; } - return config -} + return config; +}; From c15daf7959f4c138b990ccb6d82087850ba34850 Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Tue, 24 Mar 2026 18:11:52 -0500 Subject: [PATCH 04/10] added util files for calculating permits & color, leverage React leaflet Popup. update React components. UI WIP --- map/static/js/RestaurantPermitMap.js | 89 +++++++++++++++---- map/static/js/components/LeafletPopUp.jsx | 16 ++++ map/static/js/components/YearSelect.jsx | 3 +- .../js/utils/generateAreaIdPermitObject.js | 11 +++ map/static/js/utils/getMapAreaColor.js | 7 +- map/static/js/utils/getMaxNumberOfPermits.js | 2 +- map/static/js/utils/getTotalPermitsPerYear.js | 8 ++ 7 files changed, 113 insertions(+), 23 deletions(-) create mode 100644 map/static/js/components/LeafletPopUp.jsx create mode 100644 map/static/js/utils/generateAreaIdPermitObject.js create mode 100644 map/static/js/utils/getTotalPermitsPerYear.js diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 3b71915..d091480 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -1,9 +1,16 @@ import { useEffect, useState } from "react"; -import { MapContainer, TileLayer, GeoJSON } from "react-leaflet"; +import { MapContainer, TileLayer, GeoJSON, Popup } from "react-leaflet"; import YearSelect from "./components/YearSelect"; import YearlyPermitInfo from "./components/YearlyPermitInfo"; +import Loading from "./components/Loading"; +import Error from "./components/Error"; +import LeafletPopUp from "./components/LeafletPopUp"; + import getMaxNumberOfPermits from "./utils/getMaxNumberOfPermits"; +import getMapAreaColor from "./utils/getMapAreaColor"; +import generateAreaIdPermitObject from "./utils/generateAreaIdPermitObject"; +import getTotalPermitsPerYear from "./utils/getTotalPermitsPerYear"; import "leaflet/dist/leaflet.css"; @@ -12,7 +19,10 @@ import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson"; export default function RestaurantPermitMap() { const [isLoadingMap, setIsLoadingMap] = useState(true); const [error, setIsError] = useState(false); + const [activeArea, setActiveArea] = useState(); + const [totalPermits, setTotalPermits] = useState(0); const [currentYearData, setCurrentYearData] = useState([]); + const [areaIdMap, setAreaIdMap] = useState({}); const [year, setYear] = useState(2026); const yearlyDataEndpoint = `/map-data/?year=${year}`; @@ -23,12 +33,19 @@ export default function RestaurantPermitMap() { setIsLoadingMap(true); try { const response = await fetch(yearlyDataEndpoint); + if (!response.ok) { throw new Error("Network response was NOT ok"); } + const data = await response.json(); + const areaIdObject = generateAreaIdPermitObject(data); + const totalPermitsForTheYear = getTotalPermitsPerYear(data); setCurrentYearData(data); - } catch { + setAreaIdMap(areaIdObject); + setTotalPermits(totalPermitsForTheYear); + } catch (err) { + console.error("Error details:", err); setIsError(true); } finally { setIsLoadingMap(false); @@ -40,19 +57,19 @@ export default function RestaurantPermitMap() { if (isLoadingMap) { //TODO loading component - return
Loading..
; + return ; } if (error) { - //TODO Error component + //TODO Error component. possibly move down component return (
-

Error

{" "} +
); } const maxNumPermits = getMaxNumberOfPermits(currentYearData); - // console.log(" this is the maxNum permits", maxNumPermits); + function setAreaInteraction(feature, layer) { /** * TODO: Use the methods below to: @@ -61,29 +78,65 @@ export default function RestaurantPermitMap() { * 2) On hover, display a popup with the community area's raw * permit count for the year */ - layer.setStyle(); - layer.on("", () => { - layer.bindPopup(""); - layer.openPopup(); + + const communityName = feature.properties.community; + const area_id = feature.properties.area_num_1; + const permitPercentage = (areaIdMap[area_id] / maxNumPermits.max) * 100; + + layer.setStyle({ + fillColor: getMapAreaColor(permitPercentage), + weight: 1, + //TODO update color + color: "red", + fillOpacity: 0.8, + }); + layer.on({ + mouseover: (e) => { + setActiveArea({ + position: e.latlng, + name: communityName, + id: area_id, + }); + }, + //TODO verify if this is wanted behavior + // mouseout: () => setActiveArea(null), }); } return ( <> - - {/* TODO create func to calculate permits*/} - + + + {currentYearData.length > 0 ? ( - + <> + + {activeArea && ( + setActiveArea(null)} + > + + + )} + ) : null} diff --git a/map/static/js/components/LeafletPopUp.jsx b/map/static/js/components/LeafletPopUp.jsx new file mode 100644 index 0000000..61d8238 --- /dev/null +++ b/map/static/js/components/LeafletPopUp.jsx @@ -0,0 +1,16 @@ +const LeafletPopUp = ({ communityName, year, permits }) => { + //TODO verify if button is needed + const handleClick = (e) => { + e.preventDefault(); + }; + return ( +
+

{communityName}

+

{`Permits for ${year}: ${permits}`}

+ {/* TODO Verify */} + +
+ ); +}; + +export default LeafletPopUp; diff --git a/map/static/js/components/YearSelect.jsx b/map/static/js/components/YearSelect.jsx index d28f025..90466e2 100644 --- a/map/static/js/components/YearSelect.jsx +++ b/map/static/js/components/YearSelect.jsx @@ -1,4 +1,4 @@ -const YearSelect = ({ setYear }) => { +const YearSelect = ({ year, setYear }) => { const startYear = 2026; const years = [...Array(11).keys()].map((increment) => { return startYear - increment; @@ -20,6 +20,7 @@ const YearSelect = ({ setYear }) => { id="yearSelect" className="form-select form-select-lg mb-3" onChange={(e) => setYear(e.target.value)} + value={year} > {options} diff --git a/map/static/js/utils/generateAreaIdPermitObject.js b/map/static/js/utils/generateAreaIdPermitObject.js new file mode 100644 index 0000000..bcae91d --- /dev/null +++ b/map/static/js/utils/generateAreaIdPermitObject.js @@ -0,0 +1,11 @@ +const generateAreaIdPermitObject = (mapData) => { + const map = {}; + for (let res of mapData) { + const [_, value] = Object.entries(res)[0]; + map[value.area_id] = value.num_permits; + } + + return map; +}; + +export default generateAreaIdPermitObject; diff --git a/map/static/js/utils/getMapAreaColor.js b/map/static/js/utils/getMapAreaColor.js index de013a3..614fd97 100644 --- a/map/static/js/utils/getMapAreaColor.js +++ b/map/static/js/utils/getMapAreaColor.js @@ -1,9 +1,10 @@ const getMapAreaColor = (percentageOfPermits) => { + const communityAreaColors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"]; + if (typeof percentageOfPermits !== "number") { - //TODO verify data type. Will it always be a num or are we passing in string - return "#f4722d"; + return "#d3500a"; } - const communityAreaColors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"]; + let fillColor; if (percentageOfPermits < 25) { diff --git a/map/static/js/utils/getMaxNumberOfPermits.js b/map/static/js/utils/getMaxNumberOfPermits.js index e5bcfe4..08b091a 100644 --- a/map/static/js/utils/getMaxNumberOfPermits.js +++ b/map/static/js/utils/getMaxNumberOfPermits.js @@ -6,7 +6,7 @@ const getMaxNumberOfPermits = (currentYearMapData) => { for (let data of currentYearMapData) { const [key, value] = Object.entries(data)[0]; - console.log("this is the community", key); + if (value.num_permits > maxNumPermits.max) { maxNumPermits = { max: value.num_permits, name: key }; } diff --git a/map/static/js/utils/getTotalPermitsPerYear.js b/map/static/js/utils/getTotalPermitsPerYear.js new file mode 100644 index 0000000..e71265a --- /dev/null +++ b/map/static/js/utils/getTotalPermitsPerYear.js @@ -0,0 +1,8 @@ +const getTotalPermitsPerYear = (mapData) => { + return mapData.reduce((sum, areaObj) => { + const area = Object.values(areaObj)[0]; + return sum + area.num_permits; + }, 0); +}; + +export default getTotalPermitsPerYear; From cb4221ac6789f317aab24265a2bc753651d32cf9 Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Wed, 25 Mar 2026 13:39:25 -0500 Subject: [PATCH 05/10] add test assertions on test_views --- tests/test_views.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_views.py b/tests/test_views.py index 24cc64e..ea294a4 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -32,10 +32,14 @@ def test_map_data_view(): community_area_id=area2.area_id, issue_date=date(2021, 6, 22) ) - # Query the map data endpoint client = APIClient() response = client.get(reverse("map_data", query={"year": 2021})) - - # TODO: Complete the test by asserting that the /map-data/ endpoint - # returns the correct number of permits for Beverly and Lincoln - # Park in 2021 + assert response.status_code == 200 + + data = response.json() + results = {list(item.keys())[0]: list(item.values())[0] for item in data} + + assert results["Beverly"]["num_permits"] == 2 + assert results["Beverly"]["area_id"] == 1 + assert results["Lincoln Park"]["num_permits"] == 3 + assert results["Lincoln Park"]["area_id"] == 2 From 137acbe863db0d4b6155ee940ba46f32182f3697 Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Wed, 25 Mar 2026 15:34:14 -0500 Subject: [PATCH 06/10] rename isLoadingMap. update error screen & loading. Add inline styles for error & loading page. --- map/static/images/chicagoStars.svg | 3 +++ map/static/js/RestaurantPermitMap.js | 10 ++++------ map/static/js/components/Error.jsx | 29 +++++++++++++++++++++++++++- map/static/js/components/Loading.jsx | 20 ++++++++++++++++++- 4 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 map/static/images/chicagoStars.svg diff --git a/map/static/images/chicagoStars.svg b/map/static/images/chicagoStars.svg new file mode 100644 index 0000000..68303e5 --- /dev/null +++ b/map/static/images/chicagoStars.svg @@ -0,0 +1,3 @@ + + + diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index d091480..48974e8 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -17,7 +17,7 @@ import "leaflet/dist/leaflet.css"; import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson"; export default function RestaurantPermitMap() { - const [isLoadingMap, setIsLoadingMap] = useState(true); + const [isLoading, setIsLoading] = useState(true); const [error, setIsError] = useState(false); const [activeArea, setActiveArea] = useState(); const [totalPermits, setTotalPermits] = useState(0); @@ -30,7 +30,7 @@ export default function RestaurantPermitMap() { useEffect(() => { const fetchMapData = async () => { setIsError(false); - setIsLoadingMap(true); + setIsLoading(true); try { const response = await fetch(yearlyDataEndpoint); @@ -48,19 +48,17 @@ export default function RestaurantPermitMap() { console.error("Error details:", err); setIsError(true); } finally { - setIsLoadingMap(false); + setIsLoading(false); } }; fetchMapData(); }, [yearlyDataEndpoint]); - if (isLoadingMap) { - //TODO loading component + if (isLoading) { return ; } if (error) { - //TODO Error component. possibly move down component return (
diff --git a/map/static/js/components/Error.jsx b/map/static/js/components/Error.jsx index d476790..f31252e 100644 --- a/map/static/js/components/Error.jsx +++ b/map/static/js/components/Error.jsx @@ -1,5 +1,32 @@ const Error = () => { - return
TODO Error
; + const handleRefresh = () => { + window.location.reload(); + }; + + return ( +
+

Oh no, something went wrong

+ +
+ ); }; export default Error; diff --git a/map/static/js/components/Loading.jsx b/map/static/js/components/Loading.jsx index 0e65faf..4760668 100644 --- a/map/static/js/components/Loading.jsx +++ b/map/static/js/components/Loading.jsx @@ -1,5 +1,23 @@ const Loading = () => { - return
TODO
; + return ( +
+

Loading Data...

+ Description of your image +
+ ); }; export default Loading; From b0f64acf95f9ecd005da15158d21cb6504820adc Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Wed, 25 Mar 2026 20:16:08 -0500 Subject: [PATCH 07/10] clean up todo. update error w chicago blue, update useEffect,create mapLegend for more context on data. add community name to max num of year --- map/static/js/RestaurantPermitMap.js | 43 ++++++++++--------- map/static/js/components/Error.jsx | 2 +- map/static/js/components/LeafletPopUp.jsx | 8 +--- map/static/js/components/MapLegend.jsx | 42 ++++++++++++++++++ map/static/js/components/YearlyPermitInfo.jsx | 5 ++- 5 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 map/static/js/components/MapLegend.jsx diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 48974e8..37e3991 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -6,6 +6,7 @@ import YearlyPermitInfo from "./components/YearlyPermitInfo"; import Loading from "./components/Loading"; import Error from "./components/Error"; import LeafletPopUp from "./components/LeafletPopUp"; +import Legend from "./components/MapLegend"; import getMaxNumberOfPermits from "./utils/getMaxNumberOfPermits"; import getMapAreaColor from "./utils/getMapAreaColor"; @@ -53,51 +54,50 @@ export default function RestaurantPermitMap() { }; fetchMapData(); - }, [yearlyDataEndpoint]); + }, [year]); if (isLoading) { return ; } if (error) { - return ( -
- - -
- ); + return ; } + const maxNumPermits = getMaxNumberOfPermits(currentYearData); function setAreaInteraction(feature, layer) { - /** - * TODO: Use the methods below to: - * 1) Shade each community area according to what percentage of - * permits were issued there in the selected year - * 2) On hover, display a popup with the community area's raw - * permit count for the year - */ + const communityName = feature.properties.community; const area_id = feature.properties.area_num_1; const permitPercentage = (areaIdMap[area_id] / maxNumPermits.max) * 100; - - layer.setStyle({ + const defaultStyle = { fillColor: getMapAreaColor(permitPercentage), weight: 1, - //TODO update color color: "red", fillOpacity: 0.8, - }); + }; + + const hoverStyle = { + ...defaultStyle, + color: "green", + }; + + layer.setStyle(defaultStyle); layer.on({ mouseover: (e) => { + layer.setStyle(hoverStyle); setActiveArea({ position: e.latlng, name: communityName, id: area_id, + color: "green", }); }, - //TODO verify if this is wanted behavior - // mouseout: () => setActiveArea(null), + mouseout: () => { + layer.setStyle(defaultStyle); + setActiveArea(null); + }, }); } @@ -107,7 +107,7 @@ export default function RestaurantPermitMap() { )} + ) : null} diff --git a/map/static/js/components/Error.jsx b/map/static/js/components/Error.jsx index f31252e..67c9536 100644 --- a/map/static/js/components/Error.jsx +++ b/map/static/js/components/Error.jsx @@ -19,7 +19,7 @@ const Error = () => { style={{ width: "fit-content", borderRadius: "8px", - background: "blue", + background: "#41B6E6", color: "white", }} > diff --git a/map/static/js/components/LeafletPopUp.jsx b/map/static/js/components/LeafletPopUp.jsx index 61d8238..fdda939 100644 --- a/map/static/js/components/LeafletPopUp.jsx +++ b/map/static/js/components/LeafletPopUp.jsx @@ -1,14 +1,8 @@ const LeafletPopUp = ({ communityName, year, permits }) => { - //TODO verify if button is needed - const handleClick = (e) => { - e.preventDefault(); - }; return (

{communityName}

-

{`Permits for ${year}: ${permits}`}

- {/* TODO Verify */} - +

{`Permits this year: ${permits}`}

); }; diff --git a/map/static/js/components/MapLegend.jsx b/map/static/js/components/MapLegend.jsx new file mode 100644 index 0000000..0a1a43c --- /dev/null +++ b/map/static/js/components/MapLegend.jsx @@ -0,0 +1,42 @@ +import { useEffect } from "react"; +import { useMap } from "react-leaflet"; +import L from "leaflet"; + +const MapLegend = () => { + const map = useMap(); + + useEffect(() => { + const legend = L.control({ position: "topright" }); + + legend.onAdd = () => { + const div = L.DomUtil.create("div", "info legend"); + const grades = [0, 25, 50, 75]; + const colors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"]; + + div.style.backgroundColor = "white"; + div.style.padding = "10px"; + div.style.lineHeight = "18px"; + div.style.color = "#555"; + + div.innerHTML = "

Yearly Permits %

"; + + for (let i = 0; i < grades.length; i++) { + div.innerHTML += ` +
+
+ ${grades[i]}${grades[i + 1] ? "–" + grades[i + 1] : "+"}% +
+ `; + } + + return div; + }; + + legend.addTo(map); + return () => legend.remove(); + }, [map]); + + return null; +}; + +export default MapLegend; diff --git a/map/static/js/components/YearlyPermitInfo.jsx b/map/static/js/components/YearlyPermitInfo.jsx index df7bad0..66a7a25 100644 --- a/map/static/js/components/YearlyPermitInfo.jsx +++ b/map/static/js/components/YearlyPermitInfo.jsx @@ -1,12 +1,13 @@ const YearlyPermitInfo = ({ totalPermits, maxPermits }) => { + const { max, name } = maxPermits; return (

Restaurant permits issued this year: {totalPermits || "N/A"}

- Maximum number of restaurant permits in a single area: - {maxPermits || "N/A"} + Maximum number of restaurant permits in a single area:{" "} + {`${max} (${name})` || "N/A"}

); From 7117724336995bf58238281688e37129c6b3424f Mon Sep 17 00:00:00 2001 From: Salgado3 Date: Wed, 25 Mar 2026 21:20:31 -0500 Subject: [PATCH 08/10] refactor legend to react, mv Loading inside mapContainer to avoid unmounting, add svg to error page. update bootstrap class on permit info headlines --- map/static/images/chicagoStarsError.svg | 9 +++ map/static/js/RestaurantPermitMap.js | 11 ++-- map/static/js/components/Error.jsx | 6 ++ map/static/js/components/LeafletPopUp.jsx | 2 +- map/static/js/components/Loading.jsx | 11 +++- map/static/js/components/MapLegend.jsx | 64 +++++++++++++------ map/static/js/components/YearSelect.jsx | 4 +- map/static/js/components/YearlyPermitInfo.jsx | 4 +- 8 files changed, 76 insertions(+), 35 deletions(-) create mode 100644 map/static/images/chicagoStarsError.svg diff --git a/map/static/images/chicagoStarsError.svg b/map/static/images/chicagoStarsError.svg new file mode 100644 index 0000000..dd61240 --- /dev/null +++ b/map/static/images/chicagoStarsError.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 37e3991..18384b0 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -56,9 +56,7 @@ export default function RestaurantPermitMap() { fetchMapData(); }, [year]); - if (isLoading) { - return ; - } + if (error) { return ; } @@ -66,8 +64,6 @@ export default function RestaurantPermitMap() { const maxNumPermits = getMaxNumberOfPermits(currentYearData); function setAreaInteraction(feature, layer) { - - const communityName = feature.properties.community; const area_id = feature.properties.area_num_1; const permitPercentage = (areaIdMap[area_id] / maxNumPermits.max) * 100; @@ -81,6 +77,7 @@ export default function RestaurantPermitMap() { const hoverStyle = { ...defaultStyle, color: "green", + fillOpacity: 1, }; layer.setStyle(defaultStyle); @@ -96,13 +93,13 @@ export default function RestaurantPermitMap() { }, mouseout: () => { layer.setStyle(defaultStyle); - setActiveArea(null); }, }); } return ( <> +

Chicago Restaurant Permits

+ {isLoading && } + {currentYearData.length > 0 ? ( <> { }} >

Oh no, something went wrong

+