Add custom product with full nutrients, bigger + buttons with border

Custom product:
- "+" button next to search on Products tab
- showAddProductModal: name, KBJU, + all MICRO_FIELDS (34 nutrient fields)
- All optional except name
- Creates product with source="manual"
- Refreshes search after save

Button style:
- .add-btn: 0.5px border with primary color, rounded corners (6px)
- Min 40x36px for finger-friendly tapping on retina
- Flexbox centered content
This commit is contained in:
4aiman 2026-05-29 18:21:49 +03:00
commit f871e5bb28
2 changed files with 72 additions and 4 deletions

View file

@ -478,12 +478,18 @@ select {
.add-btn {
background: none;
border: none;
border: 0.5px solid var(--primary);
border-radius: 6px;
color: var(--primary);
font-size: 22px;
line-height: 1;
cursor: pointer;
padding: 0 4px;
padding: 4px 10px;
min-width: 40px;
min-height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
.log-list {

View file

@ -1197,8 +1197,11 @@ async function renderProducts(container) {
<button class="sub-tab" data-tab="recipes">Рецепты</button>
</div>
<div id="products-tab">
<input id="prod-search" class="search-input" type="search"
placeholder="Название, бренд или ингредиент..." />
<div style="display:flex;gap:8px;align-items:center">
<input id="prod-search" class="search-input" type="search" style="flex:1"
placeholder="Название, бренд или ингредиент..." />
<button class="add-btn" id="add-custom-product" title="Свой продукт">+</button>
</div>
<div id="prod-count" class="prod-count"></div>
<div id="prod-results" class="prod-list"></div>
</div>
@ -1339,6 +1342,65 @@ async function renderProducts(container) {
listEl.innerHTML = `<div class="prod-hint">Введите название продукта</div>`;
input.focus();
// Add custom product button
const addCustomBtn = container.querySelector("#add-custom-product");
if (addCustomBtn) addCustomBtn.onclick = () => showAddProductModal(() => doSearch(true));
}
function showAddProductModal(onSaved) {
const microHtml = MICRO_FIELDS.map(f => {
return `<div class="form-group micro-field">
<label>${f.label}</label>
<input id="np-${f.key}" type="number" inputmode="decimal" step="any" placeholder="—" />
</div>`;
}).join("");
showModal(`
<h3>Новый продукт</h3>
<div class="form-group">
<label>Название</label>
<input id="np-name" type="text" placeholder="Тесто для чебуреков" />
</div>
<div style="display:flex;gap:8px">
<div class="form-group" style="flex:1">
<label>ккал/100г</label>
<input id="np-kcal" type="number" inputmode="decimal" placeholder="260" />
</div>
<div class="form-group" style="flex:1">
<label>Б</label>
<input id="np-protein" type="number" inputmode="decimal" step="0.1" placeholder="7" />
</div>
<div class="form-group" style="flex:1">
<label>Ж</label>
<input id="np-fat" type="number" inputmode="decimal" step="0.1" placeholder="4" />
</div>
<div class="form-group" style="flex:1">
<label>У</label>
<input id="np-carbs" type="number" inputmode="decimal" step="0.1" placeholder="48" />
</div>
</div>
<p style="color:var(--muted);font-size:12px;margin:8px 0 4px">Нутриенты на 100г (опционально):</p>
<div class="micro-grid">${microHtml}</div>`,
async overlay => {
const name = overlay.querySelector("#np-name").value.trim();
if (!name) return false;
const data = {
name,
kcal_per_100g: parseFloat(overlay.querySelector("#np-kcal").value) || null,
protein_per_100g: parseFloat(overlay.querySelector("#np-protein").value) || null,
fat_per_100g: parseFloat(overlay.querySelector("#np-fat").value) || null,
carbs_per_100g: parseFloat(overlay.querySelector("#np-carbs").value) || null,
source: "manual",
};
for (const f of MICRO_FIELDS) {
let val = parseFloat(overlay.querySelector(`#np-${f.key}`).value) || null;
if (val && f.mult) val *= f.mult;
data[f.key] = val;
}
await DB.addProduct(data);
if (onSaved) onSaved();
});
}
// ---------------------------------------------------------------------------