Refactors reports page search and filtering functionality
Moves search and filtering logic from inline script to a dedicated TypeScript module Improves code organization by creating a ReportsController with: - Enhanced search capabilities - Advanced range query parsing - Flexible filtering mechanisms Removes inline JavaScript and integrates modular approach in the application
This commit is contained in:
parent
405dabeb34
commit
2f92b0eb7b
3 changed files with 364 additions and 182 deletions
|
|
@ -391,188 +391,6 @@
|
|||
@section Scripts {
|
||||
<script type="module">
|
||||
import { createChart } from '/lib/swp-charting/dist/swp-charting.js';
|
||||
import Fuse from '/lib/fuse/fuse.mjs';
|
||||
|
||||
// === SEARCH FUNCTIONALITY ===
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const tableRows = document.querySelectorAll('swp-card.sales-table swp-data-table-row');
|
||||
|
||||
// Build searchable data from table rows
|
||||
const salesData = Array.from(tableRows).map((row, index) => {
|
||||
const cells = row.querySelectorAll('swp-data-table-cell');
|
||||
return {
|
||||
index,
|
||||
invoice: cells[0]?.textContent?.trim() || '',
|
||||
date: cells[1]?.textContent?.trim() || '',
|
||||
customer: cells[2]?.textContent?.trim() || '',
|
||||
employee: cells[3]?.textContent?.trim() || '',
|
||||
services: cells[4]?.textContent?.trim() || '',
|
||||
amount: cells[5]?.textContent?.trim() || '',
|
||||
payment: cells[6]?.textContent?.trim() || '',
|
||||
status: cells[7]?.textContent?.trim() || ''
|
||||
};
|
||||
});
|
||||
|
||||
// Initialize Fuse.js
|
||||
const fuse = new Fuse(salesData, {
|
||||
keys: ['invoice', 'customer', 'employee', 'services', 'amount', 'payment', 'status'],
|
||||
threshold: 0.3,
|
||||
ignoreLocation: true
|
||||
});
|
||||
|
||||
// Parse amount string "1.450 kr" -> 1450
|
||||
function parseAmountFromText(text) {
|
||||
const match = text.match(/-?([\d.]+)/);
|
||||
if (!match) return null;
|
||||
// Remove thousand separators (Danish format uses . for thousands)
|
||||
return parseFloat(match[1].replace(/\./g, ''));
|
||||
}
|
||||
|
||||
// Parse invoice number "#1847" -> 1847
|
||||
function parseInvoiceNumber(text) {
|
||||
const match = text.match(/#(\d+)/);
|
||||
return match ? parseInt(match[1], 10) : null;
|
||||
}
|
||||
|
||||
// Parse range operators from query (supports both amount and invoice ranges)
|
||||
function parseRangeQuery(query) {
|
||||
let textQuery = query;
|
||||
let minAmount = null;
|
||||
let maxAmount = null;
|
||||
let minInvoice = null;
|
||||
let maxInvoice = null;
|
||||
|
||||
// === INVOICE NUMBER RANGES (with # prefix) ===
|
||||
// Match #1840-1845
|
||||
let match = textQuery.match(/#(\d+)-(\d+)/);
|
||||
if (match) {
|
||||
minInvoice = parseInt(match[1], 10);
|
||||
maxInvoice = parseInt(match[2], 10);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #>=
|
||||
match = textQuery.match(/#>=\s*(\d+)/);
|
||||
if (match) {
|
||||
minInvoice = parseInt(match[1], 10);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #> (but not #>=)
|
||||
match = textQuery.match(/#>\s*(\d+)/);
|
||||
if (match) {
|
||||
minInvoice = parseInt(match[1], 10) + 1;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #<=
|
||||
match = textQuery.match(/#<=\s*(\d+)/);
|
||||
if (match) {
|
||||
maxInvoice = parseInt(match[1], 10);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #< (but not #<=)
|
||||
match = textQuery.match(/#<\s*(\d+)/);
|
||||
if (match) {
|
||||
maxInvoice = parseInt(match[1], 10) - 1;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// === AMOUNT RANGES (no prefix) ===
|
||||
// Match range syntax: 400-1000
|
||||
match = textQuery.match(/(\d+)-(\d+)/);
|
||||
if (match) {
|
||||
minAmount = parseFloat(match[1]);
|
||||
maxAmount = parseFloat(match[2]);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match >=
|
||||
match = textQuery.match(/>=\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
minAmount = parseFloat(match[1]);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match > (but not >=)
|
||||
match = textQuery.match(/>\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
minAmount = parseFloat(match[1]) + 0.01;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match <=
|
||||
match = textQuery.match(/<=\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
maxAmount = parseFloat(match[1]);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match < (but not <=)
|
||||
match = textQuery.match(/<\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
maxAmount = parseFloat(match[1]) - 0.01;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
return { textQuery, minAmount, maxAmount, minInvoice, maxInvoice };
|
||||
}
|
||||
|
||||
// Search handler with range support
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.trim();
|
||||
|
||||
if (!query) {
|
||||
// Show all rows
|
||||
tableRows.forEach(row => row.style.display = '');
|
||||
return;
|
||||
}
|
||||
|
||||
const { textQuery, minAmount, maxAmount, minInvoice, maxInvoice } = parseRangeQuery(query);
|
||||
|
||||
// Start with all indices
|
||||
let matchedIndices = new Set(salesData.map((_, i) => i));
|
||||
|
||||
// Apply invoice number range filter
|
||||
if (minInvoice !== null || maxInvoice !== null) {
|
||||
matchedIndices = new Set(
|
||||
salesData
|
||||
.filter((item) => {
|
||||
const invoiceNum = parseInvoiceNumber(item.invoice);
|
||||
if (invoiceNum === null) return false;
|
||||
if (minInvoice !== null && invoiceNum < minInvoice) return false;
|
||||
if (maxInvoice !== null && invoiceNum > maxInvoice) return false;
|
||||
return true;
|
||||
})
|
||||
.map(item => item.index)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply amount range filter
|
||||
if (minAmount !== null || maxAmount !== null) {
|
||||
matchedIndices = new Set(
|
||||
[...matchedIndices].filter(i => {
|
||||
const amount = parseAmountFromText(salesData[i].amount);
|
||||
if (amount === null) return false;
|
||||
if (minAmount !== null && amount < minAmount) return false;
|
||||
if (maxAmount !== null && amount > maxAmount) return false;
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Apply Fuse.js text search if there's remaining text
|
||||
if (textQuery) {
|
||||
const fuseResults = fuse.search(textQuery);
|
||||
const textMatches = new Set(fuseResults.map(r => r.item.index));
|
||||
matchedIndices = new Set([...matchedIndices].filter(i => textMatches.has(i)));
|
||||
}
|
||||
|
||||
tableRows.forEach((row, index) => {
|
||||
row.style.display = matchedIndices.has(index) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Revenue bar chart
|
||||
createChart(document.getElementById('revenueChart'), {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue