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 logo - - - React logo - -
-

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: - -
    - ` + 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 <> -
    +
    diff --git a/crawler/frontend/src/components/Parameters.tsx b/crawler/frontend/src/components/Parameters.tsx index 7f86a79..9c3e5c6 100644 --- a/crawler/frontend/src/components/Parameters.tsx +++ b/crawler/frontend/src/components/Parameters.tsx @@ -1,91 +1,41 @@ -import { useState } from "react"; -import { useAuth } from "react-oidc-context"; +import { Button } from "./ui/button"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; export function Parameters( props: { - setListingData: (data: any) => void, + isOpen: boolean, + onSubmit: () => void, } ) { - const [error, setError] = useState('') - const [isLoading, setIsLoading] = useState(false) - const { user } = useAuth(); // Get user data (includes token) - 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(); - props.setListingData(data); - } catch (err) { - setError('Failed to fetch data: ' + err); - alert(error) - } finally { - setIsLoading(false); - } - }; - return <> - + {/* */} + +
    + + + + + + Parameters + + + + + + +
    +
    } diff --git a/crawler/frontend/src/components/ui/breadcrumb.tsx b/crawler/frontend/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..eb88f32 --- /dev/null +++ b/crawler/frontend/src/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return