Files
gogo-telefon/gogo/templates/dashboard/services.html
Senad Uka 646917c322 M3: chat widget, owner dashboard, super-admin panel
- Embeddable vanilla-JS chat widget (one script tag + tenant public key):
  WebSocket, resumable sessions (24h localStorage token), honeypot + per-IP
  and per-message rate limits, Bosnian UI (§7)
- Session auth (bcrypt + signed cookies), owner accounts + super-admins,
  admin impersonation ('otvori kao salon', §11)
- Owner dashboard (§10, Bosnian): requests with same actions as email,
  call/chat history with transcripts, usage bar; settings for profile,
  working hours (manual + LLM paste-to-parse), services CRUD + 'Zalijepi
  cjenovnik' LLM import with review step, scheduling (Google OAuth free/busy
  read-only + calendar mappings + buffers, partner status), telephony
  (forwarding MMI codes per operator, ring group, worker SIP creds + QR),
  agent voice/price-mode/notes + widget snippet, notifications/SMS templates
- Super-admin panel (§11): tenant CRUD incl. plan/minutes/paid-until,
  partner API config (encrypted secrets), number/SIM registry + assignment,
  connectivity test (live availability + dry-run push), versioned prompt
  template editor with publish/rollback, playground, usage overview, health
- Fixes: WAL + busy_timeout for sqlite tests, no awaits in WS disconnect
  path (deadlock), template publish autoflush bug
- 76 tests green (incl. widget WS e2e booking flow); dashboard, widget and
  admin verified live in a browser against Postgres

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:51:57 +02:00

86 lines
4.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Usluge — {{ owner.tenant.name }}{% endblock %}
{% block nav %}{% include "dashboard/_nav.html" %}{% endblock %}
{% block content %}
<h1>Usluge i cjenovnik</h1>
<div class="card">
<table>
<tr><th>Usluga</th><th>Trajanje</th><th>Cijena (KM)</th><th>Kućna posjeta</th><th>Napomena za asistenta</th><th></th></tr>
{% for s in services %}
<tr>
<form method="post" action="/dash/services/{{ s.id }}">
<td><input type="text" name="name" value="{{ s.name }}"></td>
<td style="width:90px"><input type="number" name="duration_min" value="{{ s.duration_min }}" min="5" step="5"></td>
<td style="width:150px;white-space:nowrap">
<input type="number" name="price_min" value="{{ s.price_min if s.price_min is not none }}" step="0.5" style="width:65px;display:inline-block">
<input type="number" name="price_max" value="{{ s.price_max if s.price_max is not none }}" step="0.5" style="width:65px;display:inline-block">
</td>
<td style="width:60px;text-align:center"><input type="checkbox" name="home_visit" {{ 'checked' if s.home_visit }}></td>
<td><input type="text" name="agent_note" value="{{ s.agent_note }}"></td>
<td style="white-space:nowrap">
<button class="small">Sačuvaj</button>
</form>
<form method="post" action="/dash/services/{{ s.id }}/delete" style="display:inline"
onsubmit="return confirm('Obrisati uslugu {{ s.name }}?')">
<button class="small red">Obriši</button>
</form>
</td>
</tr>
{% endfor %}
<tr>
<form method="post" action="/dash/services">
<td><input type="text" name="name" placeholder="Nova usluga…" required></td>
<td><input type="number" name="duration_min" value="30" min="5" step="5"></td>
<td style="white-space:nowrap">
<input type="number" name="price_min" step="0.5" style="width:65px;display:inline-block">
<input type="number" name="price_max" step="0.5" style="width:65px;display:inline-block">
</td>
<td style="text-align:center"><input type="checkbox" name="home_visit"></td>
<td><input type="text" name="agent_note"></td>
<td><button class="small green">Dodaj</button></td>
</form>
</tr>
</table>
</div>
<div class="card">
<h2>Zalijepi cjenovnik</h2>
<p class="muted">Zalijepite cijeli cjenovnik kako god ga imate (sa web stranice, iz Worda,
sa Facebooka…). Gogo će ga pretvoriti u tabelu — vi pregledate, ispravite trajanja i potvrdite.</p>
<form method="post" action="/dash/services/parse">
<textarea name="text" style="min-height:140px" placeholder="npr.
Šišanje i feniranje ......... 25-35 KM
Farbanje ..................... od 60 KM
Manikir ...................... 20 KM"></textarea>
<button class="ghost" style="margin-top:8px">Pročitaj cjenovnik</button>
</form>
</div>
{% if parsed %}
<div class="card">
<h2>Provjeri i potvrdi</h2>
<p class="muted">Ovako je Gogo pročitao vaš cjenovnik. Ispravite šta treba pa potvrdite —
usluge će biti <b>dodane</b> postojećim.</p>
<form method="post" action="/dash/services/import">
<table>
<tr><th></th><th>Usluga</th><th>Trajanje (min)</th><th>Cijena od</th><th>Cijena do</th><th>Kućna</th><th>Napomena</th></tr>
{% for p in parsed %}
<tr>
<td><input type="checkbox" name="row_{{ loop.index0 }}" checked></td>
<td><input type="text" name="name_{{ loop.index0 }}" value="{{ p.name }}"></td>
<td><input type="number" name="duration_{{ loop.index0 }}" value="{{ p.duration_min }}" min="5" step="5"></td>
<td><input type="number" name="pmin_{{ loop.index0 }}" value="{{ p.price_min if p.price_min is not none }}" step="0.5"></td>
<td><input type="number" name="pmax_{{ loop.index0 }}" value="{{ p.price_max if p.price_max is not none }}" step="0.5"></td>
<td style="text-align:center"><input type="checkbox" name="home_{{ loop.index0 }}" {{ 'checked' if p.home_visit }}></td>
<td><input type="text" name="note_{{ loop.index0 }}" value="{{ p.note or '' }}"></td>
</tr>
{% endfor %}
</table>
<input type="hidden" name="count" value="{{ parsed | length }}">
<button class="green" style="margin-top:12px">Dodaj označene usluge</button>
</form>
</div>
{% endif %}
{% endblock %}