HEX
Server: Microsoft-IIS/10.0
System: Windows NT ITPWINWEBSVR22 10.0 build 20348 (Windows Server 2022) AMD64
User: www.conferencesearch.co.uk (0)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: D:/web/hyperflight/apps/inxpress/app/templates/shipments_parsed - Copy (3).html
{% extends "base.html" %}
{% block title %}InXpress — Shipments{% endblock %}

{% block content %}

{% macro sort_link(label, col) -%}
  {%- 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, carrier=carrier, from=date_from, to=date_to,
                      pagesize=pagesize, page=1, sort=col, dir=newdir, duponly=1 if duponly else None) }}">
    {{ 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 %}


<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"
      action="{{ url_for('shipments_parsed') }}"
      class="row row-cols-1 row-cols-lg-auto g-2 align-items-end mb-3">
  <input type="hidden" name="page" value="{{ page }}">

  <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 }}" {{ 'selected' if invoice==inv else '' }}>{{ inv }}</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 mb-1">
        <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">
        <!-- hidden default so param posts when unticked -->
        <input type="hidden" name="noorder" value="0">
        <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>
  </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 class="table-responsive">
  <table class="table table-sm table-striped align-middle">
    <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>{{ sort_link('Service','ServiceType') }}</th>        
        <th>{{ sort_link('Country','DestCountry') }}</th>  {# still sorts by destination #}
        <th class="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="text-end">{{ sort_link('Base (£)','BaseCharge') }}</th>
        <th class="text-end">{{ sort_link('Fuel (£)','FuelSurcharge') }}</th>
        <th class="text-end">Other (£)</th>
        <th class="text-end">{{ sort_link('Net (£)','NetAmount') }}</th>
        <th class="text-end">{{ sort_link('VAT (£)','VATAmount') }}</th>
        <th class="text-end">{{ sort_link('Total (£)','Total') }}</th>
        <th class="text-end">{{ sort_link('Charge (£)',     'Charge') }}</th>
        <th class="text-end">{{ sort_link('Cost (£)',       'Cost') }}</th>
        <th class="text-end">{{ sort_link('Calc (£)', 'Calculated') }}</th>
        <th class="text-end">{{ sort_link('Cost Δ',         'DiffCost') }}</th>
        <th class="text-end">{{ sort_link('Cost Δ %',       'DiffCostPct') }}</th>
        <th class="text-end">{{ sort_link('Calc Δ',         'DiffCalc') }}</th>
        <th class="text-end">{{ sort_link('Calc Δ %',       'DiffCalcPct') }}</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="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="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 }}">{{ 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="text-end">{{ r.BaseCharge|blank_if_zero }}</td>
        <td class="text-end">{{ r.FuelSurcharge|blank_if_zero }}</td>
        <td class="text-end">{{ r.Other|blank_if_zero }}</td>
        <td class="text-end fw-semibold">{{ r.NetAmount|fmt }}</td>
        <td class="text-end">{{ r.VATAmount|fmt }}</td>
        <td class="text-end fw-semibold">{{ r.Total|fmt }}</td>
        

        {# value delta: signed colour, ASCII only #}
        {% macro diff_val(v) -%}
          {% if v is not none %}
            {% set cls = 'text-success' if v < 0 else ('text-danger' if v > 0 else 'text-body') %}
            <span class="{{ cls }}">{{ '%.2f'|format(v) }}</span>
          {% endif %}
        {%- endmacro %}

        {# percent delta: signed with + for positives, ASCII only #}
        {% macro diff_pct(p) -%}
          {% if p is not none %}
            {% set cls = 'text-success' if p < 0 else ('text-danger' if p > 0 else 'text-body') %}
            {# format with sign, then add a small space after + or - using filter chaining #}
            <span class="{{ cls }}">
              {{ '%+0.1f%%'|format(p)|replace('+', '+ ')|replace('-', '- ') }}
            </span>
          {% endif %}
        {%- endmacro %}


        {# values #}
        <td class="text-end">{{ r.Charge|blank_if_zero }}</td>
        <td class="text-end">{{ r.Cost|blank_if_zero }}</td>
        <td class="text-end">{{ r.Calculated|blank_if_zero }}</td>

        {# deltas: value + percent #}
        <td class="text-end">{{ diff_val(r.DiffCost) }}</td>
        <td class="text-end">{{ diff_pct(r.DiffCostPct) }}</td>
        <td class="text-end">{{ diff_val(r.DiffCalc) }}</td>
        <td class="text-end">{{ diff_pct(r.DiffCalcPct) }}</td>


        
      </tr>
      {% endfor %}
    </tbody>

    <tfoot>
      <tr class="table-secondary">
        <td colspan="11" class="text-end fw-semibold">Page totals:</td>
        <td class="text-end">{{ sums.BaseCharge|blank_if_zero }}</td>
        <td class="text-end">{{ sums.FuelSurcharge|blank_if_zero }}</td>
        <td class="text-end">{{ sums.Other|blank_if_zero }}</td>
        <td class="text-end fw-semibold">{{ sums.NetAmount|fmt }}</td>
        <td class="text-end">{{ sums.VATAmount|fmt }}</td>
        <td class="text-end fw-semibold">{{ sums.Total|fmt }}</td>
        
        {# …existing footer cells for other totals… #}

        {# Charge / Cost / Calculated totals #}
        <td class="text-end fw-semibold">{{ sums.Charge     | fmt }}</td>
        <td class="text-end fw-semibold">{{ sums.Cost       | fmt }}</td>
        <td class="text-end fw-semibold">{{ sums.Calculated | fmt }}</td>

        {# Δ vs Net (value + %) – value uses same coloring rule as rows #}
        {% set dc = sums.DiffCostTotal %}
        <td class="text-end fw-semibold">
          {% if dc is not none %}
            {% set cls = 'text-success' if dc < 0 else ('text-danger' if dc > 0 else 'text-body') %}
            <span class="{{ cls }}">{{ '%.2f'|format(dc) }}</span>
          {% endif %}
        </td>

        {% set dcp = sums.DiffCostPct %}
        <td class="text-end fw-semibold">
          {% if dcp is not none %}
            {% set cls = 'text-success' if dcp < 0 else ('text-danger' if dcp > 0 else 'text-body') %}
            <span class="{{ cls }}">{{ '%+0.1f%%'|format(dcp) }}</span>
          {% endif %}
        </td>

        {% set dl = sums.DiffCalcTotal %}
        <td class="text-end fw-semibold">
          {% if dl is not none %}
            {% set cls = 'text-success' if dl < 0 else ('text-danger' if dl > 0 else 'text-body') %}
            <span class="{{ cls }}">{{ '%.2f'|format(dl) }}</span>
          {% endif %}
        </td>

        {% set dlp = sums.DiffCalcPct %}
        <td class="text-end fw-semibold">
          {% if dlp is not none %}
            {% set cls = 'text-success' if dlp < 0 else ('text-danger' if dlp > 0 else 'text-body') %}
            <span class="{{ cls }}">{{ '%+0.1f%%'|format(dlp) }}</span>
          {% endif %}
        </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-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>



<script>
function openShipmentModal(anchor){
  try {
    const modalEl = document.getElementById('shipmentModal');
    const bodyEl  = document.getElementById('shipmentModalBody');
    const loadEl  = document.getElementById('shipmentModalLoading');
    if (!modalEl || !bodyEl || !loadEl || !window.bootstrap) return true; // fall back to navigation

    const id  = anchor.getAttribute('data-id') || '';
    const inv = anchor.getAttribute('data-invoice') || '';
    const url = anchor.getAttribute('href');

    // Title + reset UI
    modalEl.querySelector('.modal-title').textContent =
      'Shipment #' + id + (inv ? ' · ' + inv : '');
    loadEl.classList.remove('d-none');
    bodyEl.classList.add('d-none');
    bodyEl.innerHTML = '';

    // Show modal
    new bootstrap.Modal(modalEl).show();

    // Load content
    fetch(url, { cache: 'no-store', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
      .then(html => { bodyEl.innerHTML = html; loadEl.classList.add('d-none'); bodyEl.classList.remove('d-none'); })
      .catch(err => { bodyEl.innerHTML = '<div class="alert alert-danger small mb-0">Could not load shipment ' + id + ': ' + err.message + '</div>'; loadEl.classList.add('d-none'); bodyEl.classList.remove('d-none'); });

    return false; // PREVENT NAVIGATION
  } catch(e) {
    console.error(e);
    return true; // fallback to opening the link normally
  }
}
</script>

<div class="modal fade" id="orderModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg">
    <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>

<script>
document.addEventListener('click', function (e) {
  const link = e.target.closest('.open-order');
  if (!link) return;
  e.preventDefault();

  const ref = link.dataset.ref;
  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;
      bootstrap.Modal.getOrCreateInstance(modalEl).show();
    })
    .catch(() => { body.innerHTML = '<div class="text-danger">Error loading order.</div>'; });
});
</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 %}