Previously the badge always showed next to the price for any per_100g product and never next to the weight at all. Adds two per-product checkboxes (show_estimate_badge_price, default on; show_estimate_badge_weight, default off) so the admin can turn either one on or off independently — covers a shashlik-style product where the weight itself is also just an estimate, not only the price. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
145 lines
7.4 KiB
HTML
145 lines
7.4 KiB
HTML
{% extends "admin/base.html" %}
|
||
{% block content %}
|
||
<h1>{{ 'Редагувати товар' if product else 'Новий товар' }}</h1>
|
||
|
||
<div class="card" style="max-width:520px;margin:0 auto;">
|
||
<form method="post" enctype="multipart/form-data">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||
<label>Категорія</label>
|
||
<select name="category_id">
|
||
{% for c in categories %}
|
||
<option value="{{ c.id }}" {{ 'selected' if product and product.category_id == c.id }}>{{ c.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
|
||
<label>Назва</label>
|
||
<input type="text" name="name" value="{{ product.name if product else '' }}" required>
|
||
|
||
<label>Опис</label>
|
||
<textarea name="description" rows="3">{{ product.description if product else '' }}</textarea>
|
||
|
||
<label>Назва EN <span class="muted">(необов'язково — для перемикача мов у Mini App)</span></label>
|
||
<input type="text" name="name_en" value="{{ product.name_en if product and product.name_en else '' }}">
|
||
|
||
<label>Опис EN <span class="muted">(необов'язково)</span></label>
|
||
<textarea name="description_en" rows="3">{{ product.description_en if product and product.description_en else '' }}</textarea>
|
||
|
||
<div class="row">
|
||
<div>
|
||
<label>Вага / обʼєм (напр. 300 г)</label>
|
||
<input type="text" name="weight" value="{{ product.weight if product else '' }}">
|
||
</div>
|
||
<div>
|
||
<label>Вага/обʼєм EN <span class="muted">(напр. 300 g)</span></label>
|
||
<input type="text" name="weight_en" value="{{ product.weight_en if product and product.weight_en else '' }}">
|
||
</div>
|
||
</div>
|
||
|
||
<label>Тип ціни</label>
|
||
<select name="pricing_mode" id="pricing-mode" onchange="syncPricingMode()">
|
||
<option value="fixed" {{ 'selected' if not product or product.pricing_mode == 'fixed' }}>Фіксована ціна за порцію</option>
|
||
<option value="per_100g" {{ 'selected' if product and product.pricing_mode == 'per_100g' }}>Ціна за 100 г (страва "на вагу")</option>
|
||
</select>
|
||
|
||
<label id="price-label">Ціна, грн</label>
|
||
<input type="number" name="price" id="price-input" value="{{ product.price if product else '' }}" required min="0" oninput="recalcEstimate()">
|
||
|
||
<div id="reference-weight-row" class="row">
|
||
<div>
|
||
<label>Орієнтовна вага порції, г</label>
|
||
<input type="number" name="reference_weight_g" id="reference-weight-input"
|
||
value="{{ product.reference_weight_g if product and product.reference_weight_g else '' }}"
|
||
min="1" oninput="recalcEstimate()">
|
||
</div>
|
||
<div>
|
||
<label>Орієнтовна ціна, грн</label>
|
||
<input type="number" name="estimated_price_override" id="estimate-input" min="0"
|
||
value="{{ product.estimated_price_override if product and product.estimated_price_override else '' }}"
|
||
{% if product and product.estimated_price_override %}data-touched="1"{% endif %}
|
||
oninput="this.dataset.touched='1';" placeholder="напр. 450">
|
||
</div>
|
||
</div>
|
||
<p class="muted field-hint" id="estimate-hint" style="margin-top:-10px;"></p>
|
||
|
||
<div id="estimate-badges-row" class="row">
|
||
<label style="display:flex;align-items:center;gap:8px;">
|
||
<input type="checkbox" name="show_estimate_badge_price" class="admin-checkbox"
|
||
{{ 'checked' if not product or product.show_estimate_badge_price }}>
|
||
Показувати «≈» біля ціни
|
||
</label>
|
||
<label style="display:flex;align-items:center;gap:8px;">
|
||
<input type="checkbox" name="show_estimate_badge_weight" class="admin-checkbox"
|
||
{{ 'checked' if product and product.show_estimate_badge_weight }}>
|
||
Показувати «≈» біля ваги
|
||
</label>
|
||
</div>
|
||
|
||
<label>Фото</label>
|
||
{% if product and product.image_url %}
|
||
<img class="thumb" src="{{ product.image_url }}" style="width:80px;height:80px;margin-bottom:8px;">
|
||
{% endif %}
|
||
<input type="file" name="image" accept="image/*">
|
||
|
||
<label>Алергени</label>
|
||
<p class="muted field-hint" style="margin-top:0;">Позначте, що є у складі — покажеться клієнту іконками прямо на картці страви в меню.</p>
|
||
<div class="allergen-picker">
|
||
{% for code, (label, emoji, label_en) in allergens.items() %}
|
||
<label class="allergen-check">
|
||
<input type="checkbox" name="allergens" value="{{ code }}" class="admin-checkbox" {{ 'checked' if product and code in product.allergen_codes }}>
|
||
<span>{{ emoji }} {{ label }}</span>
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<label>Порядок сортування</label>
|
||
<input type="number" name="sort_order" value="{{ product.sort_order if product else 0 }}">
|
||
|
||
<label style="display:flex;align-items:center;gap:8px;">
|
||
<input type="checkbox" name="is_active" class="admin-checkbox" {{ 'checked' if not product or product.is_active }}>
|
||
Активний (показувати в меню)
|
||
</label>
|
||
|
||
<div style="margin-top:16px;display:flex;gap:10px;">
|
||
<button class="btn" type="submit">Зберегти</button>
|
||
<a class="btn secondary" href="{{ url_for('admin.products_list') }}">Скасувати</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
{% if inventory_enabled and product %}
|
||
<a class="btn secondary small" style="margin-top:14px;" href="{{ url_for('admin.inventory_recipe_edit', product_id=product.id) }}">🧾 Рецептура (склад страви)</a>
|
||
{% endif %}
|
||
|
||
<script>
|
||
function syncPricingMode() {
|
||
var isPer100g = document.getElementById('pricing-mode').value === 'per_100g';
|
||
document.getElementById('reference-weight-row').style.display = isPer100g ? 'flex' : 'none';
|
||
document.getElementById('estimate-hint').style.display = isPer100g ? 'block' : 'none';
|
||
document.getElementById('estimate-badges-row').style.display = isPer100g ? 'flex' : 'none';
|
||
document.getElementById('price-label').textContent = isPer100g ? 'Ціна за 100 г, грн' : 'Ціна, грн';
|
||
recalcEstimate();
|
||
}
|
||
|
||
function recalcEstimate() {
|
||
var estimateInput = document.getElementById('estimate-input');
|
||
var hint = document.getElementById('estimate-hint');
|
||
if (document.getElementById('pricing-mode').value !== 'per_100g') {
|
||
hint.textContent = '';
|
||
return;
|
||
}
|
||
var price100 = parseFloat(document.getElementById('price-input').value || '0');
|
||
var refWeight = parseFloat(document.getElementById('reference-weight-input').value || '0');
|
||
var computed = refWeight ? Math.round(price100 * refWeight / 100) : null;
|
||
// Auto-fill the estimate from price×weight only until the admin types
|
||
// their own value directly — same "touched" pattern as the order-line
|
||
// price override, so a deliberate manual number never gets clobbered.
|
||
if (!estimateInput.dataset.touched) {
|
||
estimateInput.value = computed !== null ? computed : '';
|
||
}
|
||
hint.textContent = computed !== null
|
||
? 'Розрахунково: ≈ ' + computed + ' грн — можна вписати своє значення вручну.'
|
||
: 'Вкажіть вагу для автоматичного розрахунку, або впишіть ціну вручну.';
|
||
}
|
||
|
||
syncPricingMode();
|
||
</script>
|
||
{% endblock %}
|