moving items
This commit is contained in:
parent
eef316402c
commit
6ca98dabd2
124
public/app.js
124
public/app.js
|
|
@ -411,18 +411,39 @@ function addQuoteItem(item = null) {
|
||||||
|
|
||||||
itemDiv.innerHTML = `
|
itemDiv.innerHTML = `
|
||||||
<!-- Accordion Header -->
|
<!-- Accordion Header -->
|
||||||
<div @click="open = !open" class="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
|
<div class="flex items-center p-4">
|
||||||
<div class="flex items-center space-x-4 flex-1">
|
<!-- Move Buttons (Left) - Outside accordion click area -->
|
||||||
<svg x-show="!open" class="w-5 h-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<div class="flex flex-col mr-3" onclick="event.stopPropagation()">
|
||||||
|
<button type="button" onclick="moveQuoteItemUp(${itemId})"
|
||||||
|
class="text-blue-600 hover:text-blue-800 text-lg leading-none mb-1"
|
||||||
|
title="Move Up">
|
||||||
|
↑
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="moveQuoteItemDown(${itemId})"
|
||||||
|
class="text-blue-600 hover:text-blue-800 text-lg leading-none"
|
||||||
|
title="Move Down">
|
||||||
|
↓
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Accordion Toggle & Content (Center) -->
|
||||||
|
<div @click="open = !open" class="flex items-center flex-1 cursor-pointer hover:bg-gray-50 rounded px-3 py-2">
|
||||||
|
<svg x-show="!open" class="w-5 h-5 text-gray-500 mr-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg x-show="open" class="w-5 h-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg x-show="open" class="w-5 h-5 text-gray-500 mr-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm font-medium">Qty: <span class="item-qty-preview">${previewQty}</span></span>
|
<span class="text-sm font-medium">Qty: <span class="item-qty-preview">${previewQty}</span></span>
|
||||||
<span class="text-sm text-gray-600 flex-1 truncate item-desc-preview">${previewDesc}</span>
|
<span class="text-sm text-gray-600 flex-1 truncate mx-4 item-desc-preview">${previewDesc}</span>
|
||||||
<span class="text-sm font-semibold item-amount-preview">${previewAmount}</span>
|
<span class="text-sm font-semibold item-amount-preview">${previewAmount}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Button (Right) - Outside accordion click area -->
|
||||||
|
<button type="button" onclick="removeQuoteItem(${itemId}); event.stopPropagation();"
|
||||||
|
class="ml-3 px-3 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Accordion Content -->
|
<!-- Accordion Content -->
|
||||||
|
|
@ -434,7 +455,7 @@ function addQuoteItem(item = null) {
|
||||||
value="${item ? item.quantity : ''}"
|
value="${item ? item.quantity : ''}"
|
||||||
class="quote-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
class="quote-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-5">
|
<div class="col-span-6">
|
||||||
<label class="block text-xs font-medium text-gray-700 mb-1">Description</label>
|
<label class="block text-xs font-medium text-gray-700 mb-1">Description</label>
|
||||||
<div data-item="${itemId}" data-field="description"
|
<div data-item="${itemId}" data-field="description"
|
||||||
class="quote-item-description-editor border border-gray-300 rounded-md bg-white"
|
class="quote-item-description-editor border border-gray-300 rounded-md bg-white"
|
||||||
|
|
@ -447,18 +468,12 @@ function addQuoteItem(item = null) {
|
||||||
value="${item ? item.rate : ''}"
|
value="${item ? item.rate : ''}"
|
||||||
class="quote-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
class="quote-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2">
|
<div class="col-span-3">
|
||||||
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
|
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
|
||||||
<input type="text" data-item="${itemId}" data-field="amount"
|
<input type="text" data-item="${itemId}" data-field="amount"
|
||||||
value="${item ? item.amount : ''}"
|
value="${item ? item.amount : ''}"
|
||||||
class="quote-item-amount w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
class="quote-item-amount w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1 flex items-end">
|
|
||||||
<button type="button" onclick="removeQuoteItem(${itemId})"
|
|
||||||
class="w-full px-2 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -539,6 +554,26 @@ function removeQuoteItem(itemId) {
|
||||||
updateQuoteTotals();
|
updateQuoteTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveQuoteItemUp(itemId) {
|
||||||
|
const item = document.getElementById(`quote-item-${itemId}`);
|
||||||
|
const prevItem = item.previousElementSibling;
|
||||||
|
|
||||||
|
if (prevItem) {
|
||||||
|
item.parentNode.insertBefore(item, prevItem);
|
||||||
|
updateQuoteTotals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveQuoteItemDown(itemId) {
|
||||||
|
const item = document.getElementById(`quote-item-${itemId}`);
|
||||||
|
const nextItem = item.nextElementSibling;
|
||||||
|
|
||||||
|
if (nextItem) {
|
||||||
|
item.parentNode.insertBefore(nextItem, item);
|
||||||
|
updateQuoteTotals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateQuoteTotals() {
|
function updateQuoteTotals() {
|
||||||
const items = getQuoteItems();
|
const items = getQuoteItems();
|
||||||
const taxExempt = document.getElementById('quote-tax-exempt').checked;
|
const taxExempt = document.getElementById('quote-tax-exempt').checked;
|
||||||
|
|
@ -786,18 +821,39 @@ function addInvoiceItem(item = null) {
|
||||||
|
|
||||||
itemDiv.innerHTML = `
|
itemDiv.innerHTML = `
|
||||||
<!-- Accordion Header -->
|
<!-- Accordion Header -->
|
||||||
<div @click="open = !open" class="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
|
<div class="flex items-center p-4">
|
||||||
<div class="flex items-center space-x-4 flex-1">
|
<!-- Move Buttons (Left) - Outside accordion click area -->
|
||||||
<svg x-show="!open" class="w-5 h-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<div class="flex flex-col mr-3" onclick="event.stopPropagation()">
|
||||||
|
<button type="button" onclick="moveInvoiceItemUp(${itemId})"
|
||||||
|
class="text-blue-600 hover:text-blue-800 text-lg leading-none mb-1"
|
||||||
|
title="Move Up">
|
||||||
|
↑
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="moveInvoiceItemDown(${itemId})"
|
||||||
|
class="text-blue-600 hover:text-blue-800 text-lg leading-none"
|
||||||
|
title="Move Down">
|
||||||
|
↓
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Accordion Toggle & Content (Center) -->
|
||||||
|
<div @click="open = !open" class="flex items-center flex-1 cursor-pointer hover:bg-gray-50 rounded px-3 py-2">
|
||||||
|
<svg x-show="!open" class="w-5 h-5 text-gray-500 mr-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg x-show="open" class="w-5 h-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg x-show="open" class="w-5 h-5 text-gray-500 mr-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm font-medium">Qty: <span class="item-qty-preview">${previewQty}</span></span>
|
<span class="text-sm font-medium">Qty: <span class="item-qty-preview">${previewQty}</span></span>
|
||||||
<span class="text-sm text-gray-600 flex-1 truncate item-desc-preview">${previewDesc}</span>
|
<span class="text-sm text-gray-600 flex-1 truncate mx-4 item-desc-preview">${previewDesc}</span>
|
||||||
<span class="text-sm font-semibold item-amount-preview">${previewAmount}</span>
|
<span class="text-sm font-semibold item-amount-preview">${previewAmount}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Button (Right) - Outside accordion click area -->
|
||||||
|
<button type="button" onclick="removeInvoiceItem(${itemId}); event.stopPropagation();"
|
||||||
|
class="ml-3 px-3 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Accordion Content -->
|
<!-- Accordion Content -->
|
||||||
|
|
@ -809,7 +865,7 @@ function addInvoiceItem(item = null) {
|
||||||
value="${item ? item.quantity : ''}"
|
value="${item ? item.quantity : ''}"
|
||||||
class="invoice-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
class="invoice-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-5">
|
<div class="col-span-6">
|
||||||
<label class="block text-xs font-medium text-gray-700 mb-1">Description</label>
|
<label class="block text-xs font-medium text-gray-700 mb-1">Description</label>
|
||||||
<div data-item="${itemId}" data-field="description"
|
<div data-item="${itemId}" data-field="description"
|
||||||
class="invoice-item-description-editor border border-gray-300 rounded-md bg-white"
|
class="invoice-item-description-editor border border-gray-300 rounded-md bg-white"
|
||||||
|
|
@ -822,18 +878,12 @@ function addInvoiceItem(item = null) {
|
||||||
value="${item ? item.rate : ''}"
|
value="${item ? item.rate : ''}"
|
||||||
class="invoice-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
class="invoice-item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2">
|
<div class="col-span-3">
|
||||||
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
|
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
|
||||||
<input type="text" data-item="${itemId}" data-field="amount"
|
<input type="text" data-item="${itemId}" data-field="amount"
|
||||||
value="${item ? item.amount : ''}"
|
value="${item ? item.amount : ''}"
|
||||||
class="invoice-item-amount w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
class="invoice-item-amount w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1 flex items-end">
|
|
||||||
<button type="button" onclick="removeInvoiceItem(${itemId})"
|
|
||||||
class="w-full px-2 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -914,6 +964,26 @@ function removeInvoiceItem(itemId) {
|
||||||
updateInvoiceTotals();
|
updateInvoiceTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveInvoiceItemUp(itemId) {
|
||||||
|
const item = document.getElementById(`invoice-item-${itemId}`);
|
||||||
|
const prevItem = item.previousElementSibling;
|
||||||
|
|
||||||
|
if (prevItem) {
|
||||||
|
item.parentNode.insertBefore(item, prevItem);
|
||||||
|
updateInvoiceTotals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveInvoiceItemDown(itemId) {
|
||||||
|
const item = document.getElementById(`invoice-item-${itemId}`);
|
||||||
|
const nextItem = item.nextElementSibling;
|
||||||
|
|
||||||
|
if (nextItem) {
|
||||||
|
item.parentNode.insertBefore(nextItem, item);
|
||||||
|
updateInvoiceTotals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateInvoiceTotals() {
|
function updateInvoiceTotals() {
|
||||||
const items = getInvoiceItems();
|
const items = getInvoiceItems();
|
||||||
const taxExempt = document.getElementById('invoice-tax-exempt').checked;
|
const taxExempt = document.getElementById('invoice-tax-exempt').checked;
|
||||||
|
|
@ -1020,4 +1090,4 @@ async function deleteInvoice(id) {
|
||||||
|
|
||||||
function viewInvoicePDF(id) {
|
function viewInvoicePDF(id) {
|
||||||
window.open(`/api/invoices/${id}/pdf`, '_blank');
|
window.open(`/api/invoices/${id}/pdf`, '_blank');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue