Add Steps progress indicator to admin request page
This commit is contained in:
parent
1eae83baaa
commit
de7e99b6ae
2 changed files with 121 additions and 2 deletions
38
app.py
38
app.py
|
|
@ -96,6 +96,33 @@ STATUS_LABELS = {
|
||||||
'cancelled': 'Cancelled',
|
'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
|
# Public customer routes
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -1229,8 +1256,14 @@ def admin_request(rid):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
flash(f'Payment recorded, but delivery email failed: {e}', 'error')
|
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))
|
return redirect(url_for('admin_request', rid=rid))
|
||||||
|
|
||||||
|
steps, current_step = get_request_steps(req)
|
||||||
|
musicgpt_status = req.get('musicgpt_status')
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin/request.html',
|
'admin/request.html',
|
||||||
req=req,
|
req=req,
|
||||||
|
|
@ -1246,7 +1279,10 @@ def admin_request(rid):
|
||||||
musicgpt_models=get_musicgpt_models(),
|
musicgpt_models=get_musicgpt_models(),
|
||||||
musicgpt_default_model=get_musicgpt_default_model(),
|
musicgpt_default_model=get_musicgpt_default_model(),
|
||||||
format_musicgpt_cost=format_musicgpt_cost,
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,72 @@
|
||||||
}
|
}
|
||||||
.cover-col{flex-shrink:0;}
|
.cover-col{flex-shrink:0;}
|
||||||
.status-col{flex:1;min-width:200px;}
|
.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{
|
.history-list{
|
||||||
margin:0;
|
margin:0;
|
||||||
padding:0;
|
padding:0;
|
||||||
|
|
@ -183,7 +249,24 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="status-col">
|
<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 %}
|
{% 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>
|
<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 %}
|
{% if req.musicgpt_cost is not none %}({{ format_musicgpt_cost(req.musicgpt_cost) }}){% endif %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue