Files
gomag-vending/api/app/templates/dashboard.html
Marius Mutu 5f8b9b6003 feat(dashboard): redesign UI with smart polling, unified sync card, filter bar
Replace SSE with smart polling (30s idle / 3s when running). Unify sync
panel into single two-row card with live progress text. Add unified filter
bar (period dropdown, status pills, search) with period-total counts.
Add Client/Cont tooltip for different shipping/billing persons. Add SKU
mappings pct_total badges + complete/incomplete filter + 409 duplicate
check. Add missing SKUs search + rescan progress UX. Migrate SQLite
orders schema (shipping_name, billing_name, payment_method,
delivery_method). Fix JSON_OUTPUT_DIR path for server running from
project root. Fix pagination controls showing top+bottom with per-page
selector (25/50/100/250).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 17:55:36 +02:00

198 lines
9.9 KiB
HTML

{% extends "base.html" %}
{% block title %}Dashboard - GoMag Import{% endblock %}
{% block nav_dashboard %}active{% endblock %}
{% block content %}
<h4 class="mb-4">Panou de Comanda</h4>
<!-- Sync Card (unified two-row panel) -->
<div class="sync-card">
<!-- TOP ROW: Status + Controls -->
<div class="sync-card-controls">
<span id="syncStatusDot" class="sync-status-dot idle"></span>
<span id="syncStatusText" style="font-size:0.8125rem;color:#374151;">Inactiv</span>
<div style="display:flex;align-items:center;gap:0.5rem;margin-left:auto;">
<label style="display:flex;align-items:center;gap:0.4rem;font-size:0.8125rem;color:#6b7280;">
Auto:
<input type="checkbox" id="schedulerToggle" style="cursor:pointer;" onchange="toggleScheduler()">
</label>
<select id="schedulerInterval" class="select-compact" onchange="updateSchedulerInterval()">
<option value="5">5 min</option>
<option value="10" selected>10 min</option>
<option value="30">30 min</option>
</select>
<button id="syncStartBtn" class="btn btn-primary btn-compact" onclick="startSync()">&#9654; Start Sync</button>
</div>
</div>
<div class="sync-card-divider"></div>
<!-- BOTTOM ROW: Last sync info (clickable → jurnal) -->
<div class="sync-card-info" id="lastSyncRow" role="button" tabindex="0" title="Ver jurnal sync">
<span id="lastSyncDate" style="font-weight:500;">&#8212;</span>
<span id="lastSyncDuration" style="color:#9ca3af;">&#8212;</span>
<span id="lastSyncCounts">&#8212;</span>
<span id="lastSyncStatus">&#8212;</span>
<span style="margin-left:auto;font-size:0.75rem;color:#9ca3af;">&#8599; jurnal</span>
</div>
<!-- LIVE PROGRESS (shown only when sync is running) -->
<div class="sync-card-progress" id="syncProgressArea" style="display:none;">
<span class="sync-live-dot"></span>
<span id="syncProgressText">Se proceseaza...</span>
</div>
</div>
<!-- Orders Table -->
<div class="card mb-4">
<div class="card-header">
<span>Comenzi</span>
</div>
<div class="card-body py-2 px-3">
<div class="filter-bar" id="ordersFilterBar">
<!-- Period dropdown -->
<select id="periodSelect" class="select-compact">
<option value="3">3 zile</option>
<option value="7" selected>7 zile</option>
<option value="30">30 zile</option>
<option value="90">3 luni</option>
<option value="0">Toate</option>
<option value="custom">Perioada personalizata...</option>
</select>
<!-- Custom date range (hidden until 'custom' selected) -->
<div class="period-custom-range" id="customRangeInputs">
<input type="date" id="periodStart" class="select-compact">
<span>&#8212;</span>
<input type="date" id="periodEnd" class="select-compact">
</div>
<!-- Status pills -->
<button class="filter-pill active" data-status="all">Toate <span class="filter-count" id="cntAll">0</span></button>
<button class="filter-pill" data-status="IMPORTED">Imp. <span class="filter-count" id="cntImp">0</span></button>
<button class="filter-pill" data-status="SKIPPED">Omise <span class="filter-count" id="cntSkip">0</span></button>
<button class="filter-pill" data-status="ERROR">Erori <span class="filter-count" id="cntErr">0</span></button>
<button class="filter-pill" data-status="UNINVOICED">Nefact. <span class="filter-count" id="cntNef">0</span></button>
<!-- Search (integrated, end of row) -->
<input type="search" id="orderSearch" placeholder="Cauta..." class="search-input">
</div>
</div>
<!-- Pagination top bar -->
<div class="card-body py-1 px-3 border-bottom d-flex justify-content-between align-items-center" style="gap:0.5rem;">
<small class="text-muted" id="dashPageInfoTop"></small>
<div style="display:flex;align-items:center;gap:0.5rem;">
<label style="font-size:0.8125rem;color:#6b7280;white-space:nowrap;">Per pagina:
<select id="perPageSelect" class="select-compact" style="margin-left:0.25rem;" onchange="dashChangePerPage(this.value)">
<option value="25">25</option>
<option value="50" selected>50</option>
<option value="100">100</option>
<option value="250">250</option>
</select>
</label>
<div id="dashPaginationTop" class="d-flex align-items-center gap-2"></div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th class="sortable" onclick="dashSortBy('order_number')">Nr Comanda <span class="sort-icon" data-col="order_number"></span></th>
<th class="sortable" onclick="dashSortBy('order_date')">Data <span class="sort-icon" data-col="order_date"></span></th>
<th class="sortable" onclick="dashSortBy('customer_name')">Client <span class="sort-icon" data-col="customer_name"></span></th>
<th class="sortable" onclick="dashSortBy('items_count')">Art. <span class="sort-icon" data-col="items_count"></span></th>
<th class="sortable" onclick="dashSortBy('status')">Status Import <span class="sort-icon" data-col="status"></span></th>
<th>ID ROA</th>
<th>Factura</th>
<th>Total</th>
</tr>
</thead>
<tbody id="dashOrdersBody">
<tr><td colspan="8" class="text-center text-muted py-3">Se incarca...</td></tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer d-flex justify-content-between align-items-center">
<small class="text-muted" id="dashPageInfo"></small>
<div id="dashPagination" class="d-flex align-items-center gap-2"></div>
</div>
</div>
<!-- Order Detail Modal -->
<div class="modal fade" id="orderDetailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Comanda <code id="detailOrderNumber"></code></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-6">
<small class="text-muted">Client:</small> <strong id="detailCustomer"></strong><br>
<small class="text-muted">Data comanda:</small> <span id="detailDate"></span><br>
<small class="text-muted">Status:</small> <span id="detailStatus"></span>
</div>
<div class="col-md-6">
<small class="text-muted">ID Comanda ROA:</small> <span id="detailIdComanda">-</span><br>
<small class="text-muted">ID Partener:</small> <span id="detailIdPartener">-</span><br>
<small class="text-muted">ID Adr. Facturare:</small> <span id="detailIdAdresaFact">-</span><br>
<small class="text-muted">ID Adr. Livrare:</small> <span id="detailIdAdresaLivr">-</span>
</div>
</div>
<div class="table-responsive">
<table class="table table-sm table-bordered mb-0">
<thead class="table-light">
<tr>
<th>SKU</th>
<th>Produs</th>
<th>Cant.</th>
<th>Pret</th>
<th>TVA</th>
<th>CODMAT</th>
<th>Status</th>
<th>Actiune</th>
</tr>
</thead>
<tbody id="detailItemsBody">
</tbody>
</table>
</div>
<div id="detailError" class="alert alert-danger mt-3" style="display:none;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Inchide</button>
</div>
</div>
</div>
</div>
<!-- Quick Map Modal (used from order detail) -->
<div class="modal fade" id="quickMapModal" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Mapeaza SKU: <code id="qmSku"></code></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<small class="text-muted">Produs web:</small> <strong id="qmProductName"></strong>
</div>
<div id="qmCodmatLines">
<!-- Dynamic CODMAT lines -->
</div>
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" onclick="addQmCodmatLine()">
<i class="bi bi-plus"></i> Adauga CODMAT
</button>
<div id="qmPctWarning" class="text-danger mt-2" style="display:none;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuleaza</button>
<button type="button" class="btn btn-primary" onclick="saveQuickMapping()">Salveaza</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/dashboard.js"></script>
{% endblock %}