feat: add operator-only notes field per request
This commit is contained in:
parent
41ca43b9c2
commit
3e25c3638b
5 changed files with 38 additions and 17 deletions
|
|
@ -69,7 +69,7 @@ pending → prompt_ready → songs_uploaded → awaiting_payment → paid → de
|
||||||
| `templates/player.html` | Customer audio player, approval, and revision form. |
|
| `templates/player.html` | Customer audio player, approval, and revision form. |
|
||||||
| `templates/admin/login.html` | Admin login page. |
|
| `templates/admin/login.html` | Admin login page. |
|
||||||
| `templates/admin/dashboard.html` | Operator queue with filters and auto-refresh. |
|
| `templates/admin/dashboard.html` | Operator queue with filters and auto-refresh. |
|
||||||
| `templates/admin/request.html` | Single-request detail / prompt / upload / delivery page. Customer email is editable here. |
|
| `templates/admin/request.html` | Single-request detail / prompt / upload / delivery page. Customer email and operator notes are editable here. |
|
||||||
| `templates/admin/settings.html` | Maintenance, settings, backup/restore, and reset page. |
|
| `templates/admin/settings.html` | Maintenance, settings, backup/restore, and reset page. |
|
||||||
| `static/Trollgorithm_booth.jpg` | Banner image on the request page. |
|
| `static/Trollgorithm_booth.jpg` | Banner image on the request page. |
|
||||||
| `static/DM-Logo_email.png` | Inline Dionysis Media logo attached to emails. |
|
| `static/DM-Logo_email.png` | Inline Dionysis Media logo attached to emails. |
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ Flask app that lets convention attendees request custom AI-generated theme songs
|
||||||
| `config.py` | Env vars. `ADMIN_PASSWORD` is plain text. |
|
| `config.py` | Env vars. `ADMIN_PASSWORD` is plain text. |
|
||||||
| `models.py` | SQLite schema + CRUD. `player_token` is a secret URL-safe token. |
|
| `models.py` | SQLite schema + CRUD. `player_token` is a secret URL-safe token. |
|
||||||
| `init_db.py` | Run once after deploy: `python init_db.py`. |
|
| `init_db.py` | Run once after deploy: `python init_db.py`. |
|
||||||
| `templates/admin/request.html` | Biggest template; prompt copy helpers and JS live here. Customer email is editable here. |
|
| `templates/admin/request.html` | Biggest template; prompt copy helpers and JS live here. Customer email and operator notes are editable here. |
|
||||||
| `templates/admin/dashboard.html` | Queue table + filters + auto-refresh + topbar Reset System button. |
|
| `templates/admin/dashboard.html` | Queue table + filters + auto-refresh + topbar Reset System button. |
|
||||||
| `templates/admin/settings.html` | SMTP config, MP3 metadata defaults, DB backup/restore, health check, reset. |
|
| `templates/admin/settings.html` | SMTP config, MP3 metadata defaults, DB backup/restore, health check, reset. |
|
||||||
| `templates/faq.html` | Customer FAQ page. |
|
| `templates/faq.html` | Customer FAQ page. |
|
||||||
|
|
|
||||||
13
app.py
13
app.py
|
|
@ -572,9 +572,9 @@ def admin_request(rid):
|
||||||
"""
|
"""
|
||||||
Detail/edit page for a single request.
|
Detail/edit page for a single request.
|
||||||
GET -> render customer info (email editable), prompt, upload status,
|
GET -> render customer info (email editable), prompt, upload status,
|
||||||
email status, and delivery forms.
|
email status, operator notes, and delivery forms.
|
||||||
POST -> handle one of five actions:
|
POST -> handle one of six actions:
|
||||||
update_customer_email, save_prompt, upload_songs,
|
update_customer_email, save_operator_notes, save_prompt, upload_songs,
|
||||||
notify_customer, mark_paid_deliver
|
notify_customer, mark_paid_deliver
|
||||||
Uploaded MP3s are tagged with metadata defaults from /admin/settings.
|
Uploaded MP3s are tagged with metadata defaults from /admin/settings.
|
||||||
"""
|
"""
|
||||||
|
|
@ -614,6 +614,11 @@ def admin_request(rid):
|
||||||
flash('Customer email updated.', 'success')
|
flash('Customer email updated.', 'success')
|
||||||
return redirect(url_for('admin_request', rid=rid))
|
return redirect(url_for('admin_request', rid=rid))
|
||||||
|
|
||||||
|
elif action == 'save_operator_notes':
|
||||||
|
update_request(rid, operator_notes=request.form.get('operator_notes', '').strip())
|
||||||
|
flash('Operator notes saved.', 'success')
|
||||||
|
return redirect(url_for('admin_request', rid=rid))
|
||||||
|
|
||||||
elif action == 'save_prompt':
|
elif action == 'save_prompt':
|
||||||
# Store the generated title/style/lyrics and mark prompt ready.
|
# Store the generated title/style/lyrics and mark prompt ready.
|
||||||
update_request(rid,
|
update_request(rid,
|
||||||
|
|
@ -786,7 +791,7 @@ def admin_settings():
|
||||||
'style_genre', 'extra_requests', 'vocal_gender', 'status', 'suno_title', 'suno_style',
|
'style_genre', 'extra_requests', 'vocal_gender', 'status', 'suno_title', 'suno_style',
|
||||||
'suno_lyrics', 'song_a_path', 'song_b_path', 'customer_approved',
|
'suno_lyrics', 'song_a_path', 'song_b_path', 'customer_approved',
|
||||||
'approval_notified_at', 'preview_sent_at', 'delivery_sent_at',
|
'approval_notified_at', 'preview_sent_at', 'delivery_sent_at',
|
||||||
'square_payment_ref', 'admin_alert_email', 'player_token', 'revision_note', 'revision_count'
|
'square_payment_ref', 'admin_alert_email', 'player_token', 'revision_note', 'revision_count', 'operator_notes'
|
||||||
}
|
}
|
||||||
health = {'ok': True, 'missing_columns': [], 'message': 'Database schema looks good.'}
|
health = {'ok': True, 'missing_columns': [], 'message': 'Database schema looks good.'}
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ CREATE TABLE IF NOT EXISTS requests (
|
||||||
admin_alert_email TEXT, -- reserved for future operator alerts; currently unused
|
admin_alert_email TEXT, -- reserved for future operator alerts; currently unused
|
||||||
player_token TEXT NOT NULL UNIQUE,
|
player_token TEXT NOT NULL UNIQUE,
|
||||||
revision_count INTEGER DEFAULT 0,
|
revision_count INTEGER DEFAULT 0,
|
||||||
revision_note TEXT
|
revision_note TEXT,
|
||||||
|
operator_notes TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(status);
|
CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(status);
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@
|
||||||
Single-request admin detail page.
|
Single-request admin detail page.
|
||||||
Sections:
|
Sections:
|
||||||
1. Customer info (with revisions note if any)
|
1. Customer info (with revisions note if any)
|
||||||
2. Generate and save Suno prompt
|
2. Operator notes (internal, operator-only)
|
||||||
3. Upload Version A and Version B MP3s
|
3. Generate and save Suno prompt
|
||||||
4. Send preview email with private player link
|
4. Upload Version A and Version B MP3s
|
||||||
5. Mark paid and deliver selected MP3(s)
|
5. Send preview email with private player link
|
||||||
|
6. Mark paid and deliver selected MP3(s)
|
||||||
*/
|
*/
|
||||||
body{
|
body{
|
||||||
font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
||||||
|
|
@ -162,9 +163,23 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 2: Generate Suno prompt -->
|
<!-- Section 2: Operator notes -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>1. Generate & Save Suno Prompt</h2>
|
<h2>Operator Notes</h2>
|
||||||
|
<p class="copy-hint">Internal notes for the booth team. Customers cannot see this.</p>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="save_operator_notes">
|
||||||
|
<label for="operator_notes">Notes</label>
|
||||||
|
<textarea id="operator_notes" name="operator_notes" rows="4" placeholder="e.g. paid cash, VIP friend, follow up...">{{ req.operator_notes or '' }}</textarea>
|
||||||
|
<div class="actions">
|
||||||
|
<button type="submit" class="secondary">Save Notes</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Section 3: Generate Suno prompt -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>1. Generate & Save Suno Prompt</h2>
|
||||||
|
|
||||||
<button type="button" onclick="copyPromptForHermes()">Copy customer info for Hermes</button>
|
<button type="button" onclick="copyPromptForHermes()">Copy customer info for Hermes</button>
|
||||||
<p class="copy-hint">Paste Hermes' Title, Style, and Lyrics directly into the fields below, then save.</p>
|
<p class="copy-hint">Paste Hermes' Title, Style, and Lyrics directly into the fields below, then save.</p>
|
||||||
|
|
@ -190,7 +205,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 3: Upload songs -->
|
<!-- Section 4: Upload songs -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>2. Upload Songs</h2>
|
<h2>2. Upload Songs</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -222,7 +237,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 4: Notify customer -->
|
<!-- Section 5: Notify customer -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>3. Notify Customer</h2>
|
<h2>3. Notify Customer</h2>
|
||||||
|
|
||||||
|
|
@ -248,9 +263,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 5: Payment & delivery -->
|
<!-- Section 6: Payment & delivery -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>4. Payment & Delivery</h2>
|
<h2>4. Payment & Delivery</h2>
|
||||||
<p>
|
<p>
|
||||||
Customer approved:
|
Customer approved:
|
||||||
{% if req.customer_approved == 'none' %}
|
{% if req.customer_approved == 'none' %}
|
||||||
|
|
|
||||||
Reference in a new issue