fixes and refactor
This commit is contained in:
@@ -230,13 +230,16 @@
|
||||
<button type="submit" class="w-full px-4 py-2 bg-primary-600 text-white hover:bg-primary-700 rounded-lg transition-colors flex items-center justify-center gap-2">
|
||||
<i class="fa-solid fa-magnifying-glass"></i> Search
|
||||
</button>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<button type="button" class="w-full px-4 py-2 text-neutral-700 dark:text-neutral-300 bg-neutral-200 dark:bg-neutral-700 hover:bg-neutral-300 dark:hover:bg-neutral-600 rounded-lg transition-colors flex items-center justify-center gap-2" id="saveSearchBtn">
|
||||
<i class="fa-solid fa-bookmark"></i> Save Search
|
||||
</button>
|
||||
<button type="button" class="w-full px-4 py-2 text-neutral-700 dark:text-neutral-300 bg-neutral-200 dark:bg-neutral-700 hover:bg-neutral-300 dark:hover:bg-neutral-600 rounded-lg transition-colors flex items-center justify-center gap-2" id="resetSearchBtn">
|
||||
<i class="fa-solid fa-rotate-right"></i> Reset
|
||||
</button>
|
||||
<button type="button" class="w-full px-4 py-2 text-neutral-700 dark:text-neutral-300 bg-neutral-200 dark:bg-neutral-700 hover:bg-neutral-300 dark:hover:bg-neutral-600 rounded-lg transition-colors flex items-center justify-center gap-2" id="restoreLastBtn">
|
||||
<i class="fa-solid fa-clock-rotate-left"></i> Restore Last
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -412,10 +415,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
setupEventHandlers();
|
||||
setupKeyboardShortcuts();
|
||||
|
||||
// Check for URL parameters to auto-load search
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('q')) {
|
||||
document.getElementById('searchQuery').value = urlParams.get('q');
|
||||
// Apply URL parameters to form and auto-perform search if any present
|
||||
const didApplyFromUrl = applyCriteriaFromUrl();
|
||||
if (didApplyFromUrl) {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
@@ -541,6 +543,8 @@ function setupEventHandlers() {
|
||||
document.getElementById('confirmSaveSearch').addEventListener('click', saveCurrentSearch);
|
||||
document.getElementById('savedSearchBtn').addEventListener('click', loadSavedSearches);
|
||||
document.getElementById('clearAllBtn').addEventListener('click', clearAll);
|
||||
const restoreBtn = document.getElementById('restoreLastBtn');
|
||||
if (restoreBtn) restoreBtn.addEventListener('click', restoreLastSearch);
|
||||
|
||||
// Sort change handlers
|
||||
document.getElementById('sortBy').addEventListener('change', () => {
|
||||
@@ -553,6 +557,19 @@ function setupEventHandlers() {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
|
||||
// Facet chip click handler (event delegation)
|
||||
const facetsContainer = document.getElementById('facetsContainer');
|
||||
facetsContainer.addEventListener('click', function(e) {
|
||||
const chip = e.target.closest('.facet-chip');
|
||||
if (chip && facetsContainer.contains(chip)) {
|
||||
const facet = chip.getAttribute('data-facet');
|
||||
const value = chip.getAttribute('data-value');
|
||||
if (applyFacetFilter(facet, value)) {
|
||||
performSearch(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupKeyboardShortcuts() {
|
||||
@@ -569,8 +586,14 @@ function setupKeyboardShortcuts() {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
// Escape to clear search
|
||||
// Escape: hide suggestions if open; otherwise clear search
|
||||
if (e.key === 'Escape' && document.activeElement.id === 'searchQuery') {
|
||||
const dropdown = document.getElementById('searchSuggestions');
|
||||
if (dropdown && !dropdown.classList.contains('hidden')) {
|
||||
e.preventDefault();
|
||||
dropdown.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
clearAll();
|
||||
}
|
||||
});
|
||||
@@ -631,6 +654,10 @@ async function performSearch(offset = 0) {
|
||||
const criteria = buildSearchCriteria();
|
||||
criteria.offset = offset;
|
||||
currentSearchCriteria = criteria;
|
||||
// Sync URL with current criteria for shareable searches
|
||||
syncUrlWithCriteria(criteria);
|
||||
// Save last criteria best-effort
|
||||
saveLastCriteria(criteria);
|
||||
|
||||
try {
|
||||
const response = await window.http.wrappedFetch('/api/search/advanced', {
|
||||
@@ -651,6 +678,59 @@ async function performSearch(offset = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveLastCriteria(criteria) {
|
||||
try {
|
||||
await window.http.wrappedFetch('/api/search/last_criteria', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(criteria)
|
||||
});
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
async function restoreLastSearch() {
|
||||
try {
|
||||
const resp = await window.http.wrappedFetch('/api/search/last_criteria');
|
||||
if (!resp.ok) throw new Error('failed');
|
||||
const saved = await resp.json();
|
||||
if (!saved || Object.keys(saved).length === 0) {
|
||||
showAlert('No previous search found', 'info');
|
||||
return;
|
||||
}
|
||||
// Populate form from saved criteria
|
||||
document.getElementById('searchQuery').value = saved.query || '';
|
||||
document.getElementById('exactPhrase').checked = !!saved.exact_phrase;
|
||||
document.getElementById('caseSensitive').checked = !!saved.case_sensitive;
|
||||
document.getElementById('wholeWords').checked = !!saved.whole_words;
|
||||
if (Array.isArray(saved.search_types) && saved.search_types.length) {
|
||||
document.querySelectorAll('.search-type').forEach(cb => cb.checked = saved.search_types.includes(cb.value));
|
||||
}
|
||||
if (saved.date_field) document.getElementById('dateField').value = saved.date_field;
|
||||
if (saved.date_from) document.getElementById('dateFrom').value = saved.date_from;
|
||||
if (saved.date_to) document.getElementById('dateTo').value = saved.date_to;
|
||||
if (saved.amount_field) document.getElementById('amountField').value = saved.amount_field;
|
||||
if (saved.amount_min != null) document.getElementById('amountMin').value = saved.amount_min;
|
||||
if (saved.amount_max != null) document.getElementById('amountMax').value = saved.amount_max;
|
||||
const setMulti = (id, values) => {
|
||||
if (!values || !values.length) return;
|
||||
const select = document.getElementById(id);
|
||||
if (!select) return;
|
||||
const set = new Set(values.map(String));
|
||||
Array.from(select.options).forEach(o => { o.selected = set.has(String(o.value)); });
|
||||
};
|
||||
setMulti('fileTypes', saved.file_types);
|
||||
setMulti('fileStatuses', saved.file_statuses);
|
||||
setMulti('employees', saved.employees);
|
||||
setMulti('transactionTypes', saved.transaction_types);
|
||||
setMulti('states', saved.states);
|
||||
document.getElementById('activeOnly').checked = saved.active_only !== false;
|
||||
document.getElementById('hasBalance').checked = !!saved.has_balance;
|
||||
document.getElementById('isBilled').checked = !!saved.is_billed;
|
||||
performSearch(0);
|
||||
} catch (e) {
|
||||
showAlert('Could not restore last search', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
function buildSearchCriteria() {
|
||||
const searchTypes = [];
|
||||
document.querySelectorAll('.search-type:checked').forEach(checkbox => {
|
||||
@@ -833,9 +913,11 @@ function displayFacets(facets) {
|
||||
facetsHTML += `
|
||||
<div class="facet-group mb-2">
|
||||
<strong>${facetName.replace('_', ' ').toUpperCase()}:</strong>
|
||||
${Object.entries(facetData).map(([value, count]) =>
|
||||
`<span class="inline-block px-2 py-0.5 text-xs rounded bg-neutral-200 text-neutral-700 ml-1">${value} (${count})</span>`
|
||||
).join('')}
|
||||
${Object.entries(facetData).map(([value, count]) => {
|
||||
const isClickable = ['state','transaction_type','file_type','status','employee'].includes(facetName);
|
||||
const cls = isClickable ? 'facet-chip cursor-pointer hover:bg-neutral-300' : '';
|
||||
return `<span class="inline-block px-2 py-0.5 text-xs rounded bg-neutral-200 text-neutral-700 ml-1 ${cls}" data-facet="${facetName}" data-value="${String(value).replace(/"/g,'"')}">${value} (${count})</span>`
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -844,6 +926,106 @@ function displayFacets(facets) {
|
||||
container.innerHTML = facetsHTML;
|
||||
}
|
||||
|
||||
// Apply a clicked facet value to the appropriate filter control
|
||||
function applyFacetFilter(facetName, value) {
|
||||
const map = {
|
||||
'state': 'states',
|
||||
'transaction_type': 'transactionTypes',
|
||||
'file_type': 'fileTypes',
|
||||
'status': 'fileStatuses',
|
||||
'employee': 'employees'
|
||||
};
|
||||
const selectId = map[facetName];
|
||||
if (!selectId) return false;
|
||||
const select = document.getElementById(selectId);
|
||||
if (!select) return false;
|
||||
const option = Array.from(select.options).find(o => String(o.value) === String(value));
|
||||
if (!option) return false;
|
||||
option.selected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sync URL query params with current criteria (shareable/bookmarkable)
|
||||
function syncUrlWithCriteria(criteria) {
|
||||
const params = new URLSearchParams();
|
||||
if (criteria.query) params.set('q', criteria.query);
|
||||
if (Array.isArray(criteria.search_types) && criteria.search_types.length) params.set('types', criteria.search_types.join(','));
|
||||
if (criteria.exact_phrase) params.set('exact_phrase', '1');
|
||||
if (criteria.case_sensitive) params.set('case_sensitive', '1');
|
||||
if (criteria.whole_words) params.set('whole_words', '1');
|
||||
if (criteria.sort_by) params.set('sort_by', criteria.sort_by);
|
||||
if (criteria.sort_order) params.set('sort_order', criteria.sort_order);
|
||||
if (criteria.date_field) params.set('date_field', criteria.date_field);
|
||||
if (criteria.date_from) params.set('date_from', criteria.date_from);
|
||||
if (criteria.date_to) params.set('date_to', criteria.date_to);
|
||||
if (criteria.amount_field) params.set('amount_field', criteria.amount_field);
|
||||
if (criteria.amount_min != null) params.set('amount_min', String(criteria.amount_min));
|
||||
if (criteria.amount_max != null) params.set('amount_max', String(criteria.amount_max));
|
||||
if (Array.isArray(criteria.file_types) && criteria.file_types.length) params.set('file_types', criteria.file_types.join(','));
|
||||
if (Array.isArray(criteria.file_statuses) && criteria.file_statuses.length) params.set('file_statuses', criteria.file_statuses.join(','));
|
||||
if (Array.isArray(criteria.employees) && criteria.employees.length) params.set('employees', criteria.employees.join(','));
|
||||
if (Array.isArray(criteria.transaction_types) && criteria.transaction_types.length) params.set('transaction_types', criteria.transaction_types.join(','));
|
||||
if (Array.isArray(criteria.states) && criteria.states.length) params.set('states', criteria.states.join(','));
|
||||
if (criteria.active_only === false) params.set('active_only', '0');
|
||||
if (criteria.has_balance === true) params.set('has_balance', '1');
|
||||
if (criteria.is_billed === true) params.set('is_billed', '1');
|
||||
const page = Math.floor((criteria.offset || 0) / (criteria.limit || 50)) + 1;
|
||||
if (page > 1) params.set('page', String(page));
|
||||
const newUrl = `${window.location.pathname}?${params.toString()}`;
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
}
|
||||
|
||||
function applyCriteriaFromUrl() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let applied = false;
|
||||
const getBool = (key) => {
|
||||
const v = urlParams.get(key);
|
||||
return v === '1' || v === 'true';
|
||||
};
|
||||
const getCsv = (key) => {
|
||||
const v = urlParams.get(key);
|
||||
return v ? v.split(',').filter(Boolean) : [];
|
||||
};
|
||||
const q = urlParams.get('q');
|
||||
if (q) {
|
||||
document.getElementById('searchQuery').value = q;
|
||||
applied = true;
|
||||
}
|
||||
const types = getCsv('types').length ? getCsv('types') : getCsv('search_types');
|
||||
if (types.length) {
|
||||
document.querySelectorAll('.search-type').forEach(cb => cb.checked = types.includes(cb.value));
|
||||
applied = true;
|
||||
}
|
||||
if (urlParams.has('exact_phrase')) { document.getElementById('exactPhrase').checked = getBool('exact_phrase'); applied = true; }
|
||||
if (urlParams.has('case_sensitive')) { document.getElementById('caseSensitive').checked = getBool('case_sensitive'); applied = true; }
|
||||
if (urlParams.has('whole_words')) { document.getElementById('wholeWords').checked = getBool('whole_words'); applied = true; }
|
||||
if (urlParams.has('sort_by')) { document.getElementById('sortBy').value = urlParams.get('sort_by'); applied = true; }
|
||||
if (urlParams.has('sort_order')) { document.getElementById('sortOrder').value = urlParams.get('sort_order'); applied = true; }
|
||||
if (urlParams.has('date_field')) { document.getElementById('dateField').value = urlParams.get('date_field'); applied = true; }
|
||||
if (urlParams.has('date_from')) { document.getElementById('dateFrom').value = urlParams.get('date_from'); applied = true; }
|
||||
if (urlParams.has('date_to')) { document.getElementById('dateTo').value = urlParams.get('date_to'); applied = true; }
|
||||
if (urlParams.has('amount_field')) { document.getElementById('amountField').value = urlParams.get('amount_field'); applied = true; }
|
||||
if (urlParams.has('amount_min')) { document.getElementById('amountMin').value = urlParams.get('amount_min'); applied = true; }
|
||||
if (urlParams.has('amount_max')) { document.getElementById('amountMax').value = urlParams.get('amount_max'); applied = true; }
|
||||
const setMulti = (id, values) => {
|
||||
if (!values || !values.length) return false;
|
||||
const select = document.getElementById(id);
|
||||
if (!select) return false;
|
||||
const set = new Set(values.map(String));
|
||||
Array.from(select.options).forEach(o => { o.selected = set.has(String(o.value)); });
|
||||
return true;
|
||||
};
|
||||
if (setMulti('fileTypes', getCsv('file_types'))) applied = true;
|
||||
if (setMulti('fileStatuses', getCsv('file_statuses'))) applied = true;
|
||||
if (setMulti('employees', getCsv('employees'))) applied = true;
|
||||
if (setMulti('transactionTypes', getCsv('transaction_types'))) applied = true;
|
||||
if (setMulti('states', getCsv('states'))) applied = true;
|
||||
if (urlParams.has('active_only')) { document.getElementById('activeOnly').checked = getBool('active_only'); applied = true; }
|
||||
if (urlParams.has('has_balance')) { document.getElementById('hasBalance').checked = getBool('has_balance'); applied = true; }
|
||||
if (urlParams.has('is_billed')) { document.getElementById('isBilled').checked = getBool('is_billed'); applied = true; }
|
||||
return applied;
|
||||
}
|
||||
|
||||
function displayPagination(pageInfo) {
|
||||
const paginationContainer = document.getElementById('searchPagination');
|
||||
paginationContainer.innerHTML = '';
|
||||
|
||||
Reference in New Issue
Block a user