diff --git a/crawler/api/app.py b/crawler/api/app.py
index da56826..b702ce8 100644
--- a/crawler/api/app.py
+++ b/crawler/api/app.py
@@ -31,5 +31,5 @@ async def get_listing(user: Annotated[User, Depends(get_current_user)]):
@app.get("/api/listing_geojson")
async def get_listing_geojson(user: Annotated[User, Depends(get_current_user)]):
repository = ListingRepository(engine)
- geojson_data = await export_immoweb(repository, limit=500)
+ geojson_data = await export_immoweb(repository, limit=None)
return geojson_data
diff --git a/crawler/frontend/index.html b/crawler/frontend/index.html
index eb84ccd..97b6bf6 100644
--- a/crawler/frontend/index.html
+++ b/crawler/frontend/index.html
@@ -5,13 +5,12 @@
-
Vite + React + TS
-
+ Wrongmove
-
+
diff --git a/crawler/frontend/package-lock.json b/crawler/frontend/package-lock.json
index 0402683..0226995 100644
--- a/crawler/frontend/package-lock.json
+++ b/crawler/frontend/package-lock.json
@@ -10,7 +10,12 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-scroll-area": "^1.2.9",
+ "@radix-ui/react-select": "^2.2.5",
+ "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tooltip": "^1.2.7",
+ "@tabler/icons-react": "^3.34.0",
"@tailwindcss/vite": "^4.1.10",
"@types/crossfilter": "^0.0.38",
"@types/d3": "^7.4.3",
@@ -626,6 +631,44 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz",
+ "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz",
+ "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.1",
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz",
+ "integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
+ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
+ "license": "MIT"
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -840,12 +883,67 @@
"node": ">= 8"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
"license": "MIT"
},
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -912,6 +1010,21 @@
}
}
},
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
@@ -1020,6 +1133,38 @@
}
}
},
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
+ "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-portal": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
@@ -1091,6 +1236,103 @@
}
}
},
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz",
+ "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz",
+ "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.10",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.7",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
+ "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
@@ -1109,6 +1351,40 @@
}
}
},
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz",
+ "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.10",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.7",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@@ -1194,6 +1470,86 @@
}
}
},
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.11",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
@@ -1687,6 +2043,32 @@
"@swc/counter": "^0.1.3"
}
},
+ "node_modules/@tabler/icons": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.34.0.tgz",
+ "integrity": "sha512-jtVqv0JC1WU2TTEBN32D9+R6mc1iEBuPwLnBsWaR02SIEciu9aq5806AWkCHuObhQ4ERhhXErLEK7Fs+tEZxiA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ }
+ },
+ "node_modules/@tabler/icons-react": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.34.0.tgz",
+ "integrity": "sha512-OpEIR2iZsIXECtAIMbn1zfKfQ3zKJjXyIZlkgOGUL9UkMCFycEiF2Y8AVfEQsyre/3FnBdlWJvGr0NU47n2TbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tabler/icons": "3.34.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ },
+ "peerDependencies": {
+ "react": ">= 16"
+ }
+ },
"node_modules/@tailwindcss/node": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
diff --git a/crawler/frontend/package.json b/crawler/frontend/package.json
index 759325f..00f0f2e 100644
--- a/crawler/frontend/package.json
+++ b/crawler/frontend/package.json
@@ -12,7 +12,12 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-scroll-area": "^1.2.9",
+ "@radix-ui/react-select": "^2.2.5",
+ "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tooltip": "^1.2.7",
+ "@tabler/icons-react": "^3.34.0",
"@tailwindcss/vite": "^4.1.10",
"@types/crossfilter": "^0.0.38",
"@types/d3": "^7.4.3",
diff --git a/crawler/frontend/src/App.css b/crawler/frontend/src/App.css
index 83854f7..5efa2df 100644
--- a/crawler/frontend/src/App.css
+++ b/crawler/frontend/src/App.css
@@ -1,5 +1,4 @@
#root {
- max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
@@ -43,3 +42,9 @@
.read-the-docs {
color: #888;
}
+
+html,
+body {
+ overflow: hidden;
+ height: 100%;
+}
diff --git a/crawler/frontend/src/App.tsx b/crawler/frontend/src/App.tsx
index 3c5ce2a..59bff44 100644
--- a/crawler/frontend/src/App.tsx
+++ b/crawler/frontend/src/App.tsx
@@ -1,10 +1,15 @@
import type { User } from 'oidc-client-ts';
import { useEffect, useState } from 'react';
import './App.css';
+import { AppSidebar } from './AppSidebar';
import { getUser, handleCallback, logout } from './auth/authService';
import LoginModal from './components/LoginModal';
import { Map } from './components/Map';
import { Parameters } from './components/Parameters';
+import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from './components/ui/breadcrumb';
+import { Button } from './components/ui/button';
+import { Separator } from './components/ui/separator';
+import { SidebarInset, SidebarProvider, SidebarTrigger } from './components/ui/sidebar';
function App() {
const [listingData, setListingData] = useState({});
@@ -23,46 +28,91 @@ function App() {
getUser().then(setUser);
}, []);
+ const [isLoading, setIsLoading] = useState(false)
+ const [isParametersModalOpen, setIsParametersModalOpen] = useState(true)
+ const [error, setError] = useState('')
+ const fetchData = async () => {
+ setIsLoading(true);
+
+ try {
+ const accessToken = user?.access_token;
+ const response = await fetch('/api/listing_geojson',
+ {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${accessToken}`, // Pass the token
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+ if (!response.ok) throw new Error('Error: ' + response.json());
+ const data: Response = await response.json();
+ return data;
+ } catch (err) {
+ setError('Failed to fetch data: ' + err);
+ alert(error)
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ const onSubmit = async () => {
+ // Fetch listing data
+ const data = await fetchData();
+ if (data) {
+ setListingData(data);
+ }
+ setIsLoading(false);
+ setIsParametersModalOpen(false)
+
+ }
+
if (!user) {
return
}
-
+ const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
return (
<>
-
-
Welcome, {user.profile.name}!
-
-
- {/*
- Vite + React
-
-
-
- Edit src/App.tsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
*/}
+
+
+
+
+
+
+
+
+
+
+ Building Your Application
+
+
+
+
+ Data Fetching
+
+
+
+
+
+
+
Welcome, {user.profile.name}!
+
+
+
+ {Object.keys(listingData).length > 0 &&
+
+
+
+ }
+
- {/* */}
-
- {Object.keys(listingData).length > 0 &&
-
-
-
- }
+
+
+
>
)
}
diff --git a/crawler/frontend/src/AppSidebar.tsx b/crawler/frontend/src/AppSidebar.tsx
new file mode 100644
index 0000000..51f6861
--- /dev/null
+++ b/crawler/frontend/src/AppSidebar.tsx
@@ -0,0 +1,174 @@
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarRail,
+} from "@/components/ui/sidebar"
+import * as React from "react"
+
+// This is sample data.
+const data = {
+ navMain: [
+ {
+ title: "Getting Started",
+ url: "#",
+ items: [
+ {
+ title: "Installation",
+ url: "#",
+ },
+ {
+ title: "Project Structure",
+ url: "#",
+ },
+ ],
+ },
+ {
+ title: "Building Your Application",
+ url: "#",
+ items: [
+ {
+ title: "Routing",
+ url: "#",
+ },
+ {
+ title: "Data Fetching",
+ url: "#",
+ isActive: true,
+ },
+ {
+ title: "Rendering",
+ url: "#",
+ },
+ {
+ title: "Caching",
+ url: "#",
+ },
+ {
+ title: "Styling",
+ url: "#",
+ },
+ {
+ title: "Optimizing",
+ url: "#",
+ },
+ {
+ title: "Configuring",
+ url: "#",
+ },
+ {
+ title: "Testing",
+ url: "#",
+ },
+ {
+ title: "Authentication",
+ url: "#",
+ },
+ {
+ title: "Deploying",
+ url: "#",
+ },
+ {
+ title: "Upgrading",
+ url: "#",
+ },
+ {
+ title: "Examples",
+ url: "#",
+ },
+ ],
+ },
+ {
+ title: "API Reference",
+ url: "#",
+ items: [
+ {
+ title: "Components",
+ url: "#",
+ },
+ {
+ title: "File Conventions",
+ url: "#",
+ },
+ {
+ title: "Functions",
+ url: "#",
+ },
+ {
+ title: "next.config.js Options",
+ url: "#",
+ },
+ {
+ title: "CLI",
+ url: "#",
+ },
+ {
+ title: "Edge Runtime",
+ url: "#",
+ },
+ ],
+ },
+ {
+ title: "Architecture",
+ url: "#",
+ items: [
+ {
+ title: "Accessibility",
+ url: "#",
+ },
+ {
+ title: "Fast Refresh",
+ url: "#",
+ },
+ {
+ title: "Next.js Compiler",
+ url: "#",
+ },
+ {
+ title: "Supported Browsers",
+ url: "#",
+ },
+ {
+ title: "Turbopack",
+ url: "#",
+ },
+ ],
+ },
+ ],
+}
+
+export function AppSidebar({ ...props }: React.ComponentProps) {
+ return (
+ // create closed by default
+
+
+
+
+ {/* We create a SidebarGroup for each parent. */}
+ {data.navMain.map((item) => (
+
+ {item.title}
+
+
+ {item.items.map((item) => (
+
+
+ {item.title}
+
+
+ ))}
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/crawler/frontend/src/assets/LoginModal.css b/crawler/frontend/src/assets/LoginModal.css
deleted file mode 100644
index b7a7eef..0000000
--- a/crawler/frontend/src/assets/LoginModal.css
+++ /dev/null
@@ -1,61 +0,0 @@
-/* .modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
-}
-
-.modal-content {
- background: white;
- border-radius: 8px;
- max-width: 90%;
- max-height: 90vh;
- width: 500px;
- overflow-y: auto;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- animation: modalFadeIn 0.3s ease-out;
-}
-
-.modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 24px;
- border-bottom: 1px solid #eee;
-}
-
-.modal-title {
- margin: 0;
- font-size: 1.25rem;
- color: rgba(0, 0, 0, 1)
-}
-
-.modal-close {
- background: none;
- border: none;
- font-size: 1.5rem;
- cursor: pointer;
- padding: 0 8px;
-}
-
-.modal-body {
- padding: 24px;
-}
-
-@keyframes modalFadeIn {
- from {
- opacity: 0;
- transform: translateY(-20px);
- }
-
- to {
- opacity: 1;
- transform: translateY(0);
- }
-} */
diff --git a/crawler/frontend/src/assets/Map.css b/crawler/frontend/src/assets/Map.css
new file mode 100644
index 0000000..e65969f
--- /dev/null
+++ b/crawler/frontend/src/assets/Map.css
@@ -0,0 +1,55 @@
+#map-container {
+ /* position: absolute; */
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ flex: 1;
+
+ /* position: 'relative'; */
+ /* overflow: 'visible'; */
+ /* isolation: 'isolate'; */
+
+}
+
+.sidebar {
+ background-color: rgb(35 55 75 / 90%);
+ color: #fff;
+ padding: 6px 12px;
+ font-family: monospace;
+ z-index: 1;
+ position: absolute;
+ top: 0;
+ left: 0;
+ margin: 12px;
+ border-radius: 4px;
+}
+
+
+#legend {
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+ line-height: 18px;
+ height: 310px;
+ width: 60px;
+ padding: 10px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ background: rgba(255, 255, 255, 0.8);
+ margin-top: 40px;
+ margin-right: 20px;
+ font-family: Arial, sans-serif;
+}
+
+.propertyListingPopupItem {
+ display: 'flex';
+ box-sizing: border-box;
+ border: 1px solid #aaa;
+ justify-content: center;
+ font-family: sans-serif;
+ padding: 8px;
+ width: 50%;
+ /* 2 columns */
+}
diff --git a/crawler/frontend/src/assets/style.css b/crawler/frontend/src/assets/style.css
deleted file mode 100644
index a075765..0000000
--- a/crawler/frontend/src/assets/style.css
+++ /dev/null
@@ -1,107 +0,0 @@
-/* #map {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 100%;
-}
-
-#legend {
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
- line-height: 18px;
- height: 310px;
- width: 60px;
- padding: 10px;
- position: absolute;
- top: 0;
- right: 0;
- background: rgba(255, 255, 255, 0.8);
- margin-top: 40px;
- margin-right: 20px;
- font-family: Arial, sans-serif;
-}
-
-#overlay {
- position: absolute;
-}
-
-.center{
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
-}
-
-.modal {
- position: fixed;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.65);
- visibility: hidden;
- backface-visibility: hidden;
- opacity: 0;
- transition: opacity .15s ease-in-out;
-}
-
-.modal.modal-open {
- visibility: visible;
- backface-visibility: visible;
- opacity: 1;
- z-index: 1;
-}
-
-.modal .modal-inner {
- position: relative;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-}
-
-.modal .modal-inner .modal-content {
- background-color: white;
- max-width: 35em;
- padding: 1em 1.5em;
- position: relative;
- margin: 2em;
- box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.35);
-}
-
-.modal .modal-inner .modal-content .modal-close-icon {
- position: absolute;
- right: 1.5em;
-}
-
-.modal .modal-inner .modal-content .modal-content-inner {
- padding-right: 2em;
-}
-
-.modal .modal-inner .modal-content .modal-content-inner h1, .modal .modal-inner .modal-content .modal-content-inner h2, .modal .modal-inner .modal-content .modal-content-inner h3, .modal .modal-inner .modal-content .modal-content-inner h4, .modal .modal-inner .modal-content .modal-content-inner h5, .modal .modal-inner .modal-content .modal-content-inner h6 {
- margin-bottom: 0.25em;
-}
-
-.modal .modal-inner .modal-content .modal-content-inner p {
- margin-bottom: 1em;
-}
-
-.modal .modal-inner .modal-content .modal-buttons-seperator {
- margin: 1.5em 0;
- margin-top: 0;
-}
-
-.modal .modal-inner .modal-content .modal-buttons {
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
- align-items: center;
-}
-
-.modal .modal-inner .modal-content .modal-buttons button {
- margin-left: 1em;
-}
-
-.modal .modal-inner .modal-content .modal-buttons button:first-child {
- margin-left: 0;
-} */
diff --git a/crawler/frontend/src/components/LoginModal.tsx b/crawler/frontend/src/components/LoginModal.tsx
index 7d47897..36f69f0 100644
--- a/crawler/frontend/src/components/LoginModal.tsx
+++ b/crawler/frontend/src/components/LoginModal.tsx
@@ -1,10 +1,8 @@
import { login } from '@/auth/authService';
import { Button } from "@/components/ui/button";
import React from 'react';
-import '../assets/LoginModal.css';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from './ui/dialog';
-
interface ModalProps {
isOpen: boolean;
}
diff --git a/crawler/frontend/src/components/Map.tsx b/crawler/frontend/src/components/Map.tsx
index 2afb519..86b025f 100644
--- a/crawler/frontend/src/components/Map.tsx
+++ b/crawler/frontend/src/components/Map.tsx
@@ -1,8 +1,13 @@
import crossfilter from "crossfilter2";
import * as d3 from "d3";
import mapboxgl from "mapbox-gl";
-// import 'mapbox-gl/dist/mapbox-gl.css';
+import 'mapbox-gl/dist/mapbox-gl.css'; // this hides the map for some reason
import { useEffect, useRef } from "react";
+import { renderToString } from 'react-dom/server';
+import "../assets/Map.css";
+import { Button } from "./ui/button";
+import { ScrollArea } from "./ui/scroll-area";
+import { Separator } from "./ui/separator";
export function Map(
props: {
@@ -31,7 +36,7 @@ export function Map(
// filter['countries'] = Array.from(new Set(data.features.map(function (d) { return d['properties']['country'] })));
// rivets.bind(document.getElementById('overlay'), { filter: filter });
const mapRef = useRef(mapboxgl.Map)
- const mapContainerRef = useRef('map')
+ const mapContainerRef = useRef('map-container')
useEffect(() => {
mapboxgl.accessToken = 'pk.eyJ1IjoiZGktdG8iLCJhIjoiY2o0bnBoYXcxMW1mNzJ3bDhmc2xiNWttaiJ9.ZccatVk_4shzoAsEUXXecA';
mapRef.current = new mapboxgl.Map({
@@ -72,10 +77,6 @@ export function Map(
}
function update() {
- // close overlay
- var modal = document.getElementById('modal_overlay');
- modal.classList.toggle('modal-open');
-
// init heatmap
heatmap = new HexgridHeatmap(mapRef.current, "hexgrid-heatmap", "waterway-label");
heatmap.setIntensity(9); // dunno yet
@@ -193,77 +194,116 @@ export function Map(
minY: latitude - searchBuffer,
maxY: latitude + searchBuffer
})
- const html = getListingDialogHTML(properties);
if (properties.length > 0) {
+ const listingDialogPopup = getListingDialog(properties);
new mapboxgl.Popup()
.setLngLat([longtitude, latitude])
- .setHTML(html)
+ .setHTML(renderToString(listingDialogPopup))
.setMaxWidth("500px")
.addTo(mapRef.current);
}
}
- function getListingDialogHTML(properties) {
- let listinHTMLs = [];
+ function getListingDialog(properties) {
+ let listingComponents = [];
for (let property of properties) {
- listinHTMLs.push(getPropertyHTML(property));
+ listingComponents.push(getPropertyComponent(property));
}
- // separate them with a line
- const result = listinHTMLs.join('
');
- const styledResult = `
-
- ${result}
-
- `
+ console.log(listingComponents.length)
+ return
+
- return styledResult;
+
+ Showing {properties.length} properties
+
+ {listingComponents.map((item) => {
+ const scrollDiv =
+ {item}
+
+
;
+ return scrollDiv
+ })}
+
+ ;
}
- function getPropertyHTML(property) {
+
+ function getPropertyComponent(property) {
const priceHistoryHTMLs = property.properties.price_history.map((d) => {
- return `${d.last_seen.split('T')[0]}: £${d.price}`;
+ return ${d.last_seen.split('T')[0]}: £${d.price};
});
- let priceHistoryHTML = '';
+ let priceHistoryHTML = <>>;
if (priceHistoryHTMLs.length > 1) {
- priceHistoryHTML = `
- Price history:
-
- ${priceHistoryHTMLs.join('')}
-
-
- `
+ priceHistoryHTML =
+
+
Price history:
+
+ ${priceHistoryHTMLs.join('')}
+
+
+
+
}
const lastSeenStr = property.properties.last_seen.split('T')[0];
const lastSeenDays = Math.round((new Date() - new Date(lastSeenStr)) / (1000 * 60 * 60 * 24));
+ return
+
+

+
+
+ Available from:
+
+
+ {property.properties.available_from}
+
+
+ Price:
+
+
+ £{property.properties.total_price}
+
+ {priceHistoryHTML}
+
+ Rooms:
+
+
- return `
-
-

-
- Available from: ${property.properties.available_from}
-
- Price: £${property.properties.total_price}
-
- ${priceHistoryHTML}
- Rooms: ${property.properties.rooms}
-
- Area: ${property.properties.qm} m²
-
- Price per area: £${property.properties.qmprice}/m²
-
- Last seen: ${lastSeenDays} days ago
-
- Agency: ${property.properties.agency}
-
- View Listing
-
+ {property.properties.rooms}
+
+
+ Area:
+
+
+ {property.properties.qm} m²
+
+
+ Price per area:
+
+
+ £{property.properties.qmprice}/m²
+
+
+ Last seen:
+
+
+ {lastSeenDays} days ago
+
+
+ Agency:
+
+
+ {property.properties.agency}
+
+
- `;
-
}
return <>
-
+