klapper
This commit is contained in:
parent
209292a61a
commit
f892450914
150
public/app.js
150
public/app.js
|
|
@ -306,47 +306,71 @@ function addQuoteItem(item = null) {
|
|||
const itemsDiv = document.getElementById('quote-items');
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'grid grid-cols-12 gap-3 items-start';
|
||||
itemDiv.className = 'border border-gray-300 rounded-lg mb-3';
|
||||
itemDiv.id = `item-${itemId}`;
|
||||
|
||||
// Create summary text
|
||||
const summaryQty = item ? item.quantity : '';
|
||||
const summaryDesc = item ? (item.description.replace(/<[^>]*>/g, '').substring(0, 50) + '...') : 'New item';
|
||||
const summaryAmount = item ? item.amount : '';
|
||||
|
||||
itemDiv.innerHTML = `
|
||||
<div class="col-span-1">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Qty</label>
|
||||
<input type="text" data-item="${itemId}" data-field="quantity"
|
||||
value="${item ? item.quantity : ''}"
|
||||
class="item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="col-span-5">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Description</label>
|
||||
<div data-item="${itemId}" data-field="description"
|
||||
class="item-description-editor border border-gray-300 rounded-md bg-white"
|
||||
style="min-height: 80px;">
|
||||
<!-- Summary Header (always visible) -->
|
||||
<div class="item-header bg-gray-50 px-4 py-3 cursor-pointer flex items-center justify-between" onclick="toggleItem(${itemId})">
|
||||
<div class="flex items-center space-x-4 flex-1">
|
||||
<span class="text-sm font-medium text-gray-700">Qty: <span class="item-summary-qty">${summaryQty}</span></span>
|
||||
<span class="text-sm text-gray-600 flex-1 truncate item-summary-desc">${summaryDesc}</span>
|
||||
<span class="text-sm font-medium text-gray-900">$<span class="item-summary-amount">${summaryAmount}</span></span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="item-chevron w-5 h-5 text-gray-500 transition-transform" 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" />
|
||||
</svg>
|
||||
</div>
|
||||
<input type="hidden" data-item="${itemId}" data-field="description-html" class="item-description-html">
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Rate</label>
|
||||
<input type="text" data-item="${itemId}" data-field="rate"
|
||||
value="${item ? item.rate : ''}"
|
||||
class="item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
|
||||
<input type="text" data-item="${itemId}" data-field="amount"
|
||||
value="${item ? item.amount : ''}"
|
||||
class="item-amount w-full px-2 py-2 border border-gray-300 rounded-md text-sm focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">TBD</label>
|
||||
<input type="checkbox" data-item="${itemId}" data-field="is_tbd"
|
||||
${item && item.is_tbd ? 'checked' : ''}
|
||||
class="item-tbd h-5 w-5 mt-2 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
</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>
|
||||
|
||||
<!-- Expandable Content (hidden by default) -->
|
||||
<div class="item-content hidden">
|
||||
<div class="grid grid-cols-12 gap-3 items-start p-4">
|
||||
<div class="col-span-1">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Qty</label>
|
||||
<input type="text" data-item="${itemId}" data-field="quantity"
|
||||
value="${item ? item.quantity : ''}"
|
||||
class="item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="col-span-5">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Description</label>
|
||||
<div data-item="${itemId}" data-field="description"
|
||||
class="item-description-editor border border-gray-300 rounded-md bg-white"
|
||||
style="min-height: 80px;">
|
||||
</div>
|
||||
<input type="hidden" data-item="${itemId}" data-field="description-html" class="item-description-html">
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Rate</label>
|
||||
<input type="text" data-item="${itemId}" data-field="rate"
|
||||
value="${item ? item.rate : ''}"
|
||||
class="item-input w-full px-2 py-2 border border-gray-300 rounded-md text-sm focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
|
||||
<input type="text" data-item="${itemId}" data-field="amount"
|
||||
value="${item ? item.amount : ''}"
|
||||
class="item-amount w-full px-2 py-2 border border-gray-300 rounded-md text-sm focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">TBD</label>
|
||||
<input type="checkbox" data-item="${itemId}" data-field="is_tbd"
|
||||
${item && item.is_tbd ? 'checked' : ''}
|
||||
class="item-tbd h-5 w-5 mt-2 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
</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>
|
||||
`;
|
||||
|
||||
|
|
@ -375,6 +399,7 @@ function addQuoteItem(item = null) {
|
|||
// Save HTML content on change
|
||||
quill.on('text-change', () => {
|
||||
hiddenInput.value = quill.root.innerHTML;
|
||||
updateItemSummary(itemId);
|
||||
updateTotals();
|
||||
});
|
||||
|
||||
|
|
@ -396,12 +421,17 @@ function addQuoteItem(item = null) {
|
|||
const amount = qty * rateValue;
|
||||
amountInput.value = amount.toFixed(2);
|
||||
}
|
||||
updateItemSummary(itemId);
|
||||
updateTotals();
|
||||
};
|
||||
|
||||
// Add event listeners for auto-calculation
|
||||
// Add event listeners for auto-calculation and summary update
|
||||
qtyInput.addEventListener('input', calculateAmount);
|
||||
rateInput.addEventListener('input', calculateAmount);
|
||||
amountInput.addEventListener('input', () => {
|
||||
updateItemSummary(itemId);
|
||||
updateTotals();
|
||||
});
|
||||
|
||||
// Add event listeners for totals update
|
||||
itemDiv.querySelectorAll('.item-input, .item-amount').forEach(input => {
|
||||
|
|
@ -438,6 +468,41 @@ function removeQuoteItem(itemId) {
|
|||
updateTotals();
|
||||
}
|
||||
|
||||
function toggleItem(itemId) {
|
||||
const itemDiv = document.getElementById(`item-${itemId}`);
|
||||
const content = itemDiv.querySelector('.item-content');
|
||||
const chevron = itemDiv.querySelector('.item-chevron');
|
||||
|
||||
if (content.classList.contains('hidden')) {
|
||||
content.classList.remove('hidden');
|
||||
chevron.style.transform = 'rotate(180deg)';
|
||||
} else {
|
||||
content.classList.add('hidden');
|
||||
chevron.style.transform = 'rotate(0deg)';
|
||||
}
|
||||
}
|
||||
|
||||
function updateItemSummary(itemId) {
|
||||
const itemDiv = document.getElementById(`item-${itemId}`);
|
||||
const qtyInput = itemDiv.querySelector('[data-field="quantity"]');
|
||||
const amountInput = itemDiv.querySelector('[data-field="amount"]');
|
||||
const descEditor = itemDiv.querySelector('.item-description-editor');
|
||||
|
||||
// Update summary displays
|
||||
const summaryQty = itemDiv.querySelector('.item-summary-qty');
|
||||
const summaryDesc = itemDiv.querySelector('.item-summary-desc');
|
||||
const summaryAmount = itemDiv.querySelector('.item-summary-amount');
|
||||
|
||||
if (summaryQty) summaryQty.textContent = qtyInput.value || '0';
|
||||
if (summaryAmount) summaryAmount.textContent = amountInput.value || '0.00';
|
||||
|
||||
if (summaryDesc && descEditor.quillInstance) {
|
||||
const plainText = descEditor.quillInstance.getText().trim();
|
||||
const preview = plainText.substring(0, 60) + (plainText.length > 60 ? '...' : '');
|
||||
summaryDesc.textContent = preview || 'New item';
|
||||
}
|
||||
}
|
||||
|
||||
function updateTotals() {
|
||||
const items = getQuoteItems();
|
||||
const taxExempt = document.getElementById('quote-tax-exempt').checked;
|
||||
|
|
@ -474,17 +539,18 @@ function getQuoteItems() {
|
|||
const itemDivs = document.querySelectorAll('#quote-items > div');
|
||||
|
||||
itemDivs.forEach(div => {
|
||||
const descEditor = div.querySelector('.item-description-editor');
|
||||
const content = div.querySelector('.item-content');
|
||||
const descEditor = content.querySelector('.item-description-editor');
|
||||
const descriptionHTML = descEditor && descEditor.quillInstance
|
||||
? descEditor.quillInstance.root.innerHTML
|
||||
: '';
|
||||
|
||||
const item = {
|
||||
quantity: div.querySelector('[data-field="quantity"]').value,
|
||||
quantity: content.querySelector('[data-field="quantity"]').value,
|
||||
description: descriptionHTML,
|
||||
rate: div.querySelector('[data-field="rate"]').value,
|
||||
amount: div.querySelector('[data-field="amount"]').value,
|
||||
is_tbd: div.querySelector('[data-field="is_tbd"]').checked
|
||||
rate: content.querySelector('[data-field="rate"]').value,
|
||||
amount: content.querySelector('[data-field="amount"]').value,
|
||||
is_tbd: content.querySelector('[data-field="is_tbd"]').checked
|
||||
};
|
||||
items.push(item);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue