fix
This commit is contained in:
@@ -67,9 +67,14 @@
|
|||||||
<div id="invoices-tab" class="tab-content hidden">
|
<div id="invoices-tab" class="tab-content hidden">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h2 class="text-3xl font-bold text-gray-800">Invoices</h2>
|
<h2 class="text-3xl font-bold text-gray-800">Invoices</h2>
|
||||||
<button onclick="openInvoiceModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-semibold shadow-md">
|
<div class="flex items-center gap-3">
|
||||||
+ New Invoice
|
<button onclick="window.invoiceView.syncFromQBO()" class="px-3 py-2 bg-indigo-600 text-white rounded-lg text-xs font-medium hover:bg-indigo-700">
|
||||||
</button>
|
⟳ Sync from QBO
|
||||||
|
</button>
|
||||||
|
<button onclick="openInvoiceModal()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-semibold shadow-md">
|
||||||
|
+ New Invoice
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="invoice-toolbar"></div>
|
<div id="invoice-toolbar"></div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ let filterCustomer = localStorage.getItem('inv_filterCustomer') || '';
|
|||||||
let filterStatus = localStorage.getItem('inv_filterStatus') || 'unpaid';
|
let filterStatus = localStorage.getItem('inv_filterStatus') || 'unpaid';
|
||||||
let groupBy = localStorage.getItem('inv_groupBy') || 'none';
|
let groupBy = localStorage.getItem('inv_groupBy') || 'none';
|
||||||
let filterWorker = localStorage.getItem('inv_filterWorker') || '';
|
let filterWorker = localStorage.getItem('inv_filterWorker') || '';
|
||||||
let filterItemType = localStorage.getItem('inv_filterItemType') === 'true';
|
let filterParts = localStorage.getItem('inv_filterParts') === 'true';
|
||||||
let filterEmptyCost = localStorage.getItem('inv_filterEmptyCost') === 'true';
|
let filterEmptyCost = localStorage.getItem('inv_filterEmptyCost') === 'true';
|
||||||
|
|
||||||
const OVERDUE_DAYS = 30;
|
const OVERDUE_DAYS = 30;
|
||||||
@@ -198,7 +198,7 @@ function saveSettings() {
|
|||||||
localStorage.setItem('inv_groupBy', groupBy);
|
localStorage.setItem('inv_groupBy', groupBy);
|
||||||
localStorage.setItem('inv_filterCustomer', filterCustomer);
|
localStorage.setItem('inv_filterCustomer', filterCustomer);
|
||||||
localStorage.setItem('inv_filterWorker', filterWorker);
|
localStorage.setItem('inv_filterWorker', filterWorker);
|
||||||
localStorage.setItem('inv_filterItemType', filterItemType);
|
localStorage.setItem('inv_filterParts', filterParts);
|
||||||
localStorage.setItem('inv_filterEmptyCost', filterEmptyCost);
|
localStorage.setItem('inv_filterEmptyCost', filterEmptyCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,8 +209,8 @@ function saveSettings() {
|
|||||||
export async function loadInvoices() {
|
export async function loadInvoices() {
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (filterItemType) {
|
if (filterParts) {
|
||||||
params.set('has_parts_or_subscription', 'true');
|
params.set('has_parts', 'true');
|
||||||
if (filterEmptyCost) params.set('empty_cost_only', 'true');
|
if (filterEmptyCost) params.set('empty_cost_only', 'true');
|
||||||
}
|
}
|
||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
@@ -611,17 +611,12 @@ export function injectToolbar() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-px h-8 bg-gray-300"></div>
|
<div class="w-px h-8 bg-gray-300"></div>
|
||||||
<div class="flex items-center gap-1 border border-gray-300 rounded-lg p-1 bg-gray-100">
|
<div class="flex items-center gap-1 border border-gray-300 rounded-lg p-1 bg-gray-100">
|
||||||
<button id="filter-item-type" onclick="window.invoiceView.toggleItemType()"
|
<button id="filter-item-type" onclick="window.invoiceView.toggleParts()"
|
||||||
class="px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${filterItemType ? 'bg-blue-500 text-white' : 'bg-white text-gray-600'}">Cost Items</button>
|
class="px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${filterParts ? 'bg-blue-500 text-white' : 'bg-white text-gray-600'}">Cost Items</button>
|
||||||
<button id="filter-empty-cost" onclick="window.invoiceView.toggleEmptyCost()" style="${filterItemType ? '' : 'display:none'}"
|
<button id="filter-empty-cost" onclick="window.invoiceView.toggleEmptyCost()" style="${filterParts ? '' : 'display:none'}"
|
||||||
class="px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${filterEmptyCost ? 'bg-blue-500 text-white' : 'bg-white text-gray-600'}">Empty cost only</button>
|
class="px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${filterEmptyCost ? 'bg-blue-500 text-white' : 'bg-white text-gray-600'}">Empty cost only</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px h-8 bg-gray-300"></div>
|
<div class="w-px h-8 bg-gray-300"></div>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button onclick="window.invoiceView.syncFromQBO()" class="px-3 py-1.5 bg-indigo-600 text-white rounded-md text-xs font-medium hover:bg-indigo-700">
|
|
||||||
⟳ Sync from QBO
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="ml-auto flex items-center gap-4">
|
<div class="ml-auto flex items-center gap-4">
|
||||||
<span id="last-sync-time" class="text-xs text-gray-400">...</span>
|
<span id="last-sync-time" class="text-xs text-gray-400">...</span>
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">
|
||||||
@@ -649,19 +644,19 @@ export function injectToolbar() {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
export function setStatus(s) { filterStatus = s; saveSettings(); renderInvoiceView(); }
|
export function setStatus(s) { filterStatus = s; saveSettings(); renderInvoiceView(); }
|
||||||
export function toggleItemType() {
|
export function toggleParts() {
|
||||||
filterItemType = !filterItemType;
|
filterParts = !filterParts;
|
||||||
if (!filterItemType) filterEmptyCost = false;
|
if (!filterParts) filterEmptyCost = false;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
const itemBtn = document.getElementById('filter-item-type');
|
const itemBtn = document.getElementById('filter-item-type');
|
||||||
const emptyBtn = document.getElementById('filter-empty-cost');
|
const emptyBtn = document.getElementById('filter-empty-cost');
|
||||||
if (itemBtn) {
|
if (itemBtn) {
|
||||||
itemBtn.className = filterItemType
|
itemBtn.className = filterParts
|
||||||
? 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-blue-500 text-white'
|
? 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-blue-500 text-white'
|
||||||
: 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-white text-gray-600';
|
: 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-white text-gray-600';
|
||||||
}
|
}
|
||||||
if (emptyBtn) {
|
if (emptyBtn) {
|
||||||
emptyBtn.style.display = filterItemType ? '' : 'none';
|
emptyBtn.style.display = filterParts ? '' : 'none';
|
||||||
emptyBtn.className = filterEmptyCost
|
emptyBtn.className = filterEmptyCost
|
||||||
? 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-blue-500 text-white'
|
? 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-blue-500 text-white'
|
||||||
: 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-white text-gray-600';
|
: 'px-3 py-1.5 text-xs font-medium rounded-md transition-colors bg-white text-gray-600';
|
||||||
@@ -669,7 +664,7 @@ export function toggleItemType() {
|
|||||||
loadInvoices();
|
loadInvoices();
|
||||||
}
|
}
|
||||||
export function toggleEmptyCost() {
|
export function toggleEmptyCost() {
|
||||||
if (!filterItemType) return;
|
if (!filterParts) return;
|
||||||
filterEmptyCost = !filterEmptyCost;
|
filterEmptyCost = !filterEmptyCost;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
const btn = document.getElementById('filter-empty-cost');
|
const btn = document.getElementById('filter-empty-cost');
|
||||||
@@ -920,5 +915,5 @@ async function populateWorkerFilter() {
|
|||||||
|
|
||||||
window.invoiceView = {
|
window.invoiceView = {
|
||||||
viewPDF, viewHTML, syncFromQBO, resetQbo, markPaid, setEmailStatus, edit, remove,
|
viewPDF, viewHTML, syncFromQBO, resetQbo, markPaid, setEmailStatus, edit, remove,
|
||||||
loadInvoices, renderInvoiceView, setStatus, toggleItemType, toggleEmptyCost, checkStripePayment, editSentDates ,_addSentDateRow, _saveSentDates
|
loadInvoices, renderInvoiceView, setStatus, toggleParts, toggleEmptyCost, checkStripePayment, editSentDates ,_addSentDateRow, _saveSentDates
|
||||||
};
|
};
|
||||||
@@ -50,17 +50,17 @@ function buildPaymentLinkHtml(invoice) {
|
|||||||
// GET all invoices
|
// GET all invoices
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { has_parts_or_subscription, empty_cost_only } = req.query;
|
const { has_parts, empty_cost_only } = req.query;
|
||||||
|
|
||||||
let whereClauses = [];
|
let whereClauses = [];
|
||||||
if (has_parts_or_subscription === 'true') {
|
if (has_parts === 'true') {
|
||||||
const costCondition = empty_cost_only === 'true'
|
const costCondition = empty_cost_only === 'true'
|
||||||
? `AND (ii.unit_cost IS NULL OR ii.unit_cost = '')`
|
? `AND (ii.unit_cost IS NULL OR ii.unit_cost = '')`
|
||||||
: '';
|
: '';
|
||||||
whereClauses.push(`EXISTS (
|
whereClauses.push(`EXISTS (
|
||||||
SELECT 1 FROM invoice_items ii
|
SELECT 1 FROM invoice_items ii
|
||||||
WHERE ii.invoice_id = i.id
|
WHERE ii.invoice_id = i.id
|
||||||
AND (ii.qbo_item_id = '9' OR ii.qbo_item_id = '115')
|
AND ii.qbo_item_id = '9'
|
||||||
${costCondition}
|
${costCondition}
|
||||||
)`);
|
)`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user