diff --git a/Rents_Only.postman_collection.json b/Rents_Only.postman_collection.json
new file mode 100644
index 0000000..aa26f91
--- /dev/null
+++ b/Rents_Only.postman_collection.json
@@ -0,0 +1,204 @@
+{
+ "info": {
+ "name": "Dubai DLD - Rents Only",
+ "_postman_id": "f3c2a1b8-6d5e-4e9a-93d1-rent-only-001",
+ "description": "Collection with only recent rents connection examples for the /api/rents/recent endpoint.",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "variable": [
+ {
+ "key": "baseUrl",
+ "value": "http://localhost:3000"
+ }
+ ],
+ "item": [
+ {
+ "name": "Recent Rents - No Filters (default limit=30)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "api",
+ "rents",
+ "recent"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by area_name (contains, case-insensitive)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?area_name=business%20bay&limit=30",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "api",
+ "rents",
+ "recent"
+ ],
+ "query": [
+ { "key": "area_name", "value": "business bay" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by property_type (matches prop_type_en or prop_sub_type_en)",
+ "request": {
+ "method": "GET",
+ "header": [
+ { "key": "Accept", "value": "application/json" }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?property_type=unit&limit=30",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "property_type", "value": "unit" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by rooms (decimal values: 1, 2, 3, 4, 5)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?rooms=3&limit=30",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "rooms", "value": "3" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by project (matches project_en or master_project_en)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?project=burj%20views&limit=30",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "project", "value": "burj views" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by size_min and size_max (range)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?size_min=1000&size_max=5000&limit=30",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "size_min", "value": "1000" },
+ { "key": "size_max", "value": "5000" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by only size_min (greater or equal)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?size_min=1500&limit=30",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "size_min", "value": "1500" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter by only size_max (less or equal)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?size_max=3000&limit=30",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "size_max", "value": "3000" },
+ { "key": "limit", "value": "30" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Combined filters (area + type + rooms + project + size range)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?area_name=business%20bay&property_type=unit&rooms=2&project=sami%20q%20tower&size_min=50&size_max=100&limit=50",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "area_name", "value": "business bay" },
+ { "key": "property_type", "value": "unit" },
+ { "key": "rooms", "value": "2" },
+ { "key": "project", "value": "sami q tower" },
+ { "key": "size_min", "value": "50" },
+ { "key": "size_max", "value": "100" },
+ { "key": "limit", "value": "50" }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Limit override (top N)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?limit=100",
+ "host": [ "{{baseUrl}}" ],
+ "path": [ "api", "rents", "recent" ],
+ "query": [
+ { "key": "limit", "value": "100" }
+ ]
+ }
+ }
+ }
+ ]
+}
+
+
+
diff --git a/Rents_Only_Paginated.postman_collection.json b/Rents_Only_Paginated.postman_collection.json
new file mode 100644
index 0000000..47c9e52
--- /dev/null
+++ b/Rents_Only_Paginated.postman_collection.json
@@ -0,0 +1,155 @@
+{
+ "info": {
+ "name": "Dubai DLD - Rents Only (Paginated)",
+ "_postman_id": "c0a5b3e6-6b0f-4b9e-9f77-rents-only-v2",
+ "description": "Only /api/rents/recent examples, covering filters, legacy limit, and server-side pagination.",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "variable": [
+ { "key": "baseUrl", "value": "http://localhost:3000" }
+ ],
+ "item": [
+ {
+ "name": "Recent - No filters (all rows, no LIMIT)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": { "raw": "{{baseUrl}}/api/rents/recent", "host": ["{{baseUrl}}"], "path": ["api","rents","recent"] }
+ }
+ },
+ {
+ "name": "Legacy: limit only (top N without OFFSET)",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?limit=10",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [ {"key":"limit","value":"10"} ]
+ }
+ }
+ },
+ {
+ "name": "Pagination: page 1, size 30",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?page=1&page_size=30",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [ {"key":"page","value":"1"}, {"key":"page_size","value":"30"} ]
+ }
+ }
+ },
+ {
+ "name": "Pagination: page 2, size 30",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?page=2&page_size=30",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [ {"key":"page","value":"2"}, {"key":"page_size","value":"30"} ]
+ }
+ }
+ },
+ {
+ "name": "Filter: area_name + pagination",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?area_name=business%20bay&page=1&page_size=50",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [
+ {"key":"area_name","value":"business bay"},
+ {"key":"page","value":"1"},
+ {"key":"page_size","value":"50"}
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter: property_type + rooms + pagination",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?property_type=unit&rooms=2&page=1&page_size=30",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [
+ {"key":"property_type","value":"unit"},
+ {"key":"rooms","value":"2"},
+ {"key":"page","value":"1"},
+ {"key":"page_size","value":"30"}
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter: size range + pagination",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?size_min=1000&size_max=5000&page=1&page_size=30",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [
+ {"key":"size_min","value":"1000"},
+ {"key":"size_max","value":"5000"},
+ {"key":"page","value":"1"},
+ {"key":"page_size","value":"30"}
+ ]
+ }
+ }
+ },
+ {
+ "name": "Filter: project + pagination",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?project=burj%20views&page=1&page_size=30",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [
+ {"key":"project","value":"burj views"},
+ {"key":"page","value":"1"},
+ {"key":"page_size","value":"30"}
+ ]
+ }
+ }
+ },
+ {
+ "name": "Combined filters + pagination",
+ "request": {
+ "method": "GET",
+ "header": [ { "key": "Accept", "value": "application/json" } ],
+ "url": {
+ "raw": "{{baseUrl}}/api/rents/recent?area_name=business%20bay&property_type=unit&rooms=2&project=sami%20q%20tower&size_min=50&size_max=100&page=1&page_size=50",
+ "host": ["{{baseUrl}}"],
+ "path": ["api","rents","recent"],
+ "query": [
+ {"key":"area_name","value":"business bay"},
+ {"key":"property_type","value":"unit"},
+ {"key":"rooms","value":"2"},
+ {"key":"project","value":"sami q tower"},
+ {"key":"size_min","value":"50"},
+ {"key":"size_max","value":"100"},
+ {"key":"page","value":"1"},
+ {"key":"page_size","value":"50"}
+ ]
+ }
+ }
+ }
+ ]
+}
+
+
+
diff --git a/configure_db.sh b/configure_db.sh
old mode 100755
new mode 100644
diff --git a/node_modules/.bin/baseline-browser-mapping b/node_modules/.bin/baseline-browser-mapping
index d296188..e69de29 120000
--- a/node_modules/.bin/baseline-browser-mapping
+++ b/node_modules/.bin/baseline-browser-mapping
@@ -1 +0,0 @@
-../baseline-browser-mapping/dist/cli.js
\ No newline at end of file
diff --git a/node_modules/.bin/browserslist b/node_modules/.bin/browserslist
index 3cd991b..e69de29 120000
--- a/node_modules/.bin/browserslist
+++ b/node_modules/.bin/browserslist
@@ -1 +0,0 @@
-../browserslist/cli.js
\ No newline at end of file
diff --git a/node_modules/.bin/create-jest b/node_modules/.bin/create-jest
index 8d6301e..e69de29 120000
--- a/node_modules/.bin/create-jest
+++ b/node_modules/.bin/create-jest
@@ -1 +0,0 @@
-../create-jest/bin/create-jest.js
\ No newline at end of file
diff --git a/node_modules/.bin/esparse b/node_modules/.bin/esparse
index 7423b18..e69de29 120000
--- a/node_modules/.bin/esparse
+++ b/node_modules/.bin/esparse
@@ -1 +0,0 @@
-../esprima/bin/esparse.js
\ No newline at end of file
diff --git a/node_modules/.bin/esvalidate b/node_modules/.bin/esvalidate
index 16069ef..e69de29 120000
--- a/node_modules/.bin/esvalidate
+++ b/node_modules/.bin/esvalidate
@@ -1 +0,0 @@
-../esprima/bin/esvalidate.js
\ No newline at end of file
diff --git a/node_modules/.bin/import-local-fixture b/node_modules/.bin/import-local-fixture
index ff4b104..e69de29 120000
--- a/node_modules/.bin/import-local-fixture
+++ b/node_modules/.bin/import-local-fixture
@@ -1 +0,0 @@
-../import-local/fixtures/cli.js
\ No newline at end of file
diff --git a/node_modules/.bin/jest b/node_modules/.bin/jest
index 61c1861..e69de29 120000
--- a/node_modules/.bin/jest
+++ b/node_modules/.bin/jest
@@ -1 +0,0 @@
-../jest/bin/jest.js
\ No newline at end of file
diff --git a/node_modules/.bin/js-yaml b/node_modules/.bin/js-yaml
index 9dbd010..e69de29 120000
--- a/node_modules/.bin/js-yaml
+++ b/node_modules/.bin/js-yaml
@@ -1 +0,0 @@
-../js-yaml/bin/js-yaml.js
\ No newline at end of file
diff --git a/node_modules/.bin/jsesc b/node_modules/.bin/jsesc
index 7237604..e69de29 120000
--- a/node_modules/.bin/jsesc
+++ b/node_modules/.bin/jsesc
@@ -1 +0,0 @@
-../jsesc/bin/jsesc
\ No newline at end of file
diff --git a/node_modules/.bin/json5 b/node_modules/.bin/json5
index 217f379..e69de29 120000
--- a/node_modules/.bin/json5
+++ b/node_modules/.bin/json5
@@ -1 +0,0 @@
-../json5/lib/cli.js
\ No newline at end of file
diff --git a/node_modules/.bin/mime b/node_modules/.bin/mime
index fbb7ee0..e69de29 120000
--- a/node_modules/.bin/mime
+++ b/node_modules/.bin/mime
@@ -1 +0,0 @@
-../mime/cli.js
\ No newline at end of file
diff --git a/node_modules/.bin/node-which b/node_modules/.bin/node-which
index 6f8415e..e69de29 120000
--- a/node_modules/.bin/node-which
+++ b/node_modules/.bin/node-which
@@ -1 +0,0 @@
-../which/bin/node-which
\ No newline at end of file
diff --git a/node_modules/.bin/nodemon b/node_modules/.bin/nodemon
index 1056ddc..e69de29 120000
--- a/node_modules/.bin/nodemon
+++ b/node_modules/.bin/nodemon
@@ -1 +0,0 @@
-../nodemon/bin/nodemon.js
\ No newline at end of file
diff --git a/node_modules/.bin/nodetouch b/node_modules/.bin/nodetouch
index 3409fdb..e69de29 120000
--- a/node_modules/.bin/nodetouch
+++ b/node_modules/.bin/nodetouch
@@ -1 +0,0 @@
-../touch/bin/nodetouch.js
\ No newline at end of file
diff --git a/node_modules/.bin/parser b/node_modules/.bin/parser
index ce7bf97..e69de29 120000
--- a/node_modules/.bin/parser
+++ b/node_modules/.bin/parser
@@ -1 +0,0 @@
-../@babel/parser/bin/babel-parser.js
\ No newline at end of file
diff --git a/node_modules/.bin/resolve b/node_modules/.bin/resolve
index b6afda6..e69de29 120000
--- a/node_modules/.bin/resolve
+++ b/node_modules/.bin/resolve
@@ -1 +0,0 @@
-../resolve/bin/resolve
\ No newline at end of file
diff --git a/node_modules/.bin/semver b/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/.bin/semver
+++ b/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/.bin/update-browserslist-db b/node_modules/.bin/update-browserslist-db
index b11e16f..e69de29 120000
--- a/node_modules/.bin/update-browserslist-db
+++ b/node_modules/.bin/update-browserslist-db
@@ -1 +0,0 @@
-../update-browserslist-db/cli.js
\ No newline at end of file
diff --git a/node_modules/.bin/uuid b/node_modules/.bin/uuid
index 588f70e..e69de29 120000
--- a/node_modules/.bin/uuid
+++ b/node_modules/.bin/uuid
@@ -1 +0,0 @@
-../uuid/dist/bin/uuid
\ No newline at end of file
diff --git a/node_modules/@babel/parser/bin/babel-parser.js b/node_modules/@babel/parser/bin/babel-parser.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/LICENSE.md b/node_modules/@hapi/hoek/LICENSE.md
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/README.md b/node_modules/@hapi/hoek/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/applyToDefaults.js b/node_modules/@hapi/hoek/lib/applyToDefaults.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/assert.js b/node_modules/@hapi/hoek/lib/assert.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/bench.js b/node_modules/@hapi/hoek/lib/bench.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/block.js b/node_modules/@hapi/hoek/lib/block.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/clone.js b/node_modules/@hapi/hoek/lib/clone.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/contain.js b/node_modules/@hapi/hoek/lib/contain.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/deepEqual.js b/node_modules/@hapi/hoek/lib/deepEqual.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/error.js b/node_modules/@hapi/hoek/lib/error.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/escapeHeaderAttribute.js b/node_modules/@hapi/hoek/lib/escapeHeaderAttribute.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/escapeHtml.js b/node_modules/@hapi/hoek/lib/escapeHtml.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/escapeJson.js b/node_modules/@hapi/hoek/lib/escapeJson.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/escapeRegex.js b/node_modules/@hapi/hoek/lib/escapeRegex.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/flatten.js b/node_modules/@hapi/hoek/lib/flatten.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/ignore.js b/node_modules/@hapi/hoek/lib/ignore.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/index.d.ts b/node_modules/@hapi/hoek/lib/index.d.ts
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/index.js b/node_modules/@hapi/hoek/lib/index.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/intersect.js b/node_modules/@hapi/hoek/lib/intersect.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/isPromise.js b/node_modules/@hapi/hoek/lib/isPromise.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/merge.js b/node_modules/@hapi/hoek/lib/merge.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/once.js b/node_modules/@hapi/hoek/lib/once.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/reach.js b/node_modules/@hapi/hoek/lib/reach.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/reachTemplate.js b/node_modules/@hapi/hoek/lib/reachTemplate.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/stringify.js b/node_modules/@hapi/hoek/lib/stringify.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/types.js b/node_modules/@hapi/hoek/lib/types.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/utils.js b/node_modules/@hapi/hoek/lib/utils.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/lib/wait.js b/node_modules/@hapi/hoek/lib/wait.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/hoek/package.json b/node_modules/@hapi/hoek/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/topo/LICENSE.md b/node_modules/@hapi/topo/LICENSE.md
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/topo/README.md b/node_modules/@hapi/topo/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/topo/lib/index.d.ts b/node_modules/@hapi/topo/lib/index.d.ts
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/topo/lib/index.js b/node_modules/@hapi/topo/lib/index.js
old mode 100755
new mode 100644
diff --git a/node_modules/@hapi/topo/package.json b/node_modules/@hapi/topo/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/README.md b/node_modules/@sideway/address/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/decode.js b/node_modules/@sideway/address/lib/decode.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/domain.js b/node_modules/@sideway/address/lib/domain.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/email.js b/node_modules/@sideway/address/lib/email.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/errors.js b/node_modules/@sideway/address/lib/errors.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/index.d.ts b/node_modules/@sideway/address/lib/index.d.ts
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/index.js b/node_modules/@sideway/address/lib/index.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/ip.js b/node_modules/@sideway/address/lib/ip.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/tlds.js b/node_modules/@sideway/address/lib/tlds.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/lib/uri.js b/node_modules/@sideway/address/lib/uri.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/address/package.json b/node_modules/@sideway/address/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/formula/README.md b/node_modules/@sideway/formula/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/formula/lib/index.d.ts b/node_modules/@sideway/formula/lib/index.d.ts
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/formula/lib/index.js b/node_modules/@sideway/formula/lib/index.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/formula/package.json b/node_modules/@sideway/formula/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/pinpoint/LICENSE.md b/node_modules/@sideway/pinpoint/LICENSE.md
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/pinpoint/README.md b/node_modules/@sideway/pinpoint/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/pinpoint/lib/index.d.ts b/node_modules/@sideway/pinpoint/lib/index.d.ts
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/pinpoint/lib/index.js b/node_modules/@sideway/pinpoint/lib/index.js
old mode 100755
new mode 100644
diff --git a/node_modules/@sideway/pinpoint/package.json b/node_modules/@sideway/pinpoint/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/aws-ssl-profiles/package.json b/node_modules/aws-ssl-profiles/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/baseline-browser-mapping/dist/cli.js b/node_modules/baseline-browser-mapping/dist/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/browserslist/cli.js b/node_modules/browserslist/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/bson/etc/prepare.js b/node_modules/bson/etc/prepare.js
old mode 100755
new mode 100644
diff --git a/node_modules/cjs-module-lexer/LICENSE b/node_modules/cjs-module-lexer/LICENSE
old mode 100755
new mode 100644
diff --git a/node_modules/cjs-module-lexer/README.md b/node_modules/cjs-module-lexer/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/cjs-module-lexer/lexer.d.ts b/node_modules/cjs-module-lexer/lexer.d.ts
old mode 100755
new mode 100644
diff --git a/node_modules/cjs-module-lexer/lexer.js b/node_modules/cjs-module-lexer/lexer.js
old mode 100755
new mode 100644
diff --git a/node_modules/cjs-module-lexer/package.json b/node_modules/cjs-module-lexer/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/create-jest/bin/create-jest.js b/node_modules/create-jest/bin/create-jest.js
old mode 100755
new mode 100644
diff --git a/node_modules/esprima/bin/esparse.js b/node_modules/esprima/bin/esparse.js
old mode 100755
new mode 100644
diff --git a/node_modules/esprima/bin/esvalidate.js b/node_modules/esprima/bin/esvalidate.js
old mode 100755
new mode 100644
diff --git a/node_modules/exit/test/fixtures/create-files.sh b/node_modules/exit/test/fixtures/create-files.sh
old mode 100755
new mode 100644
diff --git a/node_modules/import-local/fixtures/cli.js b/node_modules/import-local/fixtures/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/istanbul-lib-instrument/node_modules/.bin/semver b/node_modules/istanbul-lib-instrument/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/istanbul-lib-instrument/node_modules/.bin/semver
+++ b/node_modules/istanbul-lib-instrument/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/istanbul-lib-instrument/node_modules/semver/bin/semver.js b/node_modules/istanbul-lib-instrument/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/jest-cli/bin/jest.js b/node_modules/jest-cli/bin/jest.js
old mode 100755
new mode 100644
diff --git a/node_modules/jest-snapshot/node_modules/.bin/semver b/node_modules/jest-snapshot/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/jest-snapshot/node_modules/.bin/semver
+++ b/node_modules/jest-snapshot/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/jest-snapshot/node_modules/semver/bin/semver.js b/node_modules/jest-snapshot/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/jest/bin/jest.js b/node_modules/jest/bin/jest.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/LICENSE.md b/node_modules/joi/LICENSE.md
old mode 100755
new mode 100644
diff --git a/node_modules/joi/README.md b/node_modules/joi/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/annotate.js b/node_modules/joi/lib/annotate.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/base.js b/node_modules/joi/lib/base.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/cache.js b/node_modules/joi/lib/cache.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/common.js b/node_modules/joi/lib/common.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/compile.js b/node_modules/joi/lib/compile.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/errors.js b/node_modules/joi/lib/errors.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/extend.js b/node_modules/joi/lib/extend.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/index.js b/node_modules/joi/lib/index.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/manifest.js b/node_modules/joi/lib/manifest.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/messages.js b/node_modules/joi/lib/messages.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/modify.js b/node_modules/joi/lib/modify.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/ref.js b/node_modules/joi/lib/ref.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/schemas.js b/node_modules/joi/lib/schemas.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/state.js b/node_modules/joi/lib/state.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/template.js b/node_modules/joi/lib/template.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/trace.js b/node_modules/joi/lib/trace.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/alternatives.js b/node_modules/joi/lib/types/alternatives.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/any.js b/node_modules/joi/lib/types/any.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/array.js b/node_modules/joi/lib/types/array.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/binary.js b/node_modules/joi/lib/types/binary.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/boolean.js b/node_modules/joi/lib/types/boolean.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/date.js b/node_modules/joi/lib/types/date.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/function.js b/node_modules/joi/lib/types/function.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/keys.js b/node_modules/joi/lib/types/keys.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/link.js b/node_modules/joi/lib/types/link.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/number.js b/node_modules/joi/lib/types/number.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/object.js b/node_modules/joi/lib/types/object.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/string.js b/node_modules/joi/lib/types/string.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/types/symbol.js b/node_modules/joi/lib/types/symbol.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/validator.js b/node_modules/joi/lib/validator.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/lib/values.js b/node_modules/joi/lib/values.js
old mode 100755
new mode 100644
diff --git a/node_modules/joi/package.json b/node_modules/joi/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/js-yaml/bin/js-yaml.js b/node_modules/js-yaml/bin/js-yaml.js
old mode 100755
new mode 100644
diff --git a/node_modules/jsesc/bin/jsesc b/node_modules/jsesc/bin/jsesc
old mode 100755
new mode 100644
diff --git a/node_modules/json5/lib/cli.js b/node_modules/json5/lib/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/make-dir/node_modules/.bin/semver b/node_modules/make-dir/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/make-dir/node_modules/.bin/semver
+++ b/node_modules/make-dir/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/make-dir/node_modules/semver/bin/semver.js b/node_modules/make-dir/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/micromatch/LICENSE b/node_modules/micromatch/LICENSE
old mode 100755
new mode 100644
diff --git a/node_modules/mime/cli.js b/node_modules/mime/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/mime/src/build.js b/node_modules/mime/src/build.js
old mode 100755
new mode 100644
diff --git a/node_modules/mongodb/etc/prepare.js b/node_modules/mongodb/etc/prepare.js
old mode 100755
new mode 100644
diff --git a/node_modules/natural/lib/natural/util/stopwords_nl.js b/node_modules/natural/lib/natural/util/stopwords_nl.js
old mode 100755
new mode 100644
diff --git a/node_modules/nodemon/bin/nodemon.js b/node_modules/nodemon/bin/nodemon.js
old mode 100755
new mode 100644
diff --git a/node_modules/nodemon/node_modules/.bin/semver b/node_modules/nodemon/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/nodemon/node_modules/.bin/semver
+++ b/node_modules/nodemon/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/nodemon/node_modules/semver/bin/semver.js b/node_modules/nodemon/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/pretty-format/README.md b/node_modules/pretty-format/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/prompts/readme.md b/node_modules/prompts/readme.md
old mode 100755
new mode 100644
diff --git a/node_modules/resolve/bin/resolve b/node_modules/resolve/bin/resolve
old mode 100755
new mode 100644
diff --git a/node_modules/semver/bin/semver.js b/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/sift/README.md b/node_modules/sift/README.md
old mode 100755
new mode 100644
diff --git a/node_modules/simple-update-notifier/node_modules/.bin/semver b/node_modules/simple-update-notifier/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/simple-update-notifier/node_modules/.bin/semver
+++ b/node_modules/simple-update-notifier/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/simple-update-notifier/node_modules/semver/bin/semver.js b/node_modules/simple-update-notifier/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/sisteransi/package.json b/node_modules/sisteransi/package.json
old mode 100755
new mode 100644
diff --git a/node_modules/sisteransi/readme.md b/node_modules/sisteransi/readme.md
old mode 100755
new mode 100644
diff --git a/node_modules/superagent/node_modules/.bin/mime b/node_modules/superagent/node_modules/.bin/mime
index fbb7ee0..e69de29 120000
--- a/node_modules/superagent/node_modules/.bin/mime
+++ b/node_modules/superagent/node_modules/.bin/mime
@@ -1 +0,0 @@
-../mime/cli.js
\ No newline at end of file
diff --git a/node_modules/superagent/node_modules/.bin/semver b/node_modules/superagent/node_modules/.bin/semver
index 5aaadf4..e69de29 120000
--- a/node_modules/superagent/node_modules/.bin/semver
+++ b/node_modules/superagent/node_modules/.bin/semver
@@ -1 +0,0 @@
-../semver/bin/semver.js
\ No newline at end of file
diff --git a/node_modules/superagent/node_modules/mime/cli.js b/node_modules/superagent/node_modules/mime/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/superagent/node_modules/semver/bin/semver.js b/node_modules/superagent/node_modules/semver/bin/semver.js
old mode 100755
new mode 100644
diff --git a/node_modules/touch/bin/nodetouch.js b/node_modules/touch/bin/nodetouch.js
old mode 100755
new mode 100644
diff --git a/node_modules/update-browserslist-db/cli.js b/node_modules/update-browserslist-db/cli.js
old mode 100755
new mode 100644
diff --git a/node_modules/uuid/dist/bin/uuid b/node_modules/uuid/dist/bin/uuid
old mode 100755
new mode 100644
diff --git a/node_modules/which/bin/node-which b/node_modules/which/bin/node-which
old mode 100755
new mode 100644
diff --git a/node_modules/wrap-ansi/index.js b/node_modules/wrap-ansi/index.js
old mode 100755
new mode 100644
diff --git a/public/js/index.js b/public/js/index.js
new file mode 100644
index 0000000..70ec75c
--- /dev/null
+++ b/public/js/index.js
@@ -0,0 +1,183 @@
+// Dubai DLD Analytics Dashboard JavaScript
+// No inline scripts or handlers to satisfy CSP
+
+const API_BASE = '/api';
+
+// Helper functions
+function showLoading(show) {
+ document.getElementById('loading').style.display = show ? 'block' : 'none';
+}
+
+function showError(message) {
+ const errorDiv = document.getElementById('error');
+ errorDiv.textContent = message;
+ errorDiv.style.display = 'block';
+}
+
+function hideError() {
+ document.getElementById('error').style.display = 'none';
+}
+
+// Clear results
+function clearResults() {
+ document.getElementById('cardsContainer').innerHTML = '';
+ document.getElementById('chartsContainer').innerHTML = '';
+ document.getElementById('queryInput').value = '';
+ hideError();
+}
+
+// Render cards
+function renderCards(cards) {
+ const container = document.getElementById('cardsContainer');
+ container.innerHTML = cards.map(card => `
+
+
${card.title}
+
${card.value}
+
${card.subtitle}
+
+ `).join('');
+}
+
+// Render charts
+function renderCharts(visualizations) {
+ const container = document.getElementById('chartsContainer');
+ container.innerHTML = '';
+
+ visualizations.forEach((viz, index) => {
+ const chartDiv = document.createElement('div');
+ chartDiv.className = 'chart-container';
+ chartDiv.innerHTML = `
+ ${viz.title}
+
+ `;
+ container.appendChild(chartDiv);
+
+ const canvas = document.getElementById(`chart${index}`);
+ const ctx = canvas.getContext('2d');
+
+ new Chart(ctx, {
+ type: viz.type,
+ data: viz.data,
+ options: viz.options || {}
+ });
+ });
+}
+
+// Process query
+async function processQuery(query) {
+ if (!query || query.trim() === '') {
+ showError('Please enter a query');
+ return;
+ }
+
+ showLoading(true);
+ hideError();
+
+ try {
+ const response = await fetch(`${API_BASE}/query`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ query })
+ });
+
+ const data = await response.json();
+
+ if (data.success && data.data) {
+ // Render cards and charts
+ if (data.data.cards && data.data.cards.length > 0) {
+ renderCards(data.data.cards);
+ }
+ if (data.data.visualizations && data.data.visualizations.length > 0) {
+ renderCharts(data.data.visualizations);
+ }
+ } else {
+ showError(data.message || 'Failed to process query');
+ }
+ } catch (error) {
+ showError('Network error: ' + error.message);
+ } finally {
+ showLoading(false);
+ }
+}
+
+// Event listeners setup
+document.addEventListener('DOMContentLoaded', () => {
+ // Analyze button
+ const btnAnalyze = document.getElementById('btn-analyze');
+ btnAnalyze.addEventListener('click', async () => {
+ const query = document.getElementById('queryInput').value;
+ await processQuery(query);
+ });
+
+ // Clear button
+ const btnClear = document.getElementById('btn-clear');
+ btnClear.addEventListener('click', clearResults);
+
+ // Predefined query buttons
+ document.getElementById('btn-rental-trend').addEventListener('click', async () => {
+ const query = 'Give me the last 6 months rental price trend for Business Bay';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-top-areas').addEventListener('click', async () => {
+ const query = 'Which area is having more rental transactions?';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-project-summary').addEventListener('click', async () => {
+ const query = 'Brief about the Project';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-commercial-leasing').addEventListener('click', async () => {
+ const query = 'Top 5 areas for Commercial leasing and why?';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-residential-leasing').addEventListener('click', async () => {
+ const query = 'Top 5 areas for Residential leasing and why?';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-example-marina').addEventListener('click', async () => {
+ const query = 'Give me the last 6 months rental price trend for Dubai Marina';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-example-top-areas').addEventListener('click', async () => {
+ const query = 'Which area is having more rental transactions?';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-example-villas').addEventListener('click', async () => {
+ const query = 'Show me villa transactions in Dubai Marina';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ document.getElementById('btn-example-3bhk').addEventListener('click', async () => {
+ const query = 'Avg price of 3 BHK apartment by area in last 6 months';
+ document.getElementById('queryInput').value = query;
+ await processQuery(query);
+ });
+
+ // Enter key support
+ document.getElementById('queryInput').addEventListener('keypress', async (e) => {
+ if (e.key === 'Enter') {
+ const query = e.target.value;
+ await processQuery(query);
+ }
+ });
+});
+
+
+
diff --git a/public/js/rents.js b/public/js/rents.js
new file mode 100644
index 0000000..a611527
--- /dev/null
+++ b/public/js/rents.js
@@ -0,0 +1,590 @@
+// Rents page logic moved to external file to satisfy CSP (no inline scripts or handlers)
+
+const API_BASE = '/api';
+
+// Area dropdown values for rents
+const areas = [
+ 'al mamzer', 'jabal ali first', 'al nahda second', 'burj khalifa',
+ 'al merkadh', 'dubai investment park second', 'al yelayiss 2',
+ 'dubai investment park first', 'al yelayiss 1', 'al jadaf',
+ 'al nahda first', 'al suq al kabeer', 'mirdif', 'business bay',
+ 'madinat hind 4', 'al thanyah third',
+ 'al qusais industrial fourth', 'al qusais industrial fifth',
+ 'al warqa first', 'muhaisanah fourth', 'al qusais second',
+ 'al karama', 'al hudaiba', 'madinat dubai almelaheyah',
+ 'al safouh second', 'al qusais first', 'wadi al amardi', 'al rega',
+ 'wadi al safa 5', 'marsa dubai',
+ 'hadaeq sheikh mohammed bin rashid', 'me\'aisem first',
+ 'ras al khor industrial third', 'al qusais industrial first',
+ 'al murqabat', 'wadi al safa 6', 'naif', 'nad al shiba third',
+ 'al saffa first', 'um suqaim third', 'al khairan first',
+ 'madinat al mataar', 'saih shuaib 2', 'mankhool',
+ 'al thanyah fifth', 'al hebiah fourth', 'trade center second',
+ 'al warsan first', 'nadd hessa', 'port saeed',
+ 'trade center first', 'al wasl', 'jumeirah first',
+ 'al goze industrial second', 'nad al shiba first', 'al muteena',
+ 'al barsha first', 'jabal ali industrial first', 'palm jumeirah',
+ 'al raffa', 'margham', 'al barshaa south third', 'al satwa',
+ 'al khawaneej second', 'al bada', 'al hamriya', 'al thanyah first',
+ 'al barsha south fourth', 'um hurair second', 'al baraha',
+ 'ras al khor industrial second', 'al goze industrial first',
+ 'al hebiah fifth', 'jumeirah second', 'al mararr',
+ 'wadi al safa 3', 'um suqaim second', 'hor al anz', 'al kheeran',
+ 'wadi al safa 2', 'hor al anz east', 'al manara', 'eyal nasser',
+ 'al yufrah 1', 'al khawaneej first', 'hatta', 'warsan fourth',
+ 'um ramool', 'al goze fourth', 'al goze third',
+ 'al goze industrial third', 'al barsha south fifth',
+ 'al hebiah sixth', 'saih shuaib 3', 'al goze first', 'abu hail',
+ 'al hebiah third', 'al hebiah second', 'rega al buteen',
+ 'al goze industrial fourth', 'nad al hamar', 'al jafliya',
+ 'al barshaa south second', 'al qusais industrial second',
+ 'oud metha', 'um suqaim first', 'jabal ali', 'jumeirah third',
+ 'lehbab second', 'al dhagaya', 'al lusaily', 'hessyan first',
+ 'al khabeesi', 'wadi al safa 7', 'al hebiah first',
+ 'al thanayah fourth', 'al ras', 'muhaisanah second',
+ 'muhaisanah first', 'al-cornich', 'al mizhar third',
+ 'al rashidiya', 'cornich deira', 'lehbab first', 'um al sheif',
+ 'al garhoud', 'al yufrah 2', 'al yufrah 3', 'al saffa second',
+ 'madinat hind 1', 'al sabkha', 'ras al khor industrial first',
+ 'al buteen', 'jabal ali industrial second', 'al safouh first',
+ 'al aweer first', 'al eyas', 'al mizhar first',
+ 'al qusais industrial third', 'um hurair first', 'al kifaf',
+ 'zaabeel first', 'al waheda', 'zaabeel second', 'ghadeer al tair',
+ 'al twar first', 'grayteesah', 'al warsan third', 'al ttay',
+ 'nad al shiba second', 'al barsha second', 'al athbah',
+ 'al warqa fourth'
+];
+
+// Projects list from rents
+const projects = [
+ 'starz tower by danube', 'azizi feirouz i',
+ 'candace acacia hotel apartments', 'burj al nujoom',
+ 'azizi riviera 35', 'shams townhouses', 'reem-mira community ph 1',
+ 'schon business park', 'damac hills (2) - albizia',
+ 'reem - mira oasis community', 'trident grand', 'collective 2.0',
+ 'lakeside', 'marina vista', 'madinat jumeriah living - phase 2',
+ 'creek beach - surf', 'creek beach - bayshore',
+ 'creek beach - sunset', 'creek beach - breeze',
+ 'creek beach - vida residences', 'creek beach - orchid',
+ 'creek beach - savanna-cedar-mangrove',
+ 'creek beach - canopy - moor', 'creek beach - summer',
+ 'creek beach - grove', 'creek beach - rosewater',
+ 'creek beach - lotus', 'azizi riviera 46', 'sahara meadows2',
+ 'palace beach residence', 'blue wave', 'new dubai gate1',
+ 'elite 4 sports residence', 'binghatti canal', 'azizi riviera 4',
+ 'liva', 'una', 'azizi park avenue', 'la residence 4 at the lotus',
+ 'damac towers by paramount', 'la residence 3 at the lotus',
+ 'damac hills (2) - viridis', 'vera tower', 'maple iii',
+ 'the lakes deema 2', 'harbour gate', 'luma22', 'sunset gardens b',
+ 'reem-mira community ph5', 'canal front residences cf1 & cf2',
+ 'westburry tower', 'arabian ranches lll - june', 'remraam',
+ 'remraam - al ramth 2', 'remraam - al ramth',
+ 'living legends phase 5', 'damac hills (2) - pacifica',
+ 'qpoint liwan-plot r054', 'qline (qpoint phaseii) 3c-lw-r-054',
+ 'hds business centre', 'jumeirah business centre 4',
+ 'gold crest views2', 'le grand chateau', 'the spirit',
+ 'the valley - orania', 'latifa tower',
+ 'serenia residences the palm', 'mag218', 'empire heights',
+ 'the fields at d11 - mbrmc', 'equiti arcade', 'al jawhara tower',
+ 'mudon views', 'sondos orchid', 'sky courts',
+ 'the pulse beachfront 2', 'damac hills - rochester',
+ 'glitz residence 3', 'burj vista', 'grand boulevard', 'sidra',
+ 'the regent', 'the valley - eden', 'collective', 'iris bay',
+ 'stallion tower', 'sondos zinnia', 'damac heights',
+ 'the polo residence',
+ 'mohammed bin rashid al maktoum city- district one, phase 1',
+ 'the grand', 'maple 2', 'al habtoor city', 'al habtoor tower',
+ 'ajmal sarah', 'the haven', 'elite ii sports residence',
+ 'arabian gate', 'hayat boulevard', 'prime gardens', 'coopet',
+ 'azizi riviera 41', 'the apricot', 'executive bay a',
+ 'executive bay b', 'elite 10 sports residence', 'mag 5 boulevard',
+ 'the court',
+ 'mohammed bin rashid al maktoum city-district one phase ii villas',
+ 'manhattan', 'panorama', 'badra phase 1',
+ 'mirdif hills- nasayem avenue', 'jannat', 'midtown - mesk',
+ 'midtown - afnan', 'midtown - dania', 'midtown - noor',
+ 'azizi riviera 20', 'trillionaire residences by binghatti',
+ 'boris becker business tower', 'rukan', 'opalz by danube',
+ 'oakley square residences', 'royale residence1', 'diamond views 3',
+ 'ag tower', 'the address dubai opera', 'burj park v', 'il primo',
+ 'grande', 'act one | act two', 'the mansion', 'burj park iii',
+ 'opera grand', 'the st. regis residences, downtown dubai',
+ 'm burj', 'the residence | burj khalifa', 'burj khalifa towers',
+ 'sharena residence 1', 'sherena residence', 'burj royale',
+ 'azizi riviera 33', 'the vybe', 'damac hills (2) - victoria 2',
+ 'emirates living - springs 2', 'arabella 2 - townhouses at mudon',
+ 'binghatti onyx', 'escan marina tower', 'fawad azizi residence',
+ 'azizi riviera 17', 'the hills', 'park ridge', 'peninsula one',
+ 'emirates garden 1 (lavender - gardenia - rose)',
+ 'port de la mer - la voile',
+ 'terhab hotel & towers at jumeirah village triangle', 'la rosa 3',
+ 'confident lancaster', 'elite 8 sports residence',
+ 'silicon gates 4', 'reem - mira oasis community phase 3',
+ 'port de la mer - la cote', 'atrium gold towers',
+ 'alduaa marina tower', 'jumeirah living marina gate',
+ 'urban oasis', 'd1', 'palazzo versace', 'azizi riviera 39',
+ 'fair view residency', 'regina', 'i - rise tower', 'time 1',
+ 'sobha hartland - the crest', '17 icon bay', 'rukan 3', 'it plaza',
+ 'dd stand point', 'axis silver', 'maha townhouses',
+ 'damac hills (2) - claret', 'qpoint liwan-plot r071',
+ 'no.17, no.18a,no.18b and no.19 citywalk residential',
+ 'elysee iii by pantheon', 'royale garden residence',
+ 'park heights i', 'azizi riviera 22', 'the pad', 'hockey tower',
+ 'saba tower 3', 'citadel tower', 'new dubai gate2',
+ 'azure residence', 'rasha 2', 'azizi riviera 67',
+ 'damac hills (2) - mimosa', 'mudon al ranim 2',
+ 'sobha hartland waves', 'emirates living - springs 7',
+ 'the bridge', 'sapphire residence', 'azizi riviera 63',
+ 'bay square', 'laguna tower', 'glitz residence 1',
+ 'axis residences 3', '15 northside', 'santevill', 'syann park1',
+ 'jadeel -madinat jumeirah living', 'qpoint liwan-plot r068',
+ 'jasmine lane', 'the onyx',
+ 'mohammed bin rashid al maktoum city , district one phase iii , residences 20',
+ 'rigel', 'elash', 'parkside', 'international city emarati',
+ 'donna towers', 'emirates living - springs 12', 'al furjan',
+ 'the crescent', 'gold crest executive', 'uniestate prime tower',
+ 'al khail heights', 'fairmont palm residence',
+ 'emirates living - springs 10', 'damac hills (2) - centaury',
+ 'upside living', 'lawnsi', 'tanaro', 'mag 22',
+ 'palace residences - dubai creek harbour', 'smart heights',
+ 'binghatti avenue', 'vincitore benessere', 'suberbia',
+ 'alef noon residence', 'damac hills (2) - amargo',
+ 'green community west- extension- phase iii', 'town central',
+ 'celestia', 'celia residence', 'merano tower', 'sami q tower',
+ 'living legends phase 3', 'town square zahra', 'dezire residences',
+ 'harbour residences', 'aykon city 2', 'aykon city', 'aykon city 3',
+ 'binghatti emerald', 'plaza residences', 'the lakes ghadeer',
+ 'the binary by omniyat', 'stadium point', 'b2b tower',
+ 'the pulse townhouses', 'elite residence', 'palma residence',
+ 'uniestate supreme residence', 'ghalia', 'the concourse',
+ 'farhad azizi residence', 'silver tower', 'marquis signature',
+ 'creekside 18', 'sandoval lane', 'miraclz tower', 'orion building',
+ 'mayfair residency', 'dunes village', 'the dubai creek residences',
+ 'asayel at madinat jumeirah living', 'ontario tower',
+ 'damac hills - carson', 'global golf residence 2', 'lake view',
+ 'damac hills (2) - avencia', 'azizi shaista residence',
+ 'jumeirah business centre 5', 'the haven residences',
+ 'avenue residence three', 'serena resedence',
+ 'lawnz residence by danube', 'azizi riviera 45', 'park islands',
+ 'villa lantana 1', 'the terraces', '1 residences', 'fahad 2',
+ 'au tower', 'prive by damac', 'the valley - talia', 'ac3', 'mudon',
+ 'marina residence', 'reem townhouses', 'orra marina',
+ 'ellington beach house', 'q - zone (qpoint - phase iii) mu007',
+ 'qpoint liwan- plot mu007', 'aura', 'binghatti tulip',
+ 'downtown views ii', 'mada\'in',
+ 'mohammed bin rashid al maktoum city , district one phase iii , residences 30',
+ 'matex tower', 'mag 318', 'mag eye phase 1', 'jewelz residence',
+ 'the ivy', 'palace estates', 'tiara residence', 'la vista 05',
+ 'reem - mira oasis community phase 2', 'mulberry at park heights',
+ 'greenview 3', 'royale residence2', 'binghatti venus',
+ 'green valley tower', 'ocean heights', 'one za\'abeel', 'maple',
+ 'westwood grande ii by imtiaz', 'grandeur residences',
+ 'creek edge', 'rawda apartments', 'azizi riviera 43',
+ 'silicon gate 1', 'emirates living - springs 5',
+ 'azizi riviera 61', 'la riviera azure', 'lake central',
+ 'the matrix', 'lagoon views at district one',
+ 'emirates living - springs 11', 'the pulse boulevard apartments',
+ 'dubai marina mall', 'reva residences',
+ 'jumeirah business centre 6', 'me do re', 'signature livings',
+ 'vida residence downtown dubai', 'windsor manor', 'amaranta 2',
+ 'madina tower', 'creek rise', 'azizi riviera 27',
+ 'jumeirah business centre2', 'urbana ii', 'avenue residence 2',
+ 'damac hills (2) - coursetia', 'town square jenna and warda',
+ 'arabian ranches iii - sun', 'azizi riviera 3', 'harbour views',
+ 'the signature', 'centrium tower', 'binghatti corner',
+ 'premiers twin tower', 'mag 5', 'bloom towers', 'the point',
+ 'continental tower', 'clayton residency', 'global green view ii',
+ 'binghatti orchid', 'dunes tower', 'reef residence',
+ 'azizi riviera 25', 'hera tower', 'lakeside residence',
+ 'manam prime', 'trafalgar central', 'elite sports residence',
+ 'dd boulevard central', 'the pulse residence', 'zada tower',
+ 'blue bay', 'warsan village - b & c',
+ 'mohammad aqil ali & ahmad ali alzarooni', 'peninsula five',
+ 'sulafa tower', 'the concourse 2', 'tower 108', 'cadi4',
+ 'dusk by binghatti', 'ariyana', 'zumurud dubai marina',
+ 'joya verde residences dubai', 'wilton terraces',
+ 'avenue residence1', 'elite 6 sports residence',
+ 'diamond views 1- villas b', 'myka residence',
+ 'vida residences dubai marina', 'hamilton residency',
+ 'madison residency', 'the royal atlantis,resort and residences',
+ 'ariana park', 'belgravia iii', 'j&g plexs', 'alandalus townhouse',
+ 'golf ville', 'majestine', 'creek palace', 'jumeirah gate',
+ 'silicon avenue', 'noor townhouses', 'golf tower',
+ 'damac hills (2) - amazonia',
+ 'the address residence fountain views ii',
+ 'the address residence fountain views iii', 'boulevard point',
+ 'vida dubai mall', 'the address residence-fountain views',
+ 'luxury family residence ii', 'iris crystal', 'montrose', 'mirar',
+ '29 boulevard', 'azizi riviera 12', 'azizi samia residence',
+ 'dm marina plaza', 'golf views', 'la visita 02', 'the autograph',
+ 'canal views', 'bloom heights', 'la rive', 'la vista 04',
+ 'rukan residence', 'qpoint liwan-plot mu002', 'lake terrace',
+ 'damac hills (2) - avencia-2', 'burj crown',
+ 'uniestate sports tower', 'cappadocia', 'riah towers',
+ 'yaass tower', 'townhouses at jumeirah islands',
+ 'damac hills (2) - aquilegia',
+ 'mohammed bin rashid al maktoum city , district one phase iii , residences 24',
+ 'oberoi centre', 'elite 5 sports residence',
+ 'the sterling east house', 'sol avenue', 'forte',
+ 'arabian ranches iii - spring', 'daytona house', 'the palm tower',
+ 'hamza tower', 'oxford boulevard', 'the pulse- beachfront',
+ 'sobha creek vistas reserve', 'pearl house i by imtiaz',
+ 'silicon information technology tower', 'binghatti mirage',
+ 'al ferdows', 'damac hills (2) - victoria', 'torch tower',
+ 'azizi riviera 11',
+ 'no.20, no.21a, no.21b and no.22 citywalk residential',
+ 'las casas',
+ 'mohammed bin rashid al maktoum city , district one phase iii , residences 1',
+ 'azizi riviera 9', 'burlington tower',
+ 'damac hills - golf promenade', 'al waleed garden 2',
+ 'residences du port, dubai marina, autograph collection residences',
+ 'damac hills - brookfield-3', 'damac hills - artesia',
+ 'qpoint liwan-plot r010', 'qline (qpoint phase ii) r010',
+ 'sobha hartland waves grande', 'al andalus phase 2',
+ 'alandalus building e', 'alandalus building g', 'alandalus',
+ 'alandalus building d', 'champions tower1', 'park lane tower',
+ 'the paragon by igo', 'vancouver', 'sidra tower', 'elan',
+ 'mohammed bin rashid al maktoum city , district one phase iii , residences 18',
+ 'solitaire cascades', 'bay central west & central towers',
+ 'q-zone (qpoint - phase iii), mu005&6', 'qpoint liwan-plot mu005',
+ 'qpointliwan-plot mu006', 'damac hills (2) - aster',
+ 'qpoint liwan-plot r055',
+ 'no.1, no.2a, no.2b, no.3a and 3b citywalk residential',
+ 'marquise square tower', 'silver star tower',
+ 'emirates living - springs 3',
+ 'mohammed bin rashid al maktoum city , district one phase iii , residences 13',
+ 'churchill tower', 'park horizon', 'binghatti crescent',
+ 'la rosa 5', 'sobha hartland one park avenue', 'la violeta 2',
+ 'villanova amaranta 4', 'beach vista', 'spica', 'water\'s edge',
+ 'beach isle', 'park field', 'azizi riviera 13', 'liv marina',
+ 'crystal tower', 'diamond views 4', 'gardenia 3', 'aura by grovy',
+ 'mbl residence', 'cayan tower', 'claren 1', 'equiti apartments',
+ 'la-riviera apartments', 'arabian & spainsh tower (phase 2)',
+ 'canal residence west 2', 'elite business bay residence',
+ 'palladium', 'the vortex tower', 'the valley-nara',
+ 'belgravia heights i', 'creek crescent', 'jumeirah park 7b&c',
+ 'condor classic', 'samana golf avenue', 'ellington house',
+ 'arabian ranches lll - caya', 'safi townhouses', 'the wings',
+ 'olivz residence', 'al jawzaa', 'prive residence',
+ 'damac hills (2) - zinnia', 'town square hayat',
+ 'the opus by omniyat', 'maria tower', 'naseem townhouses',
+ 'downtown views', 'blvd heights', 'sol bay',
+ 'damac hills - golf vista', 'urbana iii', 'indigo tower',
+ 'azizi riviera 23', 'the pulse beachfront 3', 'apex atrium',
+ 'creek gate', 'azizi riviera 7', 'concept 7 residences',
+ 'silicon heights', 'the pulse residence park',
+ 'platinum by vision', 'paramount tower hotel & residences',
+ 'azizi riviera 2', 'tenora', 'binghatti creek',
+ 'villanova amaranta 3', 'armada towers', 'town square safi',
+ 'crystal residence', 'harmony', 'oia residence',
+ 'casa flores and eden apartments', 'azizi riviera 14',
+ 'clover bay', 'binghatti heights', 'gardenia livings',
+ 'damac hills-park residences 1', 'arabian ranches iii - joy',
+ 'silverene', 'elite downtown residence', 'profile residence',
+ 'the anwa by omniyat', 'prime tower', 'damac hills (2) - vardon',
+ 'shamal waves', 'tiffany towers', 'topaz residences 2',
+ 'qpoint liwan-plot r069', 'azizi riviera 26', 'pearls by vision',
+ 'azizi riviera 47', 'azizi riviera 42', 'azizi gardens',
+ 'imperial', 'reem-mira community ph 4', 'azizi aura residences',
+ 'prime business avenue',
+ 'lincoln park(west side & lincoln park - b)',
+ 'mohammed bin rashid al maktoum city district one- c villas',
+ 'wavez residence', '51@business bay', 'arabian ranches lll - ruba',
+ 'living legends phase 7', 'elz residence', 'burj views'
+];
+
+function populateAreas() {
+ const areaSelect = document.getElementById('area_name');
+ areas.forEach(area => {
+ const option = document.createElement('option');
+ option.value = area;
+ option.textContent = area.charAt(0).toUpperCase() + area.slice(1).replace(/\b\w/g, l => l.toUpperCase());
+ areaSelect.appendChild(option);
+ });
+}
+
+function loadProjects() {
+ const projectSelect = document.getElementById('project');
+ const sortedProjects = projects.slice().sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
+ sortedProjects.forEach(project => {
+ const option = document.createElement('option');
+ option.value = project;
+ option.textContent = project.length > 70 ? project.substring(0, 70) + '...' : project;
+ projectSelect.appendChild(option);
+ });
+}
+
+function showLoading(show) {
+ document.getElementById('loading').style.display = show ? 'block' : 'none';
+}
+
+function showError(message) {
+ const errorDiv = document.getElementById('error');
+ errorDiv.textContent = message;
+ errorDiv.style.display = 'block';
+}
+
+function hideError() {
+ document.getElementById('error').style.display = 'none';
+}
+
+function showResults() {
+ document.getElementById('resultsSection').style.display = 'block';
+}
+
+function hideResults() {
+ document.getElementById('resultsSection').style.display = 'none';
+}
+
+function formatDate(dateString) {
+ if (!dateString) return 'N/A';
+ const date = new Date(dateString);
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
+}
+
+function formatNumber(num, decimals = 0) {
+ if (num === null || num === undefined) return 'N/A';
+ return parseFloat(num).toLocaleString('en-US', { maximumFractionDigits: decimals });
+}
+
+function formatCurrency(amount) {
+ if (!amount) return 'N/A';
+ return 'AED ' + parseFloat(amount).toLocaleString('en-US', { maximumFractionDigits: 0 });
+}
+
+let currentPage = 1;
+let lastPaging = { total: 0, page: null, page_size: 30, total_pages: 1, has_next: false, has_prev: false };
+
+async function searchRents(pageOverride) {
+ const formData = new FormData(document.getElementById('filtersForm'));
+ const params = new URLSearchParams();
+
+ const area_name = formData.get('area_name');
+ const property_type = formData.get('property_type');
+ const size_min = formData.get('size_min');
+ const size_max = formData.get('size_max');
+ const rooms = formData.get('rooms');
+ const project = formData.get('project');
+ const pageSize = formData.get('page_size') || '30';
+ const page = pageOverride != null ? pageOverride : (formData.get('page') || currentPage || 1);
+
+ if (area_name) params.append('area_name', area_name);
+ if (property_type && property_type !== 'all') params.append('property_type', property_type);
+ if (size_min && !isNaN(parseFloat(size_min))) params.append('size_min', size_min);
+ if (size_max && !isNaN(parseFloat(size_max))) params.append('size_max', size_max);
+ if (rooms && rooms !== 'all') params.append('rooms', rooms);
+ if (project && project !== 'all') params.append('project', project);
+ // pagination params
+ if (page) params.append('page', page);
+ if (pageSize) params.append('page_size', pageSize);
+
+ showLoading(true);
+ hideError();
+ hideResults();
+
+ try {
+ const response = await fetch(`${API_BASE}/rents/recent?${params.toString()}`);
+ const data = await response.json();
+ if (data.success) {
+ currentPage = data.data.page || 1;
+ lastPaging = {
+ total: data.data.total ?? data.data.count,
+ page: data.data.page,
+ page_size: data.data.page_size ?? parseInt(pageSize,10),
+ total_pages: data.data.total_pages ?? 1,
+ has_next: !!data.data.has_next,
+ has_prev: !!data.data.has_prev
+ };
+ displayResults(data.data);
+ } else {
+ showError(data.message || 'Failed to fetch rents');
+ }
+ } catch (error) {
+ showError('Network error: ' + error.message);
+ } finally {
+ showLoading(false);
+ }
+}
+
+function displayResults(data) {
+ const { rents, count, total, page, page_size, total_pages, has_next, has_prev } = data;
+ const tbody = document.getElementById('rentsBody');
+ const resultsCount = document.getElementById('resultsCount');
+ const showing = count;
+ const grandTotal = total ?? count;
+ resultsCount.textContent = `Showing ${showing} of ${grandTotal} rents`;
+ const pageInfo = document.getElementById('pageInfo');
+ if (pageInfo) {
+ if (page && total_pages) {
+ pageInfo.textContent = `Page ${page} of ${total_pages}`;
+ } else {
+ pageInfo.textContent = '';
+ }
+ }
+ const prevBtn = document.getElementById('prevPage');
+ const nextBtn = document.getElementById('nextPage');
+ if (prevBtn) prevBtn.disabled = !(has_prev);
+ if (nextBtn) nextBtn.disabled = !(has_next);
+
+ if (rents.length === 0) {
+ const colCount = document.querySelectorAll('#rentsTable thead th').length;
+ tbody.innerHTML = (
+ '' +
+ `| ` +
+ '' +
+ ' No rents found matching your filters ' +
+ ' | ' +
+ '
'
+ );
+ showResults();
+ return;
+ }
+
+ tbody.innerHTML = rents.map(r => (
+ '' +
+ `| ${r.rent_id ?? 'N/A'} | ` +
+ `${formatDate(r.registration_date)} | ` +
+ `${formatDate(r.start_date)} | ` +
+ `${formatDate(r.end_date)} | ` +
+ `${r.version_en ?? 'N/A'} | ` +
+ `${r.area_en ?? 'N/A'} | ` +
+ `${formatCurrency(r.contract_amount)} | ` +
+ `${formatCurrency(r.annual_amount)} | ` +
+ `${r.is_free_hold_en ?? 'N/A'} | ` +
+ `${formatNumber(r.actual_area)} | ` +
+ `${r.prop_type_en ?? 'N/A'} | ` +
+ `${r.prop_sub_type_en ?? 'N/A'} | ` +
+ `${r.rooms != null ? formatNumber(r.rooms, 1) : 'N/A'} | ` +
+ `${r.usage_en ?? 'N/A'} | ` +
+ `${r.nearest_metro_en ?? 'N/A'} | ` +
+ `${r.nearest_mall_en ?? 'N/A'} | ` +
+ `${r.nearest_landmark_en ?? 'N/A'} | ` +
+ `${r.parking != null ? formatNumber(r.parking, 1) : 'N/A'} | ` +
+ `${r.total_properties ?? '0'} | ` +
+ `${r.master_project_en ?? 'N/A'} | ` +
+ `${r.project_en ?? 'N/A'} | ` +
+ `${formatDate(r.created_at)} | ` +
+ `${formatDate(r.updated_at)} | ` +
+ '
'
+ )).join('');
+
+ showResults();
+}
+
+function resetFilters() {
+ document.getElementById('filtersForm').reset();
+ document.getElementById('size_min').value = '';
+ document.getElementById('size_max').value = '';
+ document.getElementById('sizeValue').textContent = 'All Sizes';
+ hideResults();
+ hideError();
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ populateAreas();
+ loadProjects();
+
+ const sizeMinHidden = document.getElementById('size_min');
+ const sizeMaxHidden = document.getElementById('size_max');
+ const sizeMinRange = document.getElementById('size_min_range');
+ const sizeMaxRange = document.getElementById('size_max_range');
+ const sizeFill = document.getElementById('sizeFill');
+ const sizeValue = document.getElementById('sizeValue');
+ const MIN_VAL = parseFloat(sizeMinRange.min);
+ const MAX_VAL = parseFloat(sizeMinRange.max);
+ const STEP = parseFloat(sizeMinRange.step) || 100;
+
+ function updateSizeLabel() {
+ const min = parseFloat(sizeMinRange.value);
+ const max = parseFloat(sizeMaxRange.value);
+ const hasMin = !isNaN(min);
+ const hasMax = !isNaN(max);
+ if (!hasMin && !hasMax) {
+ sizeValue.textContent = 'All Sizes';
+ } else if (hasMin && hasMax) {
+ sizeValue.textContent = `${min.toLocaleString('en-US')} - ${max.toLocaleString('en-US')} sq.ft`;
+ } else if (hasMin) {
+ sizeValue.textContent = `≥ ${min.toLocaleString('en-US')} sq.ft`;
+ } else {
+ sizeValue.textContent = `≤ ${max.toLocaleString('en-US')} sq.ft`;
+ }
+ // Persist only when different from full range
+ sizeMinHidden.value = (min > MIN_VAL) ? min : '';
+ sizeMaxHidden.value = (max < MAX_VAL) ? max : '';
+ // Update fill track
+ const left = ((Math.max(MIN_VAL, Math.min(min, max)) - MIN_VAL) / (MAX_VAL - MIN_VAL)) * 100;
+ const right = ((Math.max(MIN_VAL, Math.max(min, max)) - MIN_VAL) / (MAX_VAL - MIN_VAL)) * 100;
+ sizeFill.style.left = `${left}%`;
+ sizeFill.style.width = `${Math.max(0, right - left)}%`;
+ }
+
+ function clampRanges() {
+ if (parseFloat(sizeMinRange.value) > parseFloat(sizeMaxRange.value) - STEP) {
+ sizeMinRange.value = (parseFloat(sizeMaxRange.value) - STEP).toString();
+ }
+ if (parseFloat(sizeMaxRange.value) < parseFloat(sizeMinRange.value) + STEP) {
+ sizeMaxRange.value = (parseFloat(sizeMinRange.value) + STEP).toString();
+ }
+ }
+ sizeMinRange.addEventListener('input', () => { clampRanges(); updateSizeLabel(); });
+ sizeMaxRange.addEventListener('input', () => { clampRanges(); updateSizeLabel(); });
+
+ // Ensure both handles are draggable when overlapping by toggling z-index on interaction
+ function bringMinToFront() {
+ sizeMinRange.style.zIndex = '6';
+ sizeMaxRange.style.zIndex = '5';
+ }
+ function bringMaxToFront() {
+ sizeMinRange.style.zIndex = '5';
+ sizeMaxRange.style.zIndex = '6';
+ }
+ ['mousedown','pointerdown','touchstart'].forEach(evt => {
+ sizeMinRange.addEventListener(evt, bringMinToFront, { passive: true });
+ sizeMaxRange.addEventListener(evt, bringMaxToFront, { passive: true });
+ });
+ // Initialize
+ sizeMinRange.value = MIN_VAL;
+ sizeMaxRange.value = MAX_VAL;
+ updateSizeLabel();
+
+ document.getElementById('filtersForm').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ currentPage = 1;
+ const pageInput = document.getElementById('page');
+ if (pageInput) pageInput.value = '1';
+ await searchRents(1);
+ });
+
+ const resetBtn = document.getElementById('resetBtn');
+ if (resetBtn) {
+ resetBtn.addEventListener('click', resetFilters);
+ }
+
+ // pagination buttons
+ const prevBtn = document.getElementById('prevPage');
+ const nextBtn = document.getElementById('nextPage');
+ if (prevBtn) {
+ prevBtn.addEventListener('click', async () => {
+ if (lastPaging.has_prev && currentPage > 1) {
+ currentPage -= 1;
+ const pageInput2 = document.getElementById('page');
+ if (pageInput2) pageInput2.value = String(currentPage);
+ await searchRents(currentPage);
+ }
+ });
+ }
+ if (nextBtn) {
+ nextBtn.addEventListener('click', async () => {
+ if (lastPaging.has_next) {
+ currentPage += 1;
+ const pageInput3 = document.getElementById('page');
+ if (pageInput3) pageInput3.value = String(currentPage);
+ await searchRents(currentPage);
+ }
+ });
+ }
+});
+
+
diff --git a/public/rents.html b/public/rents.html
new file mode 100644
index 0000000..ef3646f
--- /dev/null
+++ b/public/rents.html
@@ -0,0 +1,501 @@
+
+
+
+
+
+ Recent Rents - Dubai DLD Analytics
+
+
+
+
+
+
+
+
+
+ 🔍 Fetching rents...
+
+
+
+
+
+
+
+
+
+
+ | rent_id |
+ registration_date |
+ start_date |
+ end_date |
+ version_en |
+ area_en |
+ contract_amount |
+ annual_amount |
+ is_free_hold_en |
+ actual_area |
+ prop_type_en |
+ prop_sub_type_en |
+ rooms |
+ usage_en |
+ nearest_metro_en |
+ nearest_mall_en |
+ nearest_landmark_en |
+ parking |
+ total_properties |
+ master_project_en |
+ project_en |
+ created_at |
+ updated_at |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setup.sh b/setup.sh
old mode 100755
new mode 100644
diff --git a/setup_docker.sh b/setup_docker.sh
old mode 100755
new mode 100644
diff --git a/src/app.js b/src/app.js
index ff53073..5800011 100644
--- a/src/app.js
+++ b/src/app.js
@@ -12,7 +12,22 @@ const app = express();
const PORT = process.env.PORT || 3000;
// Security middleware
-app.use(helmet());
+app.use(helmet({
+ contentSecurityPolicy: {
+ directives: {
+ defaultSrc: ["'self'"],
+ scriptSrc: ["'self'", "'unsafe-inline'"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ imgSrc: ["'self'", "data:", "https:"],
+ connectSrc: ["'self'"],
+ fontSrc: ["'self'", "https:", "data:"],
+ objectSrc: ["'none'"],
+ mediaSrc: ["'self'"],
+ frameSrc: ["'none'"],
+ },
+ },
+ crossOriginEmbedderPolicy: false
+}));
app.use(cors());
// Rate limiting
diff --git a/src/routes/api.js b/src/routes/api.js
index 97e79da..c59b805 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -224,7 +224,7 @@ router.get('/database/info', async (req, res) => {
for (const table of tables) {
try {
- const [countResult] = await database.query(`SELECT COUNT(*) as count FROM ${table}`);
+ const countResult = await database.query(`SELECT COUNT(*) as count FROM ${table}`);
info[table] = {
count: countResult[0].count,
status: 'available'
@@ -465,4 +465,144 @@ router.get('/transactions/recent', async (req, res) => {
}
});
+// Recent rents endpoint with filters
+router.get('/rents/recent', async (req, res) => {
+ try {
+ const {
+ area_name,
+ property_type,
+ size_min,
+ size_max,
+ rooms,
+ project,
+ page,
+ page_size,
+ limit
+ } = req.query;
+
+ // Validate limit
+ let limitNum = undefined;
+ if (limit !== undefined && limit !== null && `${limit}`.trim() !== '') {
+ const parsed = parseInt(limit, 10);
+ if (isNaN(parsed) || parsed < 1) {
+ return res.status(400).json({
+ success: false,
+ message: 'Limit must be a positive integer when provided'
+ });
+ }
+ limitNum = parsed;
+ }
+
+ // Build WHERE clause
+ let baseWhere = ' WHERE 1=1';
+ const params = [];
+
+ // Area filter (case-insensitive)
+ if (area_name && area_name.trim() !== '') {
+ baseWhere += ' AND LOWER(area_en) LIKE ?';
+ params.push(`%${area_name.toLowerCase().trim()}%`);
+ }
+
+ // Property type filter
+ if (property_type && property_type.trim() !== '' && property_type !== 'all') {
+ baseWhere += ' AND (LOWER(prop_type_en) LIKE ? OR LOWER(prop_sub_type_en) LIKE ?)';
+ const propType = `%${property_type.toLowerCase().trim()}%`;
+ params.push(propType, propType);
+ }
+
+ // Actual area filtering (min/max)
+ const minNum = size_min !== undefined ? parseFloat(size_min) : undefined;
+ const maxNum = size_max !== undefined ? parseFloat(size_max) : undefined;
+ const hasMin = minNum !== undefined && !isNaN(minNum);
+ const hasMax = maxNum !== undefined && !isNaN(maxNum);
+ if (hasMin && hasMax) {
+ baseWhere += ' AND actual_area BETWEEN ? AND ?';
+ params.push(minNum, maxNum);
+ } else if (hasMin) {
+ baseWhere += ' AND actual_area >= ?';
+ params.push(minNum);
+ } else if (hasMax) {
+ baseWhere += ' AND actual_area <= ?';
+ params.push(maxNum);
+ }
+
+ // Rooms filter (decimal values: 1.0, 2.0, etc.)
+ if (rooms && rooms !== 'all' && rooms !== '') {
+ const roomsNum = parseFloat(rooms);
+ if (!isNaN(roomsNum)) {
+ baseWhere += ' AND rooms = ?';
+ params.push(roomsNum);
+ }
+ }
+
+ // Project filter
+ if (project && project.trim() !== '' && project !== 'all') {
+ baseWhere += ' AND (LOWER(project_en) LIKE ? OR LOWER(master_project_en) LIKE ?)';
+ const projectName = `%${project.toLowerCase().trim()}%`;
+ params.push(projectName, projectName);
+ }
+
+ // Pagination logic
+ const wantsPagination = (page !== undefined) || (page_size !== undefined) || (limitNum !== undefined);
+ const defaultPage = 1;
+ const defaultPageSize = 30;
+ const pageNum = (() => { const n = parseInt(page ?? defaultPage, 10); return isNaN(n) || n < 1 ? defaultPage : n; })();
+ const pageSizeNum = (() => { const n = parseInt((page_size ?? (limitNum !== undefined ? limitNum : defaultPageSize)), 10); return isNaN(n) || n < 1 ? defaultPageSize : n; })();
+ const offsetNum = (pageNum - 1) * pageSizeNum;
+
+ const orderBy = ' ORDER BY registration_date DESC, rent_id DESC';
+ let dataSql;
+ let total = null;
+
+ if (!wantsPagination) {
+ dataSql = `SELECT * FROM rents ${baseWhere} ${orderBy}`;
+ } else if (limitNum !== undefined && page === undefined && page_size === undefined) {
+ dataSql = `SELECT * FROM rents ${baseWhere} ${orderBy} LIMIT ${limitNum}`;
+ } else {
+ const countSql = `SELECT COUNT(*) AS total FROM rents ${baseWhere}`;
+ const countRows = await database.query(countSql, params);
+ total = countRows && countRows[0] ? countRows[0].total : 0;
+ dataSql = `SELECT * FROM rents ${baseWhere} ${orderBy} LIMIT ${pageSizeNum} OFFSET ${offsetNum}`;
+ }
+
+ console.log(`🔍 Fetching recent rents with filters:`, {
+ area_name, property_type, size_min, size_max, rooms, project, limit: limitNum, page, page_size
+ });
+
+ const rents = await database.query(dataSql, params);
+ const totalPages = (total !== null) ? Math.max(1, Math.ceil(total / pageSizeNum)) : 1;
+
+ res.json({
+ success: true,
+ data: {
+ rents: rents,
+ count: rents.length,
+ total: total !== null ? total : rents.length,
+ page: total !== null ? pageNum : null,
+ page_size: total !== null ? pageSizeNum : (limitNum !== undefined ? limitNum : null),
+ total_pages: total !== null ? totalPages : 1,
+ has_next: total !== null ? pageNum < totalPages : false,
+ has_prev: total !== null ? pageNum > 1 : false,
+ filters: {
+ area_name: area_name || null,
+ property_type: property_type || null,
+ rooms: rooms || null,
+ project: project || null,
+ limit: limitNum ?? null,
+ page: total !== null ? pageNum : null,
+ page_size: total !== null ? pageSizeNum : (limitNum !== undefined ? limitNum : null)
+ }
+ }
+ });
+
+ } catch (error) {
+ console.error('❌ Rents query error:', error);
+ res.status(500).json({
+ success: false,
+ message: 'Failed to retrieve rents',
+ error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error'
+ });
+ }
+});
+
module.exports = router;
diff --git a/src/routes/static.js b/src/routes/static.js
index a1d53c2..d177673 100644
--- a/src/routes/static.js
+++ b/src/routes/static.js
@@ -16,5 +16,10 @@ router.get('/transactions', (req, res) => {
res.sendFile(path.join(__dirname, '../../public/transactions.html'));
});
+// Serve rents.html
+router.get('/rents', (req, res) => {
+ res.sendFile(path.join(__dirname, '../../public/rents.html'));
+});
+
module.exports = router;
diff --git a/src/services/chartFormatter.js b/src/services/chartFormatter.js
index 8da6b2d..db5a84c 100644
--- a/src/services/chartFormatter.js
+++ b/src/services/chartFormatter.js
@@ -507,28 +507,54 @@ class ChartFormatter {
// Original card logic for other queries
// Calculate summary statistics
- const prices = data.map(row => row.avg_price || row.avg_value || 0).filter(p => p > 0);
- const counts = data.map(row => row.transaction_count || row.count || 0);
+ // Filter out NULL, NaN, and invalid values
+ const prices = data
+ .map(row => {
+ const price = row.avg_price || row.avg_value;
+ // Check if price is a valid number
+ if (price === null || price === undefined || isNaN(price) || price <= 0) {
+ return null;
+ }
+ return Number(price);
+ })
+ .filter(p => p !== null && !isNaN(p) && p > 0);
+ const counts = data.map(row => {
+ const count = row.transaction_count ||
+ row.count ||
+ row.rental_count ||
+ row.commercial_count ||
+ row.residential_count ||
+ row.total_count ||
+ 0;
+ return Number(count) || 0;
+ });
+
+ // Only create price cards if we have valid prices
if (prices.length > 0) {
const avgPrice = prices.reduce((sum, price) => sum + price, 0) / prices.length;
const maxPrice = Math.max(...prices);
const minPrice = Math.min(...prices);
- cards.push({
- title: 'Average Price',
- value: this.formatCurrency(avgPrice),
- subtitle: parsedQuery.areas && parsedQuery.areas.length > 0 ? parsedQuery.areas.join(', ') : 'All Areas',
- trend: this.calculateTrend(prices),
- icon: 'trending-up'
- });
+ // Validate that averages are valid numbers
+ if (!isNaN(avgPrice) && isFinite(avgPrice) && avgPrice > 0) {
+ cards.push({
+ title: 'Average Price',
+ value: this.formatCurrency(avgPrice),
+ subtitle: parsedQuery.areas && parsedQuery.areas.length > 0 ? parsedQuery.areas.join(', ') : 'All Areas',
+ trend: this.calculateTrend(prices),
+ icon: 'trending-up'
+ });
- cards.push({
- title: 'Price Range',
- value: `${this.formatCurrency(minPrice)} - ${this.formatCurrency(maxPrice)}`,
- subtitle: 'Min to Max',
- icon: 'range'
- });
+ if (!isNaN(minPrice) && !isNaN(maxPrice) && isFinite(minPrice) && isFinite(maxPrice)) {
+ cards.push({
+ title: 'Price Range',
+ value: `${this.formatCurrency(minPrice)} - ${this.formatCurrency(maxPrice)}`,
+ subtitle: 'Min to Max',
+ icon: 'range'
+ });
+ }
+ }
}
if (counts.length > 0) {
diff --git a/src/services/hybridQueryGenerator.js b/src/services/hybridQueryGenerator.js
index 7aeaebf..6ec7b26 100644
--- a/src/services/hybridQueryGenerator.js
+++ b/src/services/hybridQueryGenerator.js
@@ -45,6 +45,8 @@ class HybridQueryGenerator {
FROM ${dataSource.table}
WHERE ${dataSource.areaColumn} = ?
AND ${dataSource.dateColumn} >= ?
+ AND ${dataSource.priceColumn} IS NOT NULL
+ AND ${dataSource.priceColumn} > 0
`;
const params = [areas[0], time_period.startDate];
@@ -81,6 +83,8 @@ class HybridQueryGenerator {
WHERE ${dataSource.areaColumn} = ?
AND ${dataSource.dateColumn} >= ?
AND usage_en = 'residential'
+ AND ${dataSource.priceColumn} IS NOT NULL
+ AND ${dataSource.priceColumn} > 0
`;
const params = [areas[0], time_period.startDate];
@@ -140,6 +144,8 @@ class HybridQueryGenerator {
AVG(actual_area) as avg_area
FROM rents
WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const params = [];
diff --git a/src/services/queryTemplates.js b/src/services/queryTemplates.js
index b83bcd8..21c3fe4 100644
--- a/src/services/queryTemplates.js
+++ b/src/services/queryTemplates.js
@@ -17,6 +17,8 @@ class QueryTemplates {
FROM rents
WHERE area_en = ?
AND start_date >= ?
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
${params.property_filter ? 'AND prop_sub_type_en = ?' : ''}
GROUP BY DATE_FORMAT(start_date, '%Y-%m')
ORDER BY month
@@ -36,6 +38,8 @@ class QueryTemplates {
FROM rents
WHERE area_en = ?
AND start_date >= ?
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
${params.property_filter ? 'AND prop_sub_type_en = ?' : ''}
GROUP BY DATE_FORMAT(start_date, '%Y-%u')
ORDER BY week
@@ -249,6 +253,8 @@ class QueryTemplates {
SUM(annual_amount) as total_rental_value
FROM rents
WHERE start_date >= ?
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
GROUP BY area_en
ORDER BY rental_count DESC
LIMIT 10
@@ -270,6 +276,8 @@ class QueryTemplates {
FROM rents
WHERE usage_en = 'commercial'
AND start_date >= ?
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
GROUP BY area_en
ORDER BY commercial_count DESC, avg_commercial_rent DESC
LIMIT ${limit}
@@ -292,6 +300,8 @@ class QueryTemplates {
FROM rents
WHERE usage_en = 'residential'
AND start_date >= ?
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
GROUP BY area_en
ORDER BY residential_count DESC, avg_residential_rent DESC
LIMIT ${limit}
@@ -315,6 +325,8 @@ class QueryTemplates {
AND (prop_sub_type_en = 'flat' OR prop_sub_type_en = 'villa')
AND start_date >= ?
AND area_en IS NOT NULL
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
GROUP BY month, area_en
HAVING transaction_count >= 1
),
@@ -339,6 +351,66 @@ class QueryTemplates {
params: [params.roomType, params.startDate]
}),
+ // Filtered property queries (transactions with area + property type)
+ filtered_transactions: (params) => ({
+ sql: `
+ SELECT
+ area_en,
+ prop_type_en,
+ prop_sb_type_en,
+ COUNT(*) as transaction_count,
+ AVG(trans_value) as avg_price,
+ AVG(actual_area) as avg_area,
+ SUM(trans_value) as total_value
+ FROM transactions
+ WHERE 1=1
+ AND trans_value IS NOT NULL
+ AND trans_value > 0
+ ${params.area ? 'AND area_en = ?' : ''}
+ ${params.property_filter ? 'AND (prop_type_en = ? OR prop_sb_type_en = ?)' : ''}
+ GROUP BY area_en, prop_type_en, prop_sb_type_en
+ ORDER BY transaction_count DESC
+ LIMIT 50
+ `,
+ params: (() => {
+ const p = [];
+ if (params.area) p.push(params.area);
+ if (params.property_filter) {
+ p.push(params.property_filter);
+ p.push(params.property_filter);
+ }
+ return p;
+ })()
+ }),
+
+ // Filtered rental queries (rents with area + property type)
+ filtered_rentals: (params) => ({
+ sql: `
+ SELECT
+ area_en,
+ prop_sub_type_en,
+ COUNT(*) as rental_count,
+ AVG(annual_amount) as avg_price,
+ AVG(actual_area) as avg_area,
+ SUM(annual_amount) as total_value
+ FROM rents
+ WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
+ ${params.area ? 'AND area_en = ?' : ''}
+ ${params.property_filter ? 'AND prop_sub_type_en = ?' : ''}
+ GROUP BY area_en, prop_sub_type_en
+ ORDER BY rental_count DESC
+ LIMIT 50
+ `,
+ params: (() => {
+ const p = [];
+ if (params.area) p.push(params.area);
+ if (params.property_filter) p.push(params.property_filter);
+ return p;
+ })()
+ }),
+
// Context-aware query with refinements
context_aware_query: (params) => {
let sql = `
@@ -359,6 +431,8 @@ class QueryTemplates {
FROM rents
WHERE area_en = ?
AND start_date >= ?
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const sqlParams = [params.area, params.startDate];
@@ -471,6 +545,27 @@ class QueryTemplates {
return 'residential_leasing_areas';
}
+ // Filter queries - transactions with area and/or property type
+ if (intent === 'filter') {
+ // Check if it's about rentals or transactions
+ if (query.includes('rental') || query.includes('rent')) {
+ return 'filtered_rentals';
+ }
+ // Default to transactions for filter queries
+ return 'filtered_transactions';
+ }
+
+ // Queries with property types and areas but no specific intent
+ if (parsedQuery.property_types && parsedQuery.property_types.length > 0 &&
+ (parsedQuery.areas && parsedQuery.areas.length > 0 || query.includes('transaction'))) {
+ // Check if it's about rentals
+ if (query.includes('rental') || query.includes('rent')) {
+ return 'filtered_rentals';
+ }
+ // Default to transactions
+ return 'filtered_transactions';
+ }
+
// General summary queries
if (intent === 'summary' && areas && areas.length > 0) {
return 'project_transaction_summary';
diff --git a/src/services/sqlGenerator.js b/src/services/sqlGenerator.js
index f4a9706..9b113b4 100644
--- a/src/services/sqlGenerator.js
+++ b/src/services/sqlGenerator.js
@@ -65,6 +65,8 @@ class SQLGenerator {
AVG(actual_area) as avg_area
FROM rents
WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const params = [];
@@ -106,6 +108,8 @@ class SQLGenerator {
AVG(actual_area) as avg_area
FROM rents
WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const params = [];
@@ -208,6 +212,8 @@ class SQLGenerator {
AVG(actual_area) as avg_area
FROM rents
WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const params = [];
@@ -233,6 +239,8 @@ class SQLGenerator {
AVG(actual_area) as avg_area
FROM rents
WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const params = [];
@@ -270,6 +278,8 @@ class SQLGenerator {
AVG(actual_area) as avg_area
FROM rents
WHERE 1=1
+ AND annual_amount IS NOT NULL
+ AND annual_amount > 0
`;
const params = [];
diff --git a/test_api.sh b/test_api.sh
old mode 100755
new mode 100644