From de7e99b6ae49e6b129d4af52e4bc0540f34e184d Mon Sep 17 00:00:00 2001 From: "Troll (Hermes Agent)" Date: Tue, 11 Aug 2026 16:36:03 +0000 Subject: [PATCH] Add Steps progress indicator to admin request page --- app.py | 38 +++++++++++++++- templates/admin/request.html | 85 +++++++++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 37ce7c5..13141d4 100644 --- a/app.py +++ b/app.py @@ -96,6 +96,33 @@ STATUS_LABELS = { 'cancelled': 'Cancelled', } +def get_request_steps(req): + """ + Return a linear step sequence and the current step index for the request lifecycle. + Steps follow the standard flow: Pending -> Prompt Ready -> Songs Ready -> Approved -> Delivered. + Special statuses like 'revisions_requested' and 'paid' are mapped to the closest visible step. + Cancelled requests are returned with index -1 so the UI can render a cancelled state. + """ + steps = [ + ('pending', 'Pending'), + ('prompt_ready', 'Prompt Ready'), + ('songs_uploaded', 'Songs Ready'), + ('awaiting_payment', 'Approved'), + ('delivered', 'Delivered'), + ] + status = req.get('status', 'pending') + if status == 'cancelled': + return steps, -1 + if status == 'revisions_requested': + return steps, 2 # still at Songs Ready visually, with a revision note + if status == 'paid': + return steps, 3 # payment recorded, awaiting final delivery + for i, (key, _) in enumerate(steps): + if key == status: + return steps, i + return steps, 0 + + # --------------------------------------------------------------------------- # Public customer routes # --------------------------------------------------------------------------- @@ -1229,8 +1256,14 @@ def admin_request(rid): except Exception as e: flash(f'Payment recorded, but delivery email failed: {e}', 'error') + steps, current_step = get_request_steps(req) + musicgpt_status = req.get('musicgpt_status') + return redirect(url_for('admin_request', rid=rid)) + steps, current_step = get_request_steps(req) + musicgpt_status = req.get('musicgpt_status') + return render_template( 'admin/request.html', req=req, @@ -1246,7 +1279,10 @@ def admin_request(rid): musicgpt_models=get_musicgpt_models(), musicgpt_default_model=get_musicgpt_default_model(), format_musicgpt_cost=format_musicgpt_cost, - deliver_album_cover=load_booth_settings().get('deliver_album_cover', False) + deliver_album_cover=load_booth_settings().get('deliver_album_cover', False), + steps=steps, + current_step=current_step, + musicgpt_status=musicgpt_status, ) diff --git a/templates/admin/request.html b/templates/admin/request.html index cbcb440..75d93aa 100644 --- a/templates/admin/request.html +++ b/templates/admin/request.html @@ -137,6 +137,72 @@ } .cover-col{flex-shrink:0;} .status-col{flex:1;min-width:200px;} + .steps{ + display:flex; + list-style:none; + margin:0; + padding:0; + gap:.25rem; + align-items:flex-start; + } + .step-item{ + display:flex; + flex-direction:column; + align-items:center; + text-align:center; + flex:1; + min-width:80px; + position:relative; + } + .step-item:not(:last-child)::after{ + content:""; + position:absolute; + top:1rem; + left:50%; + width:100%; + height:2px; + background:#4b5563; + z-index:0; + } + .step-item.completed:not(:last-child)::after{ + background:#10b981; + } + .step-indicator{ + width:2rem; + height:2rem; + border-radius:50%; + display:flex; + align-items:center; + justify-content:center; + font-size:.9rem; + font-weight:700; + background:#374151; + color:#f3f4f6; + position:relative; + z-index:1; + border:2px solid transparent; + } + .step-item.completed .step-indicator{ + background:#10b981; + color:#000; + } + .step-item.current .step-indicator{ + background:#3b82f6; + color:#fff; + border-color:#93c5fd; + } + .step-item.upcoming .step-indicator{ + background:#374151; + color:#9ca3af; + } + .step-label{ + font-size:.75rem; + margin-top:.35rem; + color:#f3f4f6; + } + .step-item.upcoming .step-label{ + color:#9ca3af; + } .history-list{ margin:0; padding:0; @@ -183,7 +249,24 @@ {% endif %}
-

Status: {{ statuses[req.status] }}

+ {% if current_step == -1 %} +

Cancelled

+ {% else %} +
    + {% for i in range(steps|length) %} + {% set key, label = steps[i] %} + {% set is_completed = i < current_step %} + {% set is_current = i == current_step %} +
  1. +
    + {% if is_completed %}✓{% else %}{{ i + 1 }}{% endif %} +
    +
    {{ label }}
    +
  2. + {% endfor %} +
+ {% endif %} + {% if req.musicgpt_status %}

MusicGPT: {{ req.musicgpt_status }} {% if req.musicgpt_cost is not none %}({{ format_musicgpt_cost(req.musicgpt_cost) }}){% endif %}