You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
3.8 KiB
86 lines
3.8 KiB
{{define "generic_content"}}
|
|
<div class="page-header">
|
|
<h2>Generic Tools</h2>
|
|
<p>Miscellaneous tools and utilities for ServiceTrade operations.</p>
|
|
</div>
|
|
|
|
<div class="page-content">
|
|
<div class="content">
|
|
<h3 class="submenu-header">Generic Tools & Utilities</h3>
|
|
|
|
<section class="card">
|
|
<h4>Invoice Clock Events Report (Proof of Concept)</h4>
|
|
<p>
|
|
Upload a CSV export that contains either job ids or invoice numbers. The tool automatically detects
|
|
which identifier is usable, gathers the related clock activity, and returns an Excel workbook formatted
|
|
like the shared audit template (Customer PO, id, Link, Clock In, Clock Out) with the header row frozen
|
|
and columns expanded.
|
|
</p>
|
|
|
|
<form id="invoice-clock-form" class="form" action="/reports/invoice-clock" method="POST"
|
|
enctype="multipart/form-data" hx-target="#invoice-clock-status" hx-indicator=".htmx-indicator">
|
|
{{if .CSRFField}}
|
|
{{.CSRFField}}
|
|
{{end}}
|
|
<input type="hidden" name="csrfToken" id="generic-csrf-token" value="{{.CSRFCookie}}">
|
|
<div class="form-group">
|
|
<label for="invoiceCsv">Select CSV file</label>
|
|
<input class="card-input" type="file" id="invoiceCsv" name="invoiceCsv" accept=".csv" required>
|
|
</div>
|
|
<p class="help-text">
|
|
If a <code>job id</code> column is present we use it directly; otherwise we fall back to columns such as
|
|
<code>invoice_number</code> or <code>customer po</code> to discover the job before pulling clock events.
|
|
</p>
|
|
<button class="btn-primary" type="submit">Generate XLSX Report</button>
|
|
</form>
|
|
<div class="status-message subtle-tip">
|
|
Generating very large reports can take a few minutes after you submit. Please keep the tab open while the download prepares.
|
|
</div>
|
|
<div id="invoice-clock-indicator" class="htmx-indicator status-indicator" style="display:none;">
|
|
<div class="spinner"></div>
|
|
<div>
|
|
<strong>Building report…</strong>
|
|
<p>Hang tight while we fetch clock events and assemble the workbook.</p>
|
|
</div>
|
|
</div>
|
|
<div id="invoice-clock-status" class="status-message"></div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
function readCookie(name) {
|
|
var re = new RegExp('(?:^|; )' + name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '=([^;]+)');
|
|
var m = document.cookie.match(re);
|
|
return m ? decodeURIComponent(m[1]) : null;
|
|
}
|
|
|
|
function applyToken(root) {
|
|
var form = root.querySelector ? root.querySelector('#invoice-clock-form') : document.getElementById('invoice-clock-form');
|
|
if (!form) { return; }
|
|
var field = form.querySelector('#generic-csrf-token');
|
|
if (!field) { return; }
|
|
var token = readCookie('XSRF-TOKEN') || readCookie('XSRF-TOKEN-VALUE');
|
|
if (token) {
|
|
field.value = token;
|
|
}
|
|
}
|
|
|
|
function ensureTokenBeforeSubmit(evt) {
|
|
if (!evt.target || evt.target.id !== 'invoice-clock-form') {
|
|
return;
|
|
}
|
|
applyToken(evt.target);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function () { applyToken(document); });
|
|
} else {
|
|
applyToken(document);
|
|
}
|
|
|
|
document.addEventListener('submit', ensureTokenBeforeSubmit, true);
|
|
|
|
})();
|
|
</script>
|
|
{{end}}
|