502 lines
20 KiB
HTML
502 lines
20 KiB
HTML
{{define "content"}}
|
|
<div class="container">
|
|
<div class="columns">
|
|
<div class="column">
|
|
<nav class="breadcrumb" aria-label="breadcrumbs">
|
|
<ul>
|
|
<li><a href="/documents">Documents</a></li>
|
|
<li class="is-active"><a href="#" aria-current="page">{{.Document.Type}} #{{.Document.ID}}</a></li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Include document type specific view -->
|
|
{{if eq .DocType "quote"}}
|
|
{{template "document-quote-view" .}}
|
|
{{else if eq .DocType "invoice"}}
|
|
{{template "document-invoice-view" .}}
|
|
{{else if eq .DocType "purchaseOrder"}}
|
|
{{template "document-purchase-order-view" .}}
|
|
{{else if eq .DocType "orderAck"}}
|
|
{{template "document-orderack-view" .}}
|
|
{{else if eq .DocType "packingList"}}
|
|
{{template "document-packinglist-view" .}}
|
|
{{else}}
|
|
<div class="notification is-warning">
|
|
Unknown document type: {{.DocType}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Line Items Section -->
|
|
<div class="box mt-5">
|
|
<h2 class="title is-4">Line Items</h2>
|
|
<div id="line-items-table">
|
|
<table class="table is-fullwidth is-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Item #</th>
|
|
<th>Title</th>
|
|
<th>Description</th>
|
|
<th>Quantity</th>
|
|
<th>Unit Price</th>
|
|
<th>Total Price</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="line-items-tbody">
|
|
{{if .LineItems}}
|
|
{{range .LineItems}}
|
|
<tr id="line-item-{{.ID}}">
|
|
<td>{{.ItemNumber}}</td>
|
|
<td>{{.Title}}</td>
|
|
<td>{{.Description}}</td>
|
|
<td>{{.Quantity}}</td>
|
|
<td>
|
|
{{if .GrossUnitPrice.Valid}}
|
|
${{.GrossUnitPrice.String}}
|
|
{{else if .UnitPriceString.Valid}}
|
|
{{.UnitPriceString.String}}
|
|
{{else}}
|
|
-
|
|
{{end}}
|
|
</td>
|
|
<td>
|
|
{{if .GrossPrice.Valid}}
|
|
${{.GrossPrice.String}}
|
|
{{else if .GrossPriceString.Valid}}
|
|
{{.GrossPriceString.String}}
|
|
{{else}}
|
|
-
|
|
{{end}}
|
|
</td>
|
|
<td>
|
|
<button class="button is-small is-primary" onclick="editLineItem({{.ID}})">
|
|
<span class="icon">
|
|
<i class="fas fa-edit"></i>
|
|
</span>
|
|
<span>Edit</span>
|
|
</button>
|
|
<button class="button is-small is-danger" onclick="deleteLineItem({{.ID}})">
|
|
<span class="icon">
|
|
<i class="fas fa-trash"></i>
|
|
</span>
|
|
<span>Delete</span>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
{{else}}
|
|
<tr id="no-line-items">
|
|
<td colspan="7" class="has-text-centered has-text-grey">
|
|
No line items yet
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<button class="button is-primary" onclick="showAddLineItemForm()">
|
|
<span class="icon">
|
|
<i class="fas fa-plus"></i>
|
|
</span>
|
|
<span>Add Line Item</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Add Line Item Modal -->
|
|
<div class="modal" id="add-line-item-modal">
|
|
<div class="modal-background" onclick="hideAddLineItemForm()"></div>
|
|
<div class="modal-card">
|
|
<header class="modal-card-head">
|
|
<p class="modal-card-title">Add Line Item</p>
|
|
<button class="delete" aria-label="close" onclick="hideAddLineItemForm()"></button>
|
|
</header>
|
|
<section class="modal-card-body">
|
|
<form id="add-line-item-form">
|
|
<input type="hidden" name="document_id" value="{{.Document.ID}}">
|
|
|
|
<div class="field">
|
|
<label class="label">Title</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="title" placeholder="Line item title" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Description</label>
|
|
<div class="control">
|
|
<textarea class="textarea" name="description" placeholder="Line item description" required></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns">
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label">Quantity</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="quantity" placeholder="1.00" required>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label">Unit Price</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="gross_unit_price" placeholder="0.00">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label">Total Price</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="gross_price" placeholder="0.00">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="control">
|
|
<label class="checkbox">
|
|
<input type="checkbox" name="option" value="1">
|
|
Optional item
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
<footer class="modal-card-foot">
|
|
<button class="button is-primary" onclick="submitLineItem()">Add Line Item</button>
|
|
<button class="button" onclick="hideAddLineItemForm()">Cancel</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Line Item Modal -->
|
|
<div class="modal" id="edit-line-item-modal">
|
|
<div class="modal-background" onclick="hideEditLineItemForm()"></div>
|
|
<div class="modal-card">
|
|
<header class="modal-card-head">
|
|
<p class="modal-card-title">Edit Line Item</p>
|
|
<button class="delete" aria-label="close" onclick="hideEditLineItemForm()"></button>
|
|
</header>
|
|
<section class="modal-card-body">
|
|
<form id="edit-line-item-form">
|
|
<input type="hidden" name="id" id="edit-line-item-id">
|
|
<input type="hidden" name="document_id" value="{{.Document.ID}}">
|
|
|
|
<div class="field">
|
|
<label class="label">Item Number</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="item_number" id="edit-item-number" placeholder="1.00" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Title</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="title" id="edit-title" placeholder="Line item title" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Description</label>
|
|
<div class="control">
|
|
<textarea class="textarea" name="description" id="edit-description" placeholder="Line item description" required></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns">
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label">Quantity</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="quantity" id="edit-quantity" placeholder="1.00" required oninput="calculateEditTotal()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label">Unit Price</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="gross_unit_price" id="edit-unit-price" placeholder="0.00" oninput="calculateEditTotal()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label">Total Price</label>
|
|
<div class="control">
|
|
<input class="input" type="text" name="gross_price" id="edit-total-price" placeholder="0.00">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="control">
|
|
<label class="checkbox">
|
|
<input type="checkbox" name="option" id="edit-option" value="1">
|
|
Optional item
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
<footer class="modal-card-foot">
|
|
<button class="button is-primary" onclick="submitEditLineItem()">Update Line Item</button>
|
|
<button class="button" onclick="hideEditLineItemForm()">Cancel</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Attachments Section -->
|
|
<div class="box mt-5">
|
|
<h2 class="title is-4">Attachments</h2>
|
|
<div id="attachments-table">
|
|
<table class="table is-fullwidth is-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Filename</th>
|
|
<th>Name</th>
|
|
<th>Description</th>
|
|
<th>Size</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td colspan="5" class="has-text-centered has-text-grey">
|
|
No attachments yet
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<button class="button is-info" onclick="addAttachment()">
|
|
<span class="icon">
|
|
<i class="fas fa-paperclip"></i>
|
|
</span>
|
|
<span>Add Attachment</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function showAddLineItemForm() {
|
|
document.getElementById('add-line-item-modal').classList.add('is-active');
|
|
}
|
|
|
|
function hideAddLineItemForm() {
|
|
document.getElementById('add-line-item-modal').classList.remove('is-active');
|
|
document.getElementById('add-line-item-form').reset();
|
|
}
|
|
|
|
function hideEditLineItemForm() {
|
|
document.getElementById('edit-line-item-modal').classList.remove('is-active');
|
|
document.getElementById('edit-line-item-form').reset();
|
|
}
|
|
|
|
function submitEditLineItem() {
|
|
const form = document.getElementById('edit-line-item-form');
|
|
const formData = new FormData(form);
|
|
|
|
fetch('/line_items/ajax_edit', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
if (data === 'SUCCESS') {
|
|
hideEditLineItemForm();
|
|
refreshLineItemsTable();
|
|
showNotification('Line item updated successfully', 'is-success');
|
|
} else {
|
|
showNotification('Failed to update line item', 'is-danger');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showNotification('Error updating line item', 'is-danger');
|
|
});
|
|
}
|
|
|
|
function submitLineItem() {
|
|
const form = document.getElementById('add-line-item-form');
|
|
const formData = new FormData(form);
|
|
|
|
fetch('/line_items/ajax_add', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
if (data === 'SUCCESS') {
|
|
hideAddLineItemForm();
|
|
refreshLineItemsTable();
|
|
showNotification('Line item added successfully', 'is-success');
|
|
} else {
|
|
showNotification('Failed to add line item', 'is-danger');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showNotification('Error adding line item', 'is-danger');
|
|
});
|
|
}
|
|
|
|
function editLineItem(id) {
|
|
// Fetch line item data
|
|
fetch(`/api/v1/line-items/${id}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Populate the edit form
|
|
document.getElementById('edit-line-item-id').value = data.id;
|
|
document.getElementById('edit-item-number').value = data.item_number;
|
|
document.getElementById('edit-title').value = data.title;
|
|
document.getElementById('edit-description').value = data.description;
|
|
document.getElementById('edit-quantity').value = data.quantity;
|
|
|
|
// Handle price fields - check both numeric and string versions
|
|
if (data.gross_unit_price && data.gross_unit_price.Valid) {
|
|
document.getElementById('edit-unit-price').value = data.gross_unit_price.String;
|
|
} else {
|
|
document.getElementById('edit-unit-price').value = '';
|
|
}
|
|
|
|
if (data.gross_price && data.gross_price.Valid) {
|
|
document.getElementById('edit-total-price').value = data.gross_price.String;
|
|
} else {
|
|
document.getElementById('edit-total-price').value = '';
|
|
}
|
|
|
|
// Set checkbox
|
|
document.getElementById('edit-option').checked = data.option;
|
|
|
|
// Show the modal
|
|
document.getElementById('edit-line-item-modal').classList.add('is-active');
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching line item:', error);
|
|
showNotification('Error loading line item data', 'is-danger');
|
|
});
|
|
}
|
|
|
|
function deleteLineItem(id) {
|
|
if (confirm('Are you sure you want to delete this line item?')) {
|
|
fetch(`/line_items/ajax_delete/${id}`, {
|
|
method: 'POST'
|
|
})
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
if (data === 'SUCCESS') {
|
|
document.getElementById(`line-item-${id}`).remove();
|
|
|
|
// Check if there are no more line items
|
|
const tbody = document.getElementById('line-items-tbody');
|
|
if (tbody.children.length === 0) {
|
|
tbody.innerHTML = '<tr id="no-line-items"><td colspan="7" class="has-text-centered has-text-grey">No line items yet</td></tr>';
|
|
}
|
|
|
|
showNotification('Line item deleted successfully', 'is-success');
|
|
} else {
|
|
showNotification('Failed to delete line item', 'is-danger');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showNotification('Error deleting line item', 'is-danger');
|
|
});
|
|
}
|
|
}
|
|
|
|
function refreshLineItemsTable() {
|
|
const documentId = {{.Document.ID}};
|
|
fetch(`/line_items/get_table/${documentId}`, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
// The response is already the tbody content
|
|
document.getElementById('line-items-tbody').innerHTML = html;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error refreshing line items:', error);
|
|
});
|
|
}
|
|
|
|
function showNotification(message, type) {
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type} is-light`;
|
|
notification.innerHTML = `
|
|
<button class="delete" onclick="this.parentElement.remove()"></button>
|
|
${message}
|
|
`;
|
|
|
|
// Add to page
|
|
const container = document.querySelector('.container');
|
|
container.insertBefore(notification, container.firstChild);
|
|
|
|
// Auto-remove after 5 seconds
|
|
setTimeout(() => {
|
|
if (notification.parentElement) {
|
|
notification.remove();
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
function addAttachment() {
|
|
// TODO: Implement attachment modal
|
|
alert('Add attachment functionality coming soon');
|
|
}
|
|
|
|
function generatePDF() {
|
|
const documentId = {{.Document.ID}};
|
|
const currentUrl = window.location.href;
|
|
|
|
// Show loading state
|
|
const buttons = document.querySelectorAll('button');
|
|
const originalTexts = new Map();
|
|
buttons.forEach(btn => {
|
|
if (btn.onclick && btn.onclick.toString().includes('generatePDF')) {
|
|
originalTexts.set(btn, btn.innerHTML);
|
|
btn.innerHTML = '<span class="icon"><i class="fas fa-spinner fa-spin"></i></span><span>Generating PDF...</span>';
|
|
btn.disabled = true;
|
|
}
|
|
});
|
|
|
|
// Call PDF generation endpoint
|
|
window.location.href = `/documents/pdf/${documentId}`;
|
|
|
|
// Reset button state after a delay (in case user stays on page)
|
|
setTimeout(() => {
|
|
buttons.forEach(btn => {
|
|
if (originalTexts.has(btn)) {
|
|
btn.innerHTML = originalTexts.get(btn);
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
}, 3000);
|
|
}
|
|
|
|
function emailDocument() {
|
|
// TODO: Implement email functionality
|
|
alert('Email functionality coming soon');
|
|
}
|
|
|
|
function calculateEditTotal() {
|
|
const quantity = parseFloat(document.getElementById('edit-quantity').value) || 0;
|
|
const unitPrice = parseFloat(document.getElementById('edit-unit-price').value) || 0;
|
|
const total = quantity * unitPrice;
|
|
|
|
if (total > 0) {
|
|
document.getElementById('edit-total-price').value = total.toFixed(2);
|
|
}
|
|
}
|
|
</script>
|
|
{{end}} |