Ui updated

This commit is contained in:
Kenil Bhikadiya 2025-10-30 12:27:26 +05:30
parent e3f4c148f6
commit cf95a3c019
4 changed files with 301 additions and 30 deletions

View File

@ -0,0 +1,154 @@
{
"info": {
"name": "Dubai DLD - Transactions Only (Paginated)",
"_postman_id": "c0a5b3e6-6b0f-4b9e-9f77-transactions-only-v2",
"description": "Only /api/transactions/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/transactions/recent", "host": ["{{baseUrl}}"], "path": ["api","transactions","recent"] }
}
},
{
"name": "Legacy: limit only (top N without OFFSET)",
"request": {
"method": "GET",
"header": [ { "key": "Accept", "value": "application/json" } ],
"url": {
"raw": "{{baseUrl}}/api/transactions/recent?limit=7",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","recent"],
"query": [ {"key":"limit","value":"7"} ]
}
}
},
{
"name": "Pagination: page 1, size 30",
"request": {
"method": "GET",
"header": [ { "key": "Accept", "value": "application/json" } ],
"url": {
"raw": "{{baseUrl}}/api/transactions/recent?page=1&page_size=30",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","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/transactions/recent?page=2&page_size=30",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","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/transactions/recent?area_name=business%20bay&page=1&page_size=50",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","recent"],
"query": [
{"key":"area_name","value":"business bay"},
{"key":"page","value":"1"},
{"key":"page_size","value":"50"}
]
}
}
},
{
"name": "Filter: property_type + beds + pagination",
"request": {
"method": "GET",
"header": [ { "key": "Accept", "value": "application/json" } ],
"url": {
"raw": "{{baseUrl}}/api/transactions/recent?property_type=apartment&beds=2&page=1&page_size=30",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","recent"],
"query": [
{"key":"property_type","value":"apartment"},
{"key":"beds","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/transactions/recent?size_min=1000&size_max=5000&page=1&page_size=30",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","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/transactions/recent?project=29%20boulevard&page=1&page_size=30",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","recent"],
"query": [
{"key":"project","value":"29 boulevard"},
{"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/transactions/recent?area_name=business%20bay&property_type=apartment&beds=2&project=29%20boulevard&size_min=800&size_max=1600&page=1&page_size=50",
"host": ["{{baseUrl}}"],
"path": ["api","transactions","recent"],
"query": [
{"key":"area_name","value":"business bay"},
{"key":"property_type","value":"apartment"},
{"key":"beds","value":"2"},
{"key":"project","value":"29 boulevard"},
{"key":"size_min","value":"800"},
{"key":"size_max","value":"1600"},
{"key":"page","value":"1"},
{"key":"page_size","value":"50"}
]
}
}
}
]
}

View File

@ -128,7 +128,10 @@ function formatCurrency(amount) {
return 'AED ' + parseFloat(amount).toLocaleString('en-US', { maximumFractionDigits: 0 });
}
async function searchTransactions() {
let currentPage = 1;
let lastPaging = { total: 0, page: null, page_size: 30, total_pages: 1, has_next: false, has_prev: false };
async function searchTransactions(pageOverride) {
const formData = new FormData(document.getElementById('filtersForm'));
const params = new URLSearchParams();
@ -138,7 +141,8 @@ async function searchTransactions() {
const size_max = formData.get('size_max');
const beds = formData.get('beds');
const project = formData.get('project');
const limit = formData.get('limit') || '30';
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);
@ -146,7 +150,9 @@ async function searchTransactions() {
if (size_max && !isNaN(parseFloat(size_max))) params.append('size_max', size_max);
if (beds && beds !== 'all') params.append('beds', beds);
if (project && project !== 'all') params.append('project', project);
params.append('limit', limit);
// pagination params
if (page) params.append('page', page);
if (pageSize) params.append('page_size', pageSize);
showLoading(true);
hideError();
@ -156,6 +162,15 @@ async function searchTransactions() {
const response = await fetch(`${API_BASE}/transactions/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 transactions');
@ -168,10 +183,24 @@ async function searchTransactions() {
}
function displayResults(data) {
const { transactions, count } = data;
const { transactions, count, total, page, page_size, total_pages, has_next, has_prev } = data;
const tbody = document.getElementById('transactionsBody');
const resultsCount = document.getElementById('resultsCount');
resultsCount.textContent = `Found ${count} transaction${count !== 1 ? 's' : ''}`;
const showing = count;
const grandTotal = total ?? count;
resultsCount.textContent = `Showing ${showing} of ${grandTotal} 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 (transactions.length === 0) {
const colCount = document.querySelectorAll('#transactionsTable thead th').length;
@ -300,13 +329,40 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('filtersForm').addEventListener('submit', async (e) => {
e.preventDefault();
await searchTransactions();
currentPage = 1;
const pageInput = document.getElementById('page');
if (pageInput) pageInput.value = '1';
await searchTransactions(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 searchTransactions(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 searchTransactions(currentPage);
}
});
}
});

View File

@ -411,8 +411,15 @@
</div>
<div class="filter-group">
<label for="limit">Results Limit</label>
<input type="number" id="limit" name="limit" value="30" min="1" max="100">
<label for="page_size">Page Size</label>
<select id="page_size" name="page_size">
<option value="10">10</option>
<option value="20">20</option>
<option value="30" selected>30</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<input type="hidden" id="page" name="page" value="1">
</div>
<div class="filter-group">
@ -452,6 +459,11 @@
<div class="results-section" id="resultsSection" style="display: none;">
<div class="results-header">
<div class="results-count" id="resultsCount"></div>
<div class="pagination-controls" style="display:flex;gap:10px;align-items:center;">
<button type="button" class="btn btn-secondary" id="prevPage">Prev</button>
<div id="pageInfo" style="font-weight:600;color:#333;"></div>
<button type="button" class="btn btn-secondary" id="nextPage">Next</button>
</div>
</div>
<div class="table-container" id="tableContainer">
<table id="transactionsTable">

View File

@ -306,6 +306,9 @@ router.get('/transactions/recent', async (req, res) => {
size_max,
beds,
project,
// pagination params
page,
page_size,
limit
} = req.query;
@ -322,22 +325,18 @@ router.get('/transactions/recent', async (req, res) => {
limitNum = parsed;
}
// Build SQL query with optional filters
let sql = `
SELECT *
FROM transactions
WHERE 1=1
`;
// Build base WHERE with optional filters
let baseWhere = ' WHERE 1=1';
const params = [];
// Apply filters (case-insensitive matching where applicable)
if (area_name && area_name.trim() !== '') {
sql += ' AND LOWER(area_en) LIKE ?';
baseWhere += ' AND LOWER(area_en) LIKE ?';
params.push(`%${area_name.toLowerCase().trim()}%`);
}
if (property_type && property_type.trim() !== '' && property_type !== 'all') {
sql += ' AND (LOWER(prop_type_en) LIKE ? OR LOWER(prop_sb_type_en) LIKE ?)';
baseWhere += ' AND (LOWER(prop_type_en) LIKE ? OR LOWER(prop_sb_type_en) LIKE ?)';
const propType = `%${property_type.toLowerCase().trim()}%`;
params.push(propType, propType);
}
@ -348,13 +347,13 @@ router.get('/transactions/recent', async (req, res) => {
const hasMin = minNum !== undefined && !isNaN(minNum);
const hasMax = maxNum !== undefined && !isNaN(maxNum);
if (hasMin && hasMax) {
sql += ' AND actual_area BETWEEN ? AND ?';
baseWhere += ' AND actual_area BETWEEN ? AND ?';
params.push(minNum, maxNum);
} else if (hasMin) {
sql += ' AND actual_area >= ?';
baseWhere += ' AND actual_area >= ?';
params.push(minNum);
} else if (hasMax) {
sql += ' AND actual_area <= ?';
baseWhere += ' AND actual_area <= ?';
params.push(maxNum);
}
@ -362,46 +361,96 @@ router.get('/transactions/recent', async (req, res) => {
const bedsStr = beds.toString().trim();
// Match beds - handle formats like "3 b/r", "3.0", "3", "studio"
if (bedsStr.toLowerCase() === 'studio') {
sql += ' AND LOWER(rooms_en) = ?';
baseWhere += ' AND LOWER(rooms_en) = ?';
params.push('studio');
} else if (bedsStr.toLowerCase() === 'null' || bedsStr === '') {
sql += ' AND (rooms_en IS NULL OR rooms_en = "")';
baseWhere += ' AND (rooms_en IS NULL OR rooms_en = "")';
} else {
sql += ' AND (rooms_en LIKE ? OR rooms_en = ? OR rooms_en = ?)';
baseWhere += ' AND (rooms_en LIKE ? OR rooms_en = ? OR rooms_en = ?)';
params.push(`%${bedsStr}%`, bedsStr, `${bedsStr}.0`);
}
}
if (project && project.trim() !== '' && project !== 'all') {
sql += ' AND (LOWER(project_en) LIKE ? OR LOWER(master_project_en) LIKE ?)';
baseWhere += ' AND (LOWER(project_en) LIKE ? OR LOWER(master_project_en) LIKE ?)';
const projectName = `%${project.toLowerCase().trim()}%`;
params.push(projectName, projectName);
}
// Order by most recent first; apply LIMIT only if provided
sql += ' ORDER BY instance_date DESC';
if (limitNum !== undefined) {
sql += ` LIMIT ${limitNum}`;
// Pagination decision: page/page_size OR legacy limit triggers bounded fetch; otherwise full data
const rawPageSize = page_size ?? undefined;
const wantsPagination = (page !== undefined) || (rawPageSize !== 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((rawPageSize ?? (limitNum !== undefined ? limitNum : defaultPageSize)), 10); return isNaN(n) || n < 1 ? defaultPageSize : n; })();
const offsetNum = (pageNum - 1) * pageSizeNum;
const orderBy = ' ORDER BY instance_date DESC, transaction_id DESC';
let dataSql;
let total = null;
if (!wantsPagination) {
dataSql = `
SELECT *
FROM transactions
${baseWhere}
${orderBy}
`;
} else if (limitNum !== undefined && page === undefined && page_size === undefined) {
// Legacy behavior: only limit provided (no paging), apply LIMIT without OFFSET
dataSql = `
SELECT *
FROM transactions
${baseWhere}
${orderBy}
LIMIT ${limitNum}
`;
} else {
// Full pagination with COUNT
const countSql = `
SELECT COUNT(*) AS total
FROM transactions
${baseWhere}
`;
const countRows = await database.query(countSql, params);
total = countRows && countRows[0] ? countRows[0].total : 0;
dataSql = `
SELECT *
FROM transactions
${baseWhere}
${orderBy}
LIMIT ${pageSizeNum} OFFSET ${offsetNum}
`;
}
console.log(`🔍 Fetching recent transactions with filters:`, {
area_name, property_type, size, size_min, size_max, beds, project, limit: limitNum
area_name, property_type, size, size_min, size_max, beds, project, limit: limitNum, page, page_size
});
const transactions = await database.query(sql, params);
const transactions = await database.query(dataSql, params);
const totalPages = (total !== null) ? Math.max(1, Math.ceil(total / pageSizeNum)) : 1;
res.json({
success: true,
data: {
transactions: transactions,
count: transactions.length,
total: total !== null ? total : transactions.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,
size: size || null,
beds: beds || null,
project: project || null,
limit: limitNum ?? null
limit: limitNum ?? null,
page: total !== null ? pageNum : null,
page_size: total !== null ? pageSizeNum : (limitNum !== undefined ? limitNum : null)
}
}
});