diff --git a/migrations/add-invoice-items-filter-index.sql b/migrations/add-invoice-items-filter-index.sql
new file mode 100644
index 0000000..73bb80f
--- /dev/null
+++ b/migrations/add-invoice-items-filter-index.sql
@@ -0,0 +1 @@
+CREATE INDEX idx_invoice_items_invoice_qbo ON public.invoice_items USING btree (invoice_id, qbo_item_id);
diff --git a/public/js/views/invoice-view.js b/public/js/views/invoice-view.js
index 36ae8dc..9ccf0de 100644
--- a/public/js/views/invoice-view.js
+++ b/public/js/views/invoice-view.js
@@ -6,6 +6,8 @@ let filterCustomer = localStorage.getItem('inv_filterCustomer') || '';
let filterStatus = localStorage.getItem('inv_filterStatus') || 'unpaid';
let groupBy = localStorage.getItem('inv_groupBy') || 'none';
let filterWorker = localStorage.getItem('inv_filterWorker') || '';
+let filterItemType = localStorage.getItem('inv_filterItemType') === 'true';
+let filterEmptyCost = localStorage.getItem('inv_filterEmptyCost') === 'true';
const OVERDUE_DAYS = 30;
@@ -196,6 +198,8 @@ function saveSettings() {
localStorage.setItem('inv_groupBy', groupBy);
localStorage.setItem('inv_filterCustomer', filterCustomer);
localStorage.setItem('inv_filterWorker', filterWorker);
+ localStorage.setItem('inv_filterItemType', filterItemType);
+ localStorage.setItem('inv_filterEmptyCost', filterEmptyCost);
}
// ============================================================
@@ -204,7 +208,14 @@ function saveSettings() {
export async function loadInvoices() {
try {
- const response = await fetch('/api/invoices');
+ const params = new URLSearchParams();
+ if (filterItemType) {
+ params.set('has_parts_or_subscription', 'true');
+ if (filterEmptyCost) params.set('empty_cost_only', 'true');
+ }
+ const qs = params.toString();
+ const url = qs ? `/api/invoices?${qs}` : '/api/invoices';
+ const response = await fetch(url);
invoices = await response.json();
renderInvoiceView();
loadLastSync();
@@ -599,6 +610,13 @@ export function injectToolbar() {
+
+
+
+
+