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>
This commit is contained in:
@@ -44,7 +44,9 @@ async def update_sync_run(run_id: str, status: str, total_orders: int = 0,
|
||||
async def upsert_order(sync_run_id: str, order_number: str, order_date: str,
|
||||
customer_name: str, status: str, id_comanda: int = None,
|
||||
id_partener: int = None, error_message: str = None,
|
||||
missing_skus: list = None, items_count: int = 0):
|
||||
missing_skus: list = None, items_count: int = 0,
|
||||
shipping_name: str = None, billing_name: str = None,
|
||||
payment_method: str = None, delivery_method: str = None):
|
||||
"""Upsert a single order — one row per order_number, status updated in place."""
|
||||
db = await get_sqlite()
|
||||
try:
|
||||
@@ -52,8 +54,9 @@ async def upsert_order(sync_run_id: str, order_number: str, order_date: str,
|
||||
INSERT INTO orders
|
||||
(order_number, order_date, customer_name, status,
|
||||
id_comanda, id_partener, error_message, missing_skus, items_count,
|
||||
last_sync_run_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
last_sync_run_id, shipping_name, billing_name,
|
||||
payment_method, delivery_method)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(order_number) DO UPDATE SET
|
||||
status = excluded.status,
|
||||
error_message = excluded.error_message,
|
||||
@@ -65,11 +68,16 @@ async def upsert_order(sync_run_id: str, order_number: str, order_date: str,
|
||||
THEN orders.times_skipped + 1
|
||||
ELSE orders.times_skipped END,
|
||||
last_sync_run_id = excluded.last_sync_run_id,
|
||||
shipping_name = COALESCE(excluded.shipping_name, orders.shipping_name),
|
||||
billing_name = COALESCE(excluded.billing_name, orders.billing_name),
|
||||
payment_method = COALESCE(excluded.payment_method, orders.payment_method),
|
||||
delivery_method = COALESCE(excluded.delivery_method, orders.delivery_method),
|
||||
updated_at = datetime('now')
|
||||
""", (order_number, order_date, customer_name, status,
|
||||
id_comanda, id_partener, error_message,
|
||||
json.dumps(missing_skus) if missing_skus else None,
|
||||
items_count, sync_run_id))
|
||||
items_count, sync_run_id, shipping_name, billing_name,
|
||||
payment_method, delivery_method))
|
||||
await db.commit()
|
||||
finally:
|
||||
await db.close()
|
||||
@@ -124,35 +132,52 @@ async def resolve_missing_sku(sku: str):
|
||||
await db.close()
|
||||
|
||||
|
||||
async def get_missing_skus_paginated(page: int = 1, per_page: int = 20, resolved: int = 0):
|
||||
"""Get paginated missing SKUs. resolved=-1 means show all."""
|
||||
async def get_missing_skus_paginated(page: int = 1, per_page: int = 20,
|
||||
resolved: int = 0, search: str = None):
|
||||
"""Get paginated missing SKUs. resolved=-1 means show all.
|
||||
Optional search filters by sku or product_name (LIKE)."""
|
||||
db = await get_sqlite()
|
||||
try:
|
||||
offset = (page - 1) * per_page
|
||||
|
||||
if resolved == -1:
|
||||
cursor = await db.execute("SELECT COUNT(*) FROM missing_skus")
|
||||
total = (await cursor.fetchone())[0]
|
||||
cursor = await db.execute("""
|
||||
SELECT sku, product_name, first_seen, resolved, resolved_at,
|
||||
order_count, order_numbers, customers
|
||||
FROM missing_skus
|
||||
ORDER BY resolved ASC, order_count DESC, first_seen DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""", (per_page, offset))
|
||||
else:
|
||||
cursor = await db.execute(
|
||||
"SELECT COUNT(*) FROM missing_skus WHERE resolved = ?", (resolved,)
|
||||
)
|
||||
total = (await cursor.fetchone())[0]
|
||||
cursor = await db.execute("""
|
||||
SELECT sku, product_name, first_seen, resolved, resolved_at,
|
||||
order_count, order_numbers, customers
|
||||
FROM missing_skus
|
||||
WHERE resolved = ?
|
||||
ORDER BY order_count DESC, first_seen DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""", (resolved, per_page, offset))
|
||||
# Build WHERE clause parts
|
||||
where_parts = []
|
||||
params_count = []
|
||||
params_data = []
|
||||
|
||||
if resolved != -1:
|
||||
where_parts.append("resolved = ?")
|
||||
params_count.append(resolved)
|
||||
params_data.append(resolved)
|
||||
|
||||
if search:
|
||||
like = f"%{search}%"
|
||||
where_parts.append("(LOWER(sku) LIKE LOWER(?) OR LOWER(COALESCE(product_name,'')) LIKE LOWER(?))")
|
||||
params_count.extend([like, like])
|
||||
params_data.extend([like, like])
|
||||
|
||||
where_clause = ("WHERE " + " AND ".join(where_parts)) if where_parts else ""
|
||||
|
||||
order_clause = (
|
||||
"ORDER BY resolved ASC, order_count DESC, first_seen DESC"
|
||||
if resolved == -1
|
||||
else "ORDER BY order_count DESC, first_seen DESC"
|
||||
)
|
||||
|
||||
cursor = await db.execute(
|
||||
f"SELECT COUNT(*) FROM missing_skus {where_clause}",
|
||||
params_count
|
||||
)
|
||||
total = (await cursor.fetchone())[0]
|
||||
|
||||
cursor = await db.execute(f"""
|
||||
SELECT sku, product_name, first_seen, resolved, resolved_at,
|
||||
order_count, order_numbers, customers
|
||||
FROM missing_skus
|
||||
{where_clause}
|
||||
{order_clause}
|
||||
LIMIT ? OFFSET ?
|
||||
""", params_data + [per_page, offset])
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
@@ -474,8 +499,13 @@ async def get_run_orders_filtered(run_id: str, status_filter: str = "all",
|
||||
async def get_orders(page: int = 1, per_page: int = 50,
|
||||
search: str = "", status_filter: str = "all",
|
||||
sort_by: str = "order_date", sort_dir: str = "desc",
|
||||
period_days: int = 7):
|
||||
"""Get orders with filters, sorting, and period. period_days=0 means all time."""
|
||||
period_days: int = 7,
|
||||
period_start: str = "", period_end: str = ""):
|
||||
"""Get orders with filters, sorting, and period.
|
||||
|
||||
period_days=0 with period_start/period_end uses custom date range.
|
||||
period_days=0 without dates means all time.
|
||||
"""
|
||||
db = await get_sqlite()
|
||||
try:
|
||||
where_clauses = []
|
||||
@@ -484,6 +514,9 @@ async def get_orders(page: int = 1, per_page: int = 50,
|
||||
if period_days and period_days > 0:
|
||||
where_clauses.append("order_date >= date('now', ?)")
|
||||
params.append(f"-{period_days} days")
|
||||
elif period_days == 0 and period_start and period_end:
|
||||
where_clauses.append("order_date BETWEEN ? AND ?")
|
||||
params.extend([period_start, period_end])
|
||||
|
||||
if search:
|
||||
where_clauses.append("(order_number LIKE ? OR customer_name LIKE ?)")
|
||||
|
||||
Reference in New Issue
Block a user