Add Steps progress indicator to admin request page

This commit is contained in:
Troll (Hermes Agent) 2026-08-11 16:36:03 +00:00
parent 1eae83baaa
commit de7e99b6ae
2 changed files with 121 additions and 2 deletions

38
app.py
View file

@ -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,
)

View file

@ -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 @@
</div>
{% endif %}
<div class="status-col">
<p style="margin:0"><strong>Status:</strong> <span class="status-badge">{{ statuses[req.status] }}</span></p>
{% if current_step == -1 %}
<p style="margin:0"><span class="status-badge missing">Cancelled</span></p>
{% else %}
<ol class="steps" aria-label="Request progress">
{% for i in range(steps|length) %}
{% set key, label = steps[i] %}
{% set is_completed = i < current_step %}
{% set is_current = i == current_step %}
<li class="step-item {% if is_completed %}completed{% elif is_current %}current{% else %}upcoming{% endif %}" {% if is_current %}aria-current="step"{% endif %}>
<div class="step-indicator">
{% if is_completed %}✓{% else %}{{ i + 1 }}{% endif %}
</div>
<div class="step-label">{{ label }}</div>
</li>
{% endfor %}
</ol>
{% endif %}
{% if req.musicgpt_status %}
<p style="margin:.5rem 0 0 0"><strong>MusicGPT:</strong> <span class="status-badge {% if req.musicgpt_status == 'COMPLETED' %}ok{% elif req.musicgpt_status in ('FAILED','ERROR','CANCELLED') %}missing{% endif %}">{{ req.musicgpt_status }}</span>
{% if req.musicgpt_cost is not none %}({{ format_musicgpt_cost(req.musicgpt_cost) }}){% endif %}