Files
cantarire-etichetare/cantarire/templates/settings.html
T
ske087 bff6d70b17 Initial commit: aplicatie de cantarire si etichetare
Aplicatie Flask + SQLite pentru productie alimentara: cantarirea
componentelor unui produs finit si tiparirea etichetei prin QZ Tray.

- produse finite (retete) formate din componente cantarite
- designer vizual de etichete (mm/px, zona sigura, campuri variabile,
  cod QR cu continut din mai multe bucati)
- legare produs finit -> sablon de eticheta
- navigare pe doua taburi: Cantarire / Setari
- administrare: componente, produse finite, sabloane, campuri, istoric
- backup, resetare baza de date, checklist de pregatire, /healthz
- CLI de administrare si serviciu systemd pentru productie
- vendor/syncpulse-scale-bridge (GPL-3.0-or-later) pentru citirea cantarului
2026-10-18 23:38:45 +03:00

195 lines
7.5 KiB
HTML

{% extends "base.html" %}
{% block title %}Setări{% endblock %}
{% block content %}
<section class="page-head">
<div>
<h1>Setări</h1>
<p class="subtitle">
Datele firmei, cântarul și imprimanta; de aici ajungi la evidențele aplicației.
</p>
</div>
</section>
<nav class="hub-grid" aria-label="Administrare">
<a class="hub-card" href="{{ url_for('components.index') }}">
<span class="hub-title">Componente</span>
<span class="hub-meta">{{ components|length }} definite · ce se cântărește</span>
</a>
<a class="hub-card" href="{{ url_for('recipes.index') }}">
<span class="hub-title">Produse finite</span>
<span class="hub-meta">{{ recipes|length }} rețete · combinații de componente</span>
</a>
<a class="hub-card" href="{{ url_for('label_templates.index') }}">
<span class="hub-title">Șabloane</span>
<span class="hub-meta">{{ templates|length }} șabloane · design de etichetă</span>
</a>
<a class="hub-card" href="{{ url_for('fields.index') }}">
<span class="hub-title">Câmpuri</span>
<span class="hub-meta">{{ fields_count }}
{{ 'variabilă' if fields_count == 1 else 'variabile' }} · text pentru etichete</span>
</a>
<a class="hub-card" href="{{ url_for('history.index') }}">
<span class="hub-title">Istoric</span>
<span class="hub-meta">Pachete finalizate, filtre și export CSV</span>
</a>
<a class="hub-card" href="{{ url_for('monitor.index') }}">
<span class="hub-title">Monitor serial</span>
<span class="hub-meta">Identifică un cântar nou</span>
</a>
</nav>
<div class="grid settings-grid">
<section class="card">
<form method="post" class="form">
<h2>Date firmă</h2>
<div class="field">
<label for="company_name">Denumire firmă</label>
<input id="company_name" name="company_name" type="text" maxlength="200"
value="{{ app_settings.company_name }}">
</div>
<div class="field">
<label for="company_address">Adresă</label>
<input id="company_address" name="company_address" type="text" maxlength="200"
value="{{ app_settings.company_address }}">
</div>
<div class="field">
<label for="company_phone">Telefon</label>
<input id="company_phone" name="company_phone" type="text" maxlength="200"
value="{{ app_settings.company_phone }}">
</div>
<h2>Cântar</h2>
<div class="field">
<label for="active_scale_profile">Profil cântar activ</label>
<select id="active_scale_profile" name="active_scale_profile">
<option value="">— introducere manuală —</option>
{% for p in profiles %}
<option value="{{ p.name }}" {{ 'selected' if app_settings.active_scale_profile == p.name }}>
{{ p.label }} · {{ p.port }} · {{ p.protocol }}
</option>
{% endfor %}
</select>
{% if not profiles %}
<p class="muted small">
Nu există profile. Configurează cântarul în SyncPulse Scale Bridge sau folosește
<a href="{{ url_for('monitor.index') }}">monitorul serial</a>.
</p>
{% endif %}
</div>
<h2>Etichete</h2>
<div class="field">
<label for="default_template_id">Șablon implicit</label>
<select id="default_template_id" name="default_template_id">
<option value="">— primul disponibil —</option>
{% for t in templates %}
<option value="{{ t.id }}" {{ 'selected' if app_settings.default_template_id == t.id|string }}>
{{ t.name }}
</option>
{% endfor %}
</select>
</div>
<div class="field">
<label for="default_operator">Operator implicit</label>
<input id="default_operator" name="default_operator" type="text" maxlength="64"
value="{{ app_settings.default_operator }}">
</div>
<label class="check">
<input type="checkbox" name="auto_print" {{ 'checked' if app_settings.auto_print == '1' }}>
Tipărește automat eticheta după ultima componentă
</label>
<div class="actions">
<button class="btn primary" type="submit">Salvează setările</button>
</div>
</form>
</section>
<aside class="card help-card">
<h2>Starea cântarului</h2>
<p>
{% if scale_available %}
<span class="badge ok">bibliotecă încărcată</span>
{% else %}
<span class="badge err">indisponibil</span>
{% endif %}
</p>
{% if scale_error %}<p class="message error">{{ scale_error }}</p>{% endif %}
<h2>Profile detectate</h2>
{% if profiles %}
<ul class="profile-list">
{% for p in profiles %}
<li>
<strong>{{ p.label }}</strong><br>
<span class="muted">{{ p.name }} · {{ p.port }} · {{ p.protocol }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Niciun profil salvat în <code>instance/bridge.json</code>.</p>
{% endif %}
<h2>Imprimantă (QZ Tray)</h2>
<p class="muted small">
Eticheta este generată de aplicație și tipărită pe imprimanta termică prin QZ Tray, separat de
cântar. Biblioteca din <code>static/js/vendor/qz-tray.js</code> este patch-uită pentru
autentificare cu pairing key.
</p>
<p class="qz-state-line">
<span class="badge" data-qz-status>QZ Tray: se verifică…</span>
<span class="muted small" data-qz-version></span>
</p>
<div class="field">
<label for="qz-printer">Imprimantă implicită</label>
<select id="qz-printer" data-qz-printer>
<option value="">Implicit (sistem)</option>
</select>
</div>
<div class="actions">
<button class="btn" type="button" data-qz-connect>Conectează</button>
<button class="btn" type="button" data-qz-refresh>Reîncarcă lista</button>
<button class="btn ghost" type="button" data-qz-test>Tipărește test</button>
</div>
<h2>Integrare</h2>
<p class="muted small">
Aplicația importă direct pachetul <code>vendor/syncpulse-scale-bridge</code>; profilul serial
este citit din <code>instance/bridge.json</code>.
</p>
<p class="muted small">
Pentru teste fără hardware: <code>CANTARIRE_SIMULATE=mahak_mds</code>.
</p>
<h2>Sistem</h2>
<p class="small">
{% if setup.ready %}<span class="badge ok">gata de cântărit</span>
{% else %}<span class="badge warn">{{ setup.missing|length }} de completat</span>{% endif %}
<span class="badge">schema v{{ db_status.schema_version }}</span>
</p>
<ul class="system-list small">
<li><span class="muted">Bază de date</span> <code>{{ db_status.path }}</code></li>
<li><span class="muted">Mărime</span> {{ db_status.size_human }}</li>
<li><span class="muted">Integritate</span> {{ db_status.integrity }}</li>
<li><span class="muted">Înregistrări</span>
{{ db_status.counts.components }} componente ·
{{ db_status.counts.recipes }} produse finite ·
{{ db_status.counts.packages }} pachete</li>
</ul>
<div class="actions">
<a class="btn" href="{{ url_for('main.settings_backup') }}">Descarcă backup</a>
<a class="btn ghost" href="{{ url_for('main.healthz') }}" target="_blank" rel="noopener">Stare (/healthz)</a>
</div>
{% if backups %}
<p class="muted small">Ultimele backup-uri:</p>
<ul class="system-list small">
{% for b in backups %}
<li>{{ b.created_at.strftime('%d.%m.%Y %H:%M') }} · {{ b.size_human }}</li>
{% endfor %}
</ul>
{% endif %}
</aside>
</div>
{% endblock %}