// Properties page logic for unified rent/sale endpoint const API_BASE = '/api'; // Combined area dropdown values (from both transactions and rents) const areas = [ 'abu hail', 'al athbah', 'al aweer first', 'al aweer second', 'al bada', 'al baraha', 'al barari', 'al barsha first', 'al barsha second', 'al barsha third', 'al barshaa south first', 'al barshaa south second', 'al barshaa south third', 'al buteen', 'al dhagaya', 'al eyas', 'al furjan', 'al garhoud', 'al goze first', 'al goze fourth', 'al goze industrial first', 'al goze industrial fourth', 'al goze industrial second', 'al goze industrial third', 'al goze third', 'al hamriya', 'al hebiah fifth', 'al hebiah first', 'al hebiah fourth', 'al hebiah sixth', 'al hebiah third', 'al hudaiba', 'al jadaf', 'al jafliya', 'al karama', 'al khabeesi', 'al khail heights', 'al khairan first', 'al khawaneej first', 'al khawaneej second', 'al kheeran', 'al kifaf', 'al lusaily', 'al mamzer', 'al manara', 'al mararr', 'al merkadh', 'al mizhar first', 'al mizhar fourth', 'al mizhar second', 'al mizhar third', 'al murqabat', 'al muteena', 'al nahda first', 'al nahda second', 'al qusais', 'al qusais industrial fifth', 'al qusais industrial first', 'al qusais industrial fourth', 'al qusais industrial third', 'al raffa', 'al ras', 'al rashidiya', 'al rega', 'al rowaiyah third', 'al saffa first', 'al saffa second', 'al safouh first', 'al satwa', 'al suq al kabeer', 'al thanyah fifth', 'al thanyah third', 'al ttay', 'al twar fifth', 'al twar first', 'al twar fourth', 'al twar second', 'al twar third', 'al waha', 'al waheda', 'al warqa first', 'al warqa fourth', 'al warqa second', 'al warqa third', 'al warsan second', 'al warsan third', 'al wasl', 'al yelayiss 1', 'al yelayiss 2', 'al yelayiss 5', 'al yufrah 1', 'arabian ranches i', 'arabian ranches ii', 'arabian ranches iii', 'arabian ranches polo club', 'arjan', 'barsha heights', 'bluewaters', 'bukadra', 'burj khalifa', 'business bay', 'business park', 'cherrywoods', 'city of arabia', 'city walk', 'damac hills', 'discovery gardens', 'dmcc-ez2', 'down town jabal ali', 'dubai creek harbour', 'dubai design district', 'dubai golf city', 'dubai harbour', 'dubai healthcare city - phase 1', 'dubai healthcare city - phase 2', 'dubai hills', 'dubai industrial city', 'dubai international airport', 'dubai investment park first', 'dubai investment park second', 'dubai land residence complex', 'dubai lifestyle city', 'dubai marina', 'dubai maritime city', 'dubai production city', 'dubai science park', 'dubai south', 'dubai sports city', 'dubai studio city', 'dubai water canal', 'dubai water front', 'emaar south', 'emirate living', 'eyal nasser', 'falcon city of wonders', 'ghadeer al tair', 'ghadeer barashy', 'grand hills dubai', 'grand views', 'hadaeq sheikh mohammed bin rashid', 'hessyan second', 'hor al anz', 'hor al anz east', 'horizon', 'international city ph 1', 'international city ph 2 & 3', 'island 2', 'jabal ali first', 'jabal ali industrial first', 'jabal ali industrial second', 'jabel ali hills', 'jaddaf waterfront', 'jumeira bay', 'jumeirah beach residence', 'jumeirah first', 'jumeirah golf', 'jumeirah heights', 'jumeirah islands', 'jumeirah lakes towers', 'jumeirah living', 'jumeirah park', 'jumeirah second', 'jumeirah third', 'jumeirah village circle', 'jumeirah village triangle', 'la mer', 'lehbab first', 'lehbab second', 'living legends', 'liwan', 'liwan 2', 'madinat al mataar', 'madinat dubai almelaheyah', 'madinat hind 3', 'madinat hind 4', 'madinat latifa', 'majan', 'mankhool', 'margham', 'marsa dubai', 'mbr district 1', 'mbr district 7', "me'aisem first", "me'aisem second", 'medyan race course villas', 'mena jabal ali', 'meydan avenue', 'meydan one', 'millennium', 'mina rashid', 'mira', 'mirdif', 'motor city', 'mudon', 'muhaisanah first', 'muhaisanah fourth', 'muhaisanah second', 'muhaisanah third', 'mushrif', 'nad al hamar', 'nad al sheba gardens', 'nad al shiba first', 'nad al shiba fourth', 'nad al shiba second', 'nad al shiba third', 'nad shamma', 'nadd hessa', 'naif', 'nazwah', 'oud metha', 'palm deira', 'palm jabal ali', 'palm jumeirah', 'palmarosa', 'pearl jumeira', 'polo townhouses igo', 'port saeed', 'ras al khor', 'ras al khor industrial first', 'ras al khor industrial second', 'ras al khor industrial third', 'rega al buteen', 'remraam', 'rukan', 'saih shuaib 1', 'saih shuaib 2', 'saih shuaib 3', 'saih shuaib 4', 'sama al jadaf', 'serena', 'silicon oasis', 'sobha heartland', 'sufouh gardens', 'sustainable city', 'tecom site a', 'tecom site d', 'the beach', 'the field', 'the greens', 'the lakes', 'the valley', 'the villa', 'the world', 'tilal al ghaf', 'town square', 'trade center first', 'trade center second', 'um al sheif', 'um hurair first', 'um hurair second', 'um ramool', 'um suqaim first', 'um suqaim second', 'um suqaim third', 'umm addamin', 'villanova', 'wadi al amardi', 'wadi al safa 2', 'wadi al safa 3', 'wadi al safa 4', 'wadi al safa 5', 'wadi al safa 6', 'wadi al safa 7', 'warsan first', 'warsan fourth', 'yaraah', 'zaabeel first', 'zaabeel second' ]; // Property types for rent const rentPropertyTypes = [ { value: 'all', label: 'All Types' }, { value: 'unit', label: 'Unit' }, { value: 'villa', label: 'Villa' }, { value: 'virtual unit', label: 'Virtual Unit' }, { value: 'land', label: 'Land' }, { value: 'building', label: 'Building' } ]; // Property types for sale const salePropertyTypes = [ { value: 'all', label: 'All Types' }, { value: 'building', label: 'Building' }, { value: 'land', label: 'Land' }, { value: 'unit', label: 'Unit' } ]; // Combined projects list (simplified - you may want to load dynamically) const projects = [ '014 tower', '08 life residences', '1 residences', '10 oxford by iman', '105 residences by kamdar', '11 hills park', '15 cascade', '15 northside', '161 jumeirah lane', '17 icon bay', '171 garden heights', '1wood residence', '2020 marquis', '23 marina', '29 boulevard', '310 riverside crescent', '311 boulevard by bam eskan', 'empire estates', 'empire heights', 'starz tower by danube', 'azizi feirouz i', 'candace acacia hotel apartments', 'burj al nujoom', 'azizi riviera 35' // Add more projects as needed ]; let currentType = 'rent'; let currentPage = 1; let lastPaging = { total: 0, page: null, page_size: 30, total_pages: 1, has_next: false, has_prev: false }; 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 updatePropertyTypes() { const propertyTypeSelect = document.getElementById('property_type'); propertyTypeSelect.innerHTML = ''; const types = currentType === 'rent' ? rentPropertyTypes : salePropertyTypes; types.forEach(type => { const option = document.createElement('option'); option.value = type.value; option.textContent = type.label; propertyTypeSelect.appendChild(option); }); } function updateTypeDependentFields() { const typeInput = document.getElementById('type'); typeInput.value = currentType; // Show/hide rooms vs beds const roomsGroup = document.getElementById('roomsGroup'); const bedsGroup = document.getElementById('bedsGroup'); if (currentType === 'rent') { roomsGroup.style.display = 'flex'; bedsGroup.style.display = 'none'; document.getElementById('beds').value = 'all'; } else { roomsGroup.style.display = 'none'; bedsGroup.style.display = 'flex'; document.getElementById('rooms').value = 'all'; } updatePropertyTypes(); updateTableHeaders(); } function updateTableHeaders() { const tableHead = document.getElementById('tableHead'); if (currentType === 'rent') { tableHead.innerHTML = ` 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 `; } else { tableHead.innerHTML = ` transaction_id transaction_number instance_date group_en procedure_en is_offplan_en is_free_hold_en usage_en area_en prop_type_en prop_sb_type_en trans_value procedure_area actual_area rooms_en parking nearest_metro_en nearest_mall_en nearest_landmark_en total_buyer total_seller master_project_en project_en created_at updated_at `; } } 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 }); } async function searchProperties(pageOverride) { const formData = new FormData(document.getElementById('filtersForm')); const params = new URLSearchParams(); const type = formData.get('type') || currentType; 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 beds = formData.get('beds'); const project = formData.get('project'); const pageSize = formData.get('page_size') || '30'; const page = pageOverride != null ? pageOverride : (formData.get('page') || currentPage || 1); params.append('type', type); 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 (type === 'rent' && rooms && rooms !== 'all') { params.append('rooms', rooms); } else if (type === 'sale' && beds && beds !== 'all') { params.append('beds', beds); } if (project && project !== 'all') params.append('project', project); if (page) params.append('page', page); if (pageSize) params.append('page_size', pageSize); showLoading(true); hideError(); hideResults(); try { const response = await fetch(`${API_BASE}/properties/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 properties'); } } catch (error) { showError('Network error: ' + error.message); } finally { showLoading(false); } } function displayResults(data) { const dataKey = currentType === 'rent' ? 'rents' : 'transactions'; const results = data[dataKey] || []; const { count, total, page, page_size, total_pages, has_next, has_prev } = data; const tbody = document.getElementById('propertiesBody'); const resultsCount = document.getElementById('resultsCount'); const showing = count; const grandTotal = total ?? count; resultsCount.textContent = `Showing ${showing} of ${grandTotal} ${currentType === 'rent' ? 'rents' : 'transactions'}`; 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 (results.length === 0) { const colCount = document.querySelectorAll('#propertiesTable thead th').length; tbody.innerHTML = ( '' + `` + '' + '' + '' + `
No ${currentType === 'rent' ? 'rents' : 'transactions'} found matching your filters
` + '' + '' ); showResults(); return; } if (currentType === 'rent') { tbody.innerHTML = results.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(''); } else { tbody.innerHTML = results.map(t => ( '' + `${t.transaction_id ?? 'N/A'}` + `${t.transaction_number ?? 'N/A'}` + `${formatDate(t.instance_date)}` + `${t.group_en ?? 'N/A'}` + `${t.procedure_en ?? 'N/A'}` + `${t.is_offplan_en ?? 'N/A'}` + `${t.is_free_hold_en ?? 'N/A'}` + `${t.usage_en ?? 'N/A'}` + `${t.area_en ?? 'N/A'}` + `${t.prop_type_en ?? 'N/A'}` + `${t.prop_sb_type_en ?? 'N/A'}` + `${formatCurrency(t.trans_value)}` + `${t.procedure_area != null ? formatNumber(t.procedure_area) : 'N/A'}` + `${t.actual_area != null ? formatNumber(t.actual_area) : 'N/A'}` + `${t.rooms_en ?? 'N/A'}` + `${t.parking ?? 'N/A'}` + `${t.nearest_metro_en ?? 'N/A'}` + `${t.nearest_mall_en ?? 'N/A'}` + `${t.nearest_landmark_en ?? 'N/A'}` + `${t.total_buyer ?? '0'}` + `${t.total_seller ?? '0'}` + `${t.master_project_en ?? 'N/A'}` + `${t.project_en ?? 'N/A'}` + `${formatDate(t.created_at)}` + `${formatDate(t.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'; document.getElementById('type_rent').checked = true; currentType = 'rent'; updateTypeDependentFields(); hideResults(); hideError(); } document.addEventListener('DOMContentLoaded', () => { populateAreas(); loadProjects(); updateTypeDependentFields(); // Type selector radio buttons document.querySelectorAll('input[name="type"]').forEach(radio => { radio.addEventListener('change', (e) => { currentType = e.target.value; updateTypeDependentFields(); hideResults(); hideError(); }); }); // Size slider setup 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`; } sizeMinHidden.value = (min > MIN_VAL) ? min : ''; sizeMaxHidden.value = (max < MAX_VAL) ? max : ''; 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(); }); 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 }); }); sizeMinRange.value = MIN_VAL; sizeMaxRange.value = MAX_VAL; updateSizeLabel(); // Form submission document.getElementById('filtersForm').addEventListener('submit', async (e) => { e.preventDefault(); currentPage = 1; const pageInput = document.getElementById('page'); if (pageInput) pageInput.value = '1'; await searchProperties(1); }); // Reset button 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 searchProperties(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 searchProperties(currentPage); } }); } });