bff6d70b17
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
121 lines
4.0 KiB
HTML
121 lines
4.0 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ "Câmp nou" if is_new else "Editare câmp" }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<section class="page-head">
|
|
<div>
|
|
<h1>{{ "Câmp nou" if is_new else "Editare câmp" }}</h1>
|
|
<p class="subtitle">
|
|
Scrie textul etichetei; între acolade poți referi un câmp existent.
|
|
</p>
|
|
</div>
|
|
<a class="btn ghost" href="{{ url_for('fields.index') }}">Înapoi la listă</a>
|
|
</section>
|
|
|
|
<section class="card narrow">
|
|
<form method="post" class="form">
|
|
<div class="field">
|
|
<label for="label">Denumire <span class="req">*</span></label>
|
|
<input id="label" name="label" type="text" maxlength="{{ max_label }}" required
|
|
value="{{ field.label or '' }}" placeholder="ex. Contact firmă" autofocus>
|
|
<p class="muted small">Apare în popup-ul din designer.</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="template">Text <span class="req">*</span></label>
|
|
<textarea id="template" name="template" rows="3" maxlength="{{ max_template }}"
|
|
placeholder="ex. Tel. {{ '{{company.phone}}' }}">{{ field.template or '' }}</textarea>
|
|
<p class="muted small">
|
|
Poți folosi orice câmp din tabelul de mai jos, scris între <code>{{ '{{' }}</code> și
|
|
<code>{{ '}}' }}</code>.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Previzualizare cu date demo</label>
|
|
<div id="field-preview" class="field-preview muted">—</div>
|
|
</div>
|
|
|
|
<label class="check">
|
|
<input type="checkbox" name="active"
|
|
{{ 'checked' if field.active in [True, 1, '1', 'on', 'true'] or is_new }}>
|
|
Câmp activ (apare în designer)
|
|
</label>
|
|
|
|
<div class="actions">
|
|
<button class="btn primary" type="submit">Salvează</button>
|
|
<a class="btn ghost" href="{{ url_for('fields.index') }}">Renunță</a>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="card narrow">
|
|
<h2>Câmpuri disponibile</h2>
|
|
<p class="muted small">Click pe un câmp pentru a-l insera în text.</p>
|
|
<div class="field-chips">
|
|
{% for c in catalog %}
|
|
<button class="field-chip" type="button" data-insert="{{ c.key }}"
|
|
title="{{ c.label }} · exemplu: {{ c.sample }}">
|
|
<span class="chip-label">{{ c.label }}</span>
|
|
<span class="chip-sample">{{ c.sample }}</span>
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
(function () {
|
|
var textarea = document.getElementById('template');
|
|
var preview = document.getElementById('field-preview');
|
|
var timer = null;
|
|
var url = {{ url_for('fields.preview')|tojson }};
|
|
|
|
function refresh() {
|
|
var text = textarea.value.trim();
|
|
if (!text) {
|
|
preview.textContent = '—';
|
|
preview.className = 'field-preview muted';
|
|
return;
|
|
}
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function () {
|
|
fetch(url + '?template=' + encodeURIComponent(text), {
|
|
headers: { Accept: 'application/json' }
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (!data.ok) {
|
|
preview.textContent = data.error;
|
|
preview.className = 'field-preview error';
|
|
return;
|
|
}
|
|
preview.textContent = data.value || '(gol)';
|
|
preview.className = 'field-preview';
|
|
})
|
|
.catch(function () {
|
|
preview.textContent = 'Previzualizarea nu a putut fi calculată.';
|
|
preview.className = 'field-preview error';
|
|
});
|
|
}, 250);
|
|
}
|
|
|
|
textarea.addEventListener('input', refresh);
|
|
refresh();
|
|
|
|
document.querySelectorAll('[data-insert]').forEach(function (button) {
|
|
button.addEventListener('click', function () {
|
|
var token = '{{ "{{" }}' + button.dataset.insert + '{{ "}}" }}';
|
|
var start = textarea.selectionStart || 0;
|
|
var end = textarea.selectionEnd || 0;
|
|
textarea.value = textarea.value.slice(0, start) + token + textarea.value.slice(end);
|
|
textarea.focus();
|
|
textarea.selectionStart = textarea.selectionEnd = start + token.length;
|
|
refresh();
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|