File: D:/web/hyperflight/apps/inxpress/app/templates/shipments_parsed.html
{% extends "base.html" %}
{% block title %}InXpress — Shipments{% endblock %}
{% block content %}
{% macro sort_link(label, col, tip=None) -%}
{%- set newdir = 'asc' if sort != col or direction == 'desc' else 'desc' -%}
<a class="link-body-emphasis text-decoration-none"
href="{{ url_for('shipments_parsed',
q=q,
invoice=invoice,
carrier=carrier,
country=country,
from=date_from,
to=date_to,
pagesize=pagesize_raw,
duponly=1 if duponly else None,
noorder=1 if noorder else None,
details=1 if show_details else None,
page=1,
sort=col,
dir=newdir) }}"
{% if tip %}
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ tip }}"
{% endif %}>
{{ label }}
{% if sort == col %}
<span class="small">{{ '▲' if direction == 'asc' else '▼' }}</span>
{% endif %}
</a>
{%- endmacro %}
{% macro ref_badge(val) -%}
{% set raw = (val or '') %}
{% set key = raw|norm_ref %}
{% if key and duplicates.get(key, 0) > 1 %}
<span class="badge bg-warning-subtle text-warning-emphasis"
title="Duplicate reference ({{ duplicates[key] }} occurrences)">
{{ raw }}
</span>
{% else %}
{{ raw }}
{% endif %}
{%- endmacro %}
{% if not show_details %}
<style>
/* hide detailed columns in simple view */
.detail-col {
display: none;
}
</style>
{% endif %}
<!--
<div class="d-flex align-items-center justify-content-between mb-3">
<h1 class="h4 mb-0">Shipments</h1>
<div class="text-muted small">Total: {{ total }}</div>
</div>
-->
<form method="get" id="filterForm"
action="{{ url_for('shipments_parsed') }}"
class="row row-cols-1 row-cols-lg-auto g-2 align-items-end mb-3">
{# Keep current sort & direction when applying filters #}
<input type="hidden" name="sort" value="{{ sort }}">
<input type="hidden" name="dir" value="{{ dir }}">
<input type="hidden" name="page" value="1">
<div class="col-auto">
<label for="q" class="form-label mb-0">Search</label>
<input id="q" name="q" type="text"
class="form-control search-narrow"
value="{{ q or '' }}"
placeholder="Ref / name / airbill"
onkeydown="if(event.key==='Enter'){ this.form.page.value=1; }">
</div>
<div class="col">
<label class="form-label">Invoice</label>
<select name="invoice" class="form-select"
onchange="this.form.page.value=1; this.form.submit()">
<option value="">All invoices</option>
{% for inv in invoices %}
<option value="{{ inv.number }}"
{{ 'selected' if invoice == inv.number else '' }}>
{{ inv.label }}
</option>
{% endfor %}
</select>
</div>
<div class="col">
<label class="form-label">Carrier</label>
<select name="carrier" class="form-select"
onchange="this.form.page.value=1; this.form.submit()">
<option value="">All carriers</option>
{% for c in carriers %}
<option value="{{ c }}" {{ 'selected' if carrier==c else '' }}>{{ c }}</option>
{% endfor %}
</select>
</div>
<div class="col">
<label class="form-label">Country</label>
<select name="country" class="form-select" style="width:14rem"
onchange="this.form.page.value=1; this.form.submit()">
<option value="">All countries</option>
{% for c in countries %}
<option value="{{ c.cc }}" {{ 'selected' if country==c.cc else '' }}>
{{ c.name }} ({{ c.cc }})
</option>
{% endfor %}
</select>
</div>
<div class="col">
<label class="form-label">From</label>
<input type="date" name="from" class="form-control" lang="en-GB"
value="{{ date_from_iso or '' }}"
onchange="this.form.page.value=1; this.form.submit()">
</div>
<div class="col">
<label class="form-label">To</label>
<input type="date" name="to" class="form-control" lang="en-GB"
value="{{ date_to_iso or '' }}"
onchange="this.form.page.value=1; this.form.submit()">
</div>
<div class="col">
<label class="form-label">Page size</label>
<select name="pagesize" class="form-select" style="width:8rem"
onchange="this.form.page.value=1; this.form.submit()">
<option value="25" {{ 'selected' if pagesize_raw=='25' else '' }}>25</option>
<option value="50" {{ 'selected' if pagesize_raw=='50' else '' }}>50</option>
<option value="100" {{ 'selected' if pagesize_raw=='100' else '' }}>100</option>
<option value="200" {{ 'selected' if pagesize_raw in ['200',''] else '' }}>200</option>
<option value="all" {{ 'selected' if pagesize_raw=='all' else '' }}>All records</option>
</select>
</div>
<!-- Duplicates + No matching order stacked -->
<div class="col-auto">
<div class="d-flex flex-column">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="duponly" name="duponly"
value="1" {{ 'checked' if duponly else '' }}
onchange="this.form.page.value=1; this.form.submit()">
<label class="form-check-label" for="duponly">Duplicates only</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="noorder" name="noorder"
value="1" {{ 'checked' if noorder else '' }}
onchange="this.form.page.value=1; this.form.submit()">
<label class="form-check-label" for="noorder">No matching order</label>
</div>
<div class="form-check">
<input class="form-check-input"
type="checkbox"
id="details"
name="details"
value="1"
{% if show_details %}checked{% endif %}
onchange="this.form.page.value=1; this.form.submit();">
<label class="form-check-label" for="details">
More details
</label>
</div>
</div>
</div>
<div class="col">
<button type="submit" class="btn btn-primary">Apply</button>
{% if q or invoice or carrier or country or date_from_iso or date_to_iso
or (pagesize_raw and pagesize_raw != '50') %}
<a class="btn btn-outline-secondary ms-1" href="{{ url_for('shipments_parsed') }}">Clear</a>
{% endif %}
</div>
</form>
<div id="shipmentsTableContainer" class="table-sticky-wrap">
<table class="table table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th class="text-end">{{ sort_link('ID','shipment_id') }}</th>
<th>{{ sort_link('Date','ShipDate') }}</th>
<th>{{ sort_link('Carrier','Carrier') }}</th>
<th class="detail-col">{{ sort_link('Service','ServiceType') }}</th>
<th>{{ sort_link('Country','DestCountry') }}</th> {# still sorts by destination #}
<th class="detail-col text-center">{{ sort_link('Zone','Zone') }}</th> <!-- centered -->
<th>{{ sort_link('Refs','Reference') }}</th> <!-- moved here -->
<th>{{ sort_link('Receiver','ReceiverName') }}</th> <!-- moved here -->
<th class="text-end">{{ sort_link('Pcs','Pieces') }}</th>
<th class="text-end">{{ sort_link('Weight','WeightKg') }}</th>
<th class="text-end">{{ sort_link('L × W × H','Length') }}</th>
<th class="detail-col text-end">{{ sort_link('Base (£)','BaseCharge') }}</th>
<th class="detail-col text-end">{{ sort_link('Fuel (£)','FuelSurcharge') }}</th>
<th class="detail-col text-end">Other (£)</th>
<th class="detail-col text-end">{{ sort_link('VAT (£)','VATAmount') }}</th>
<th class="detail-col text-end">{{ sort_link('Total (£)','Total') }}</th>
<th class="text-end col-money">{{ sort_link('Charge (£)', 'Charge', 'Delivery charged to the customer from order Delivery field, with VAT removed') }}</th>
<th class="text-end col-money">{{ sort_link('Calc (£)', 'Calculated', 'Calculated delivery cost (before markup) from the website (DeliveryCalculatedCost)') }}</th>
<th class="text-end col-money">{{ sort_link('Quote (£)', 'Cost', 'Quote from Webship stored in order DeliveryAgentCost field') }}</th>
<th class="text-end col-money">{{ sort_link('Invoice (£)', 'NetAmount', 'Net shipment charge from Inxpress invoice, with VAT removed') }}</th>
<th class="text-end col-costdelta">{{ sort_link('Quote Δ', 'DiffCost', 'Invoice − Quote. Positive (red) = shipment more expensive than Webship quote.') }}</th>
<th class="text-end col-costdelta">{{ sort_link('Quote Δ %', 'DiffCostPct', '(Invoice − Quote) ÷ Quote × 100%') }}</th>
<th class="text-end col-calcdelta">{{ sort_link('Calc Δ', 'DiffCalc', 'Invoice − Calc. Difference between Inxpress invoice and website calculation (before markup).') }}</th>
<th class="text-end col-calcdelta">{{ sort_link('Calc Δ %', 'DiffCalcPct', '(Invoice − Calc) ÷ Calc × 100%') }}</th>
</tr>
</thead>
<tbody>
{% for r in rows %}
<tr>
<td class="text-end">
<a
href="{{ url_for('shipment_detail', shipment_id=r.shipment_id) }}"
class="open-shipment"
data-id="{{ r.shipment_id }}"
data-invoice="{{ r.invoice_number or '' }}"
onclick="return openShipmentModal(this);"
>{{ r.shipment_id }}</a>
</td>
<td class="text-nowrap">{{ r.ShipDate|fmt_date }}</td>
<td class="text-nowrap">{{ r.Carrier }}</td>
<!--<td class="text-truncate" style="max-width: 240px;">{{ r.ServiceType }}</td>-->
<td class="detail-col align-middle"><div class="small text-tiny">{{ r.ServiceType }}</div></td>
{# Country cell: default DEST; if ORIGIN != GB, show ORIGIN with small back arrow #}
{% set oc = (r.OriginCountry or '')|upper %}
{% set dc = (r.DestCountry or '')|upper %}
{% set use_origin = oc and oc != 'GB' %}
<td class="text-nowrap align-middle">
{% if use_origin %}
<span class="me-1 text-muted small" title="Origin country shown">↩︎</span>
<img src="{{ url_for('static', filename='flags/' ~ (oc|lower if oc else 'globe') ~ '.svg') }}"
width="24" height="18" alt="{{ oc or 'globe' }}" class="flag-img">
<span class="cc">{{ oc }}</span>
{% else %}
<img src="{{ url_for('static', filename='flags/' ~ (dc|lower if dc else 'globe') ~ '.svg') }}"
width="24" height="18" alt="{{ dc or 'globe' }}" class="flag-img">
<span class="cc">{{ dc }}</span>
{% endif %}
</td>
<td class="detail-col text-center">{{ r.Zone }}</td> <!-- centered -->
<td class="text-truncate" style="max-width: 300px;">
{% if r.Reference %}
<a href="#" class="open-order" data-ref="{{ r.Reference }}" data-shipment-id="{{ r.shipment_id }}">{{ ref_badge(r.Reference) }}</a>
{% endif %}
{% if r.Reference2 %} · {{ ref_badge(r.Reference2) }}{% endif %}{% if r.Reference3 %} · {{ ref_badge(r.Reference3) }}{% endif %}
</td>
<!--<td class="text-truncate" style="max-width: 260px;">{{ r.ReceiverName }}</td>-->
<td class="align-middle"><div class="small text-tiny">{{ r.ReceiverName }}</div></td>
<td class="text-end">{{ r.Pieces }}</td>
<td class="text-end">{{ r.WeightKg|trim2 }}</td>
<td class="text-end">
{% set dims = [r.Length, r.Width, r.Height] %}
{% if dims[0] or dims[1] or dims[2] %}
{{ dims[0]|trim2 }}×{{ dims[1]|trim2 }}×{{ dims[2]|trim2 }}
{% endif %}
</td>
<td class="detail-col text-end">{{ r.BaseCharge|blank_if_zero }}</td>
<td class="detail-col text-end">{{ r.FuelSurcharge|blank_if_zero }}</td>
<td class="detail-col text-end">{{ r.Other|blank_if_zero }}</td>
<td class="detail-col text-end">{{ r.VATAmount|fmt }}</td>
<td class="detail-col text-end fw-semibold">{{ r.Total|fmt }}</td>
{% macro diff_val(v) -%}
{% if v is not none %}
{# v > 0 => Net higher than Cost/Calc => bad => red #}
{% set cls = 'text-danger' if v > 0 else ('text-success' if v < 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%.2f'|format(v) }}</span>
{% endif %}
{%- endmacro %}
{% macro diff_pct(p) -%}
{% if p is not none %}
{# p > 0 => Net higher than Cost/Calc => bad => red #}
{% set cls = 'text-danger' if p > 0 else ('text-success' if p < 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%+0.1f%%'|format(p) }}</span>
{% endif %}
{%- endmacro %}
{# values #}
<td class="text-end col-money">{{ r.Charge|blank_if_zero }}</td>
<td class="text-end col-money">{{ r.Calculated|blank_if_zero }}</td>
<td class="text-end col-money">{{ r.Cost|blank_if_zero }}</td>
<td class="text-end col-money">{{ r.NetAmount|fmt }}</td>
{# deltas: value + percent #}
<td class="text-end col-costdelta">{{ diff_val(r.DiffCost) }}</td>
<td class="text-end col-costdelta">{{ diff_pct(r.DiffCostPct) }}</td>
<td class="text-end col-calcdelta">{{ diff_val(r.DiffCalc) }}</td>
<td class="text-end col-calcdelta">{{ diff_pct(r.DiffCalcPct) }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="table-secondary">
<!--<td colspan="{{ 11 if show_details else 9 }}" class="text-end fw-semibold">Page totals:</td>-->
<td colspan="{{ 11 if show_details else 9 }}" class="text-muted">
<div class="d-flex justify-content-between">
<span class="text-muted small">
Showing {{ rows|length }} of {{ total }} shipments
</span>
<span class="fw-semibold">
Totals:
</span>
</div>
</td>
<td class="detail-col text-end">{{ sums.BaseCharge|blank_if_zero }}</td>
<td class="detail-col text-end">{{ sums.FuelSurcharge|blank_if_zero }}</td>
<td class="detail-col text-end">{{ sums.Other|blank_if_zero }}</td>
<td class="detail-col text-end">{{ sums.VATAmount|fmt }}</td>
<td class="detail-col text-end fw-semibold">{{ sums.Total|fmt }}</td>
{# …existing footer cells for other totals… #}
{# Charge / Cost / Calculated totals #}
<td class="text-end fw-semibold col-money">{{ sums.Charge | fmt }}</td>
<td class="text-end fw-semibold col-money">{{ sums.Calculated | fmt }}</td>
<td class="text-end fw-semibold col-money">{{ sums.Cost | fmt }}</td>
<td class="text-end fw-semibold col-money">{{ sums.NetAmount|fmt }}</td>
{# Δ vs Net (value + %) – value uses same coloring rule as rows #}
{% set dc = sums.DiffCostTotal %}
<td class="text-end col-costdelta fw-semibold">
{% if dc is not none %}
{% set cls = 'text-danger' if dc > 0 else ('text-success' if dc < 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%.2f'|format(dc) }}</span>
{% endif %}
</td>
{% set dcp = sums.DiffCostPct %}
<td class="text-end col-costdelta fw-semibold">
{% if dcp is not none %}
{% set cls = 'text-danger' if dcp > 0 else ('text-success' if dcp < 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%+0.1f%%'|format(dcp) }}</span>
{% endif %}
</td>
{% set dl = sums.DiffCalcTotal %}
<td class="text-end col-calcdelta fw-semibold">
{% if dl is not none %}
{% set cls = 'text-danger' if dl > 0 else ('text-success' if dl < 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%.2f'|format(dl) }}</span>
{% endif %}
</td>
{% set dlp = sums.DiffCalcPct %}
<td class="text-end col-calcdelta fw-semibold">
{% if dlp is not none %}
{% set cls = 'text-danger' if dlp > 0 else ('text-success' if dlp < 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%+0.1f%%'|format(dlp) }}</span>
{% endif %}
</td>
</td>
</tr>
</tfoot>
</table>
</div>
<!-- Modal -->
<div class="modal fade" id="shipmentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Shipment details</h5>
<button type="button"
class="btn btn-outline-secondary btn-sm"
data-bs-dismiss="modal"
aria-label="Close"
title="Close (Esc)">
Close
</button>
</div>
<div class="modal-body">
<div class="text-center py-5" id="shipmentModalLoading">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2 small text-muted">Loading…</div>
</div>
<div id="shipmentModalBody" class="d-none"></div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="orderModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header py-2">
<h5 class="modal-title">Order</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Loading…
</div>
</div>
</div>
</div>
<div class="modal fade" id="orderItemsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content order-items-modal">
<div class="modal-header py-2">
<h5 class="modal-title">Order items</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Loading…
</div>
</div>
</div>
</div>
<div class="modal fade" id="productModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content product-modal">
<div class="modal-header py-2">
<h5 class="modal-title">Product</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-0">
<iframe id="productFrame"
src="about:blank"
style="border:0; width:100%; height:80vh;"></iframe>
</div>
</div>
</div>
</div>
<div class="modal fade" id="pdfModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content pdf-modal">
<div class="modal-header py-2">
<h5 class="modal-title mb-0">Source PDF</h5>
<div class="d-flex align-items-center gap-2">
<div class="small me-3" id="pdfRefWrapper" style="display:none;">
Ref:
<code id="pdfRefText"></code>
<button type="button"
class="btn btn-sm btn-light ms-1 pdf-copy-ref"
title="Copy order no">
Copy
</button>
<span class="small text-success ms-1" id="pdfRefCopied" style="display:none;">
Copied
</span>
</div>
<button type="button"
class="btn btn-sm btn-outline-light pdf-open-new"
title="Open in new tab">
Open in new tab
</button>
<button type="button"
class="btn-close ms-2"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
</div>
<div class="modal-body p-0">
<div class="pdf-frame-container">
<div id="pdfFrameInner" class="pdf-frame-inner">
<iframe id="pdfFrame"
src="about:blank"
style="border:0; width:100%; height:80vh;"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let linkedShipmentId = null; // remembers which shipment is associated with current order modal
let currentPdfUrl = null;
let currentPdfRef = null;
// Helper: open shipment modal
function openShipmentModal(shipmentId) {
if (!shipmentId) return;
const modalEl = document.getElementById('shipmentModal');
const body = modalEl.querySelector('.modal-body');
body.textContent = 'Loading…';
// Build base URL using dummy 0 then strip trailing 0, to satisfy url_for(<int:shipment_id>)
const baseUrl = '{{ url_for("shipment_detail", shipment_id=0) }}'.replace(/0$/, '');
const url = baseUrl + encodeURIComponent(shipmentId);
fetch(url, { headers: { 'X-Requested-With': 'fetch' } })
.then(r => r.text())
.then(html => {
body.innerHTML = html;
bootstrap.Modal.getOrCreateInstance(modalEl).show();
})
.catch(() => {
body.innerHTML = '<div class="text-danger">Error loading shipment.</div>';
});
}
// Helper: open order modal
function openOrderModal(ref) {
if (!ref) return;
const modalEl = document.getElementById('orderModal');
const body = modalEl.querySelector('.modal-body');
body.textContent = 'Loading…';
const url = '{{ url_for("order_by_ref", ref="__REF__") }}'
.replace('__REF__', encodeURIComponent(ref));
fetch(url, { headers: { 'X-Requested-With': 'fetch' } })
.then(r => r.text())
.then(html => {
body.innerHTML = html;
// show/hide "View shipment…" based on whether we have a linked shipment id
const backBtn = body.querySelector('.open-linked-shipment');
if (backBtn) {
if (linkedShipmentId) {
backBtn.classList.remove('d-none');
} else {
backBtn.classList.add('d-none');
}
}
bootstrap.Modal.getOrCreateInstance(modalEl).show();
})
.catch(() => {
body.innerHTML = '<div class="text-danger">Error loading order.</div>';
});
}
document.addEventListener('click', function (e) {
// A) Click on shipment ID (or any .open-shipment)
const shipLink = e.target.closest('.open-shipment');
if (shipLink) {
e.preventDefault();
const sid = shipLink.dataset.id || shipLink.dataset.shipmentId || shipLink.textContent.trim();
openShipmentModal(sid);
return;
}
// B) Click on any "View order…" (grid or shipment modal)
const orderLink = e.target.closest('.open-order');
if (orderLink) {
e.preventDefault();
const ref = orderLink.dataset.ref;
// remember which shipment this order is associated with (for "View shipment…" back button)
linkedShipmentId = orderLink.dataset.shipmentId || null;
openOrderModal(ref);
return;
}
// C) Click "View shipment…" inside Order modal
const backToShipment = e.target.closest('.open-linked-shipment');
if (backToShipment) {
e.preventDefault();
if (!linkedShipmentId) return;
const shipmentModalEl = document.getElementById('shipmentModal');
const shipmentBody = shipmentModalEl.querySelector('.modal-body');
shipmentBody.textContent = 'Loading…';
const baseUrl = '{{ url_for("shipment_detail", shipment_id=0) }}'.replace(/0$/, '');
const url = baseUrl + encodeURIComponent(linkedShipmentId);
fetch(url, { headers: { 'X-Requested-With': 'fetch' } })
.then(r => r.text())
.then(html => {
shipmentBody.innerHTML = html;
// hide order modal first so shipment modal isn't behind it
const orderModalEl = document.getElementById('orderModal');
const orderModal = bootstrap.Modal.getInstance(orderModalEl);
if (orderModal) {
orderModal.hide();
}
bootstrap.Modal.getOrCreateInstance(shipmentModalEl).show();
})
.catch(() => {
shipmentBody.innerHTML = '<div class="text-danger">Error loading shipment.</div>';
});
return;
}
// D) Open Order Items modal (keep your existing logic here)
// D) Open Order Items modal
const itemsBtn = e.target.closest('.open-order-items');
if (itemsBtn) {
e.preventDefault();
const orderNo = itemsBtn.dataset.order;
const modalEl = document.getElementById('orderItemsModal');
const body = modalEl.querySelector('.modal-body');
body.textContent = 'Loading…';
// Build base URL with dummy 0, then replace that 0 with the real order no
// e.g. "/apps/inxpress/order/0/details" -> "/apps/inxpress/order/123/details"
const base = '{{ url_for("order_details", order_no=0) }}';
const url = base.replace('/0/', '/' + encodeURIComponent(orderNo) + '/');
fetch(url, { headers: { 'X-Requested-With': 'fetch' } })
.then(r => r.text())
.then(html => {
body.innerHTML = html;
bootstrap.Modal.getOrCreateInstance(modalEl).show();
})
.catch(() => {
body.innerHTML = '<div class="text-danger">Error loading order items.</div>';
});
return;
}
// E) Restore items to basket
const restoreBtn = e.target.closest('.open-restore-basket');
if (restoreBtn) {
e.preventDefault();
const orderNo = restoreBtn.dataset.order;
const sid = restoreBtn.dataset.session;
const ip = restoreBtn.dataset.ip;
if (!orderNo || !sid || !ip) return;
const url = `${window.location.origin}/products.asp` +
`?cmd=restorebasketorder` +
`&orderno=${encodeURIComponent(orderNo)}` +
`&sid=${encodeURIComponent(sid)}` +
`&ip=${encodeURIComponent(ip)}`;
window.open(url, '_blank');
return;
}
// F) Open product page in a modal (product iframe)
const productLink = e.target.closest('.open-product');
if (productLink) {
e.preventDefault();
const url = productLink.dataset.url || productLink.href;
if (!url) return;
const modalEl = document.getElementById('productModal');
const iframe = modalEl.querySelector('iframe');
iframe.src = url;
bootstrap.Modal.getOrCreateInstance(modalEl).show();
return;
}
// G) Open source PDF in modal
// Open PDF modal
const pdfLink = e.target.closest('.open-pdf');
if (pdfLink) {
e.preventDefault();
const url = pdfLink.dataset.url || pdfLink.getAttribute('href');
if (!url) return;
currentPdfUrl = url;
currentPdfRef = pdfLink.dataset.ref || '';
const modalEl = document.getElementById('pdfModal');
const frame = document.getElementById('pdfFrame');
const refWrap = document.getElementById('pdfRefWrapper');
const refText = document.getElementById('pdfRefText');
const refCopied = document.getElementById('pdfRefCopied');
// Update reference display
if (currentPdfRef) {
refText.textContent = currentPdfRef;
refWrap.style.display = '';
refCopied.style.display = 'none';
} else {
refWrap.style.display = 'none';
}
frame.src = url;
bootstrap.Modal.getOrCreateInstance(modalEl).show();
return;
}
// Open PDF in new tab
const openNewBtn = e.target.closest('.pdf-open-new');
if (openNewBtn) {
e.preventDefault();
if (currentPdfUrl) {
window.open(currentPdfUrl, '_blank', 'noopener');
}
return;
}
// Copy Ref to clipboard
const copyBtn = e.target.closest('.pdf-copy-ref');
if (copyBtn) {
e.preventDefault();
if (!currentPdfRef) return;
const refCopied = document.getElementById('pdfRefCopied');
// Try modern clipboard API first
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(currentPdfRef)
.then(() => {
refCopied.style.display = '';
setTimeout(() => { refCopied.style.display = 'none'; }, 1500);
})
.catch(() => {
const ta = document.createElement('textarea');
ta.value = currentPdfRef;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch (e) {}
document.body.removeChild(ta);
refCopied.style.display = '';
setTimeout(() => { refCopied.style.display = 'none'; }, 1500);
});
} else {
// fallback only
const ta = document.createElement('textarea');
ta.value = currentPdfRef;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch (e) {}
document.body.removeChild(ta);
refCopied.style.display = '';
setTimeout(() => { refCopied.style.display = 'none'; }, 1500);
}
return;
}
});
</script>
<script>
function resizeShipmentsTable() {
const container = document.getElementById('shipmentsTableContainer');
if (!container) return;
const rect = container.getBoundingClientRect();
const viewportHeight = window.innerHeight;
// small gap at bottom so it doesn't touch the window edge
const bottomMargin = 16; // px
const available = viewportHeight - rect.top - bottomMargin;
if (available > 0) {
container.style.maxHeight = available + 'px';
container.style.overflowY = 'auto';
}
}
document.addEventListener('DOMContentLoaded', resizeShipmentsTable);
window.addEventListener('resize', resizeShipmentsTable);
</script>
{% if last_page > 1 %}
<nav class="mt-3" aria-label="Shipments pagination">
{% set prevp = 1 if page<=1 else page-1 %}
{% set nextp = last_page if page>=last_page else page+1 %}
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ 'disabled' if page==1 }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize, duponly=1 if duponly else None, noorder=1 if noorder else None, page=1) }}">« First</a>
</li>
<li class="page-item {{ 'disabled' if page==1 }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize, duponly=1 if duponly else None, noorder=1 if noorder else None, page=prevp) }}">‹ Prev</a>
</li>
<li class="page-item disabled"><span class="page-link">Page {{ page }} / {{ last_page }}</span></li>
<li class="page-item {{ 'disabled' if page==last_page }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize,duponly=1 if duponly else None, noorder=1 if noorder else None, page=nextp) }}">Next ›</a>
</li>
<li class="page-item {{ 'disabled' if page==last_page }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize, duponly=1 if duponly else None, noorder=1 if noorder else None, page=last_page) }}">Last »</a>
</li>
</ul>
</nav>
{% endif %}
{% endblock %}