Manual KBJU input, omega-3/6/9, product micronutrients, nutrient strip expand
1. Manual food entry: added B/Zh/U fields. Creates custom product in DB with user-provided KBJU, then logs it. Fixed addProduct lastInsertId. 2. Omega 3-6-9: columns in supplements, in supplement totals query, in MICRO_FIELDS form (34 fields total now). 3. Product card micronutrients: searchProducts now fetches all micro columns. renderProdDetails shows nutrient chips (sorted by deficiency) when product is expanded. 4. Supplement form: added B5, B7, chromium, manganese, molybdenum, iodine, phosphorus, omega-3/6/9, silicon, vanadium, lutein, lycopene. 5. Nutrient strip: tap to expand/collapse. Collapsed = 1 row (36px). Tap = toggle full view showing all nutrients. 6. Seed DB v6: added "Тесто для чебуреков 500г" (261 kcal, 7.1/3.8/48.2).
This commit is contained in:
parent
885ef27f36
commit
de5cfd5c21
4 changed files with 99 additions and 28 deletions
|
|
@ -1452,6 +1452,11 @@ select {
|
|||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.nutrient-strip.nutrient-collapsed {
|
||||
max-height: 36px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.nut-item {
|
||||
font-size: 12px;
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -110,7 +110,9 @@ async function searchProducts(query, limit) {
|
|||
return select(
|
||||
`SELECT id, name, brand, category, weight_g, kcal_per_100g,
|
||||
protein_per_100g, fat_per_100g, carbs_per_100g, fiber_per_100g,
|
||||
composition
|
||||
composition, nutrients_guessed,
|
||||
vit_a_mcg, vit_c_mg, vit_b1_mg, vit_b2_mg, vit_b3_mg, vit_e_mg,
|
||||
calcium_mg, iron_mg, magnesium_mg, potassium_mg, sodium_mg, phosphorus_mg
|
||||
FROM products
|
||||
WHERE name_lower LIKE ?1
|
||||
GROUP BY COALESCE(store_id, id)
|
||||
|
|
@ -152,7 +154,8 @@ async function addProduct(p) {
|
|||
p.source ?? "manual",
|
||||
]
|
||||
);
|
||||
return r.lastInsertId;
|
||||
const rows = await select("SELECT id FROM products ORDER BY id DESC LIMIT 1");
|
||||
return rows[0]?.id;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -540,7 +543,10 @@ async function getSupplementTotalsForDate(date) {
|
|||
SUM(COALESCE(sl.dose_g,1) * s.molybdenum_mcg) as molybdenum_mcg,
|
||||
SUM(COALESCE(sl.dose_g,1) * s.iodine_mg) as iodine_mg,
|
||||
SUM(COALESCE(sl.dose_g,1) * s.phosphorus_mg) as phosphorus_mg,
|
||||
SUM(COALESCE(sl.dose_g,1) * s.carotene_mcg) as carotene_mcg
|
||||
SUM(COALESCE(sl.dose_g,1) * s.carotene_mcg) as carotene_mcg,
|
||||
SUM(COALESCE(sl.dose_g,1) * s.omega3_mg) as omega3_mg,
|
||||
SUM(COALESCE(sl.dose_g,1) * s.omega6_mg) as omega6_mg,
|
||||
SUM(COALESCE(sl.dose_g,1) * s.omega9_mg) as omega9_mg
|
||||
FROM supplement_log sl
|
||||
JOIN supplements s ON sl.supplement_id = s.id
|
||||
WHERE sl.date = ?1`,
|
||||
|
|
@ -1120,6 +1126,9 @@ async function runMigrations() {
|
|||
await addSuppCol("lutein_mg", "REAL"); // лютеин — no RDI
|
||||
await addSuppCol("lycopene_mg", "REAL"); // ликопин — no RDI
|
||||
await addSuppCol("carotene_mcg", "REAL"); // бета-каротин
|
||||
await addSuppCol("omega3_mg", "REAL"); // омега-3
|
||||
await addSuppCol("omega6_mg", "REAL"); // омега-6
|
||||
await addSuppCol("omega9_mg", "REAL"); // омега-9
|
||||
|
||||
console.log("[db.js] recipe migrations done, checking seq repair...");
|
||||
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ async function renderToday(container) {
|
|||
|
||||
<section class="log-section">
|
||||
<div class="section-header"><h3>Нутриенты</h3></div>
|
||||
<div class="nutrient-strip" id="daily-nutrients"></div>
|
||||
<div class="nutrient-strip nutrient-collapsed" id="daily-nutrients"></div>
|
||||
</section>
|
||||
|
||||
<section class="log-section">
|
||||
|
|
@ -430,6 +430,8 @@ async function renderToday(container) {
|
|||
const nutrients = calcDailyNutrients(food, suppTotals);
|
||||
dailyNutEl.innerHTML = renderNutrientChips(nutrients)
|
||||
|| `<div class="log-empty" style="padding:6px 14px">нет данных о нутриентах</div>`;
|
||||
// Tap to expand/collapse
|
||||
dailyNutEl.onclick = () => dailyNutEl.classList.toggle("nutrient-collapsed");
|
||||
}
|
||||
|
||||
container.querySelector("#add-weight-btn").onclick = () => showWeightModal();
|
||||
|
|
@ -893,11 +895,25 @@ function showFoodModal() {
|
|||
<div id="m-manual-fields">
|
||||
<div class="form-group">
|
||||
<label>Или вручную: описание</label>
|
||||
<input id="m-desc" type="text" placeholder="гречка с молоком" />
|
||||
<input id="m-desc" type="text" placeholder="Тесто для чебуреков" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>ккал (примерно)</label>
|
||||
<input id="m-kcal" type="number" inputmode="numeric" placeholder="350" />
|
||||
<label>ккал на 100г</label>
|
||||
<input id="m-kcal" type="number" inputmode="numeric" placeholder="260" />
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<div class="form-group" style="flex:1">
|
||||
<label>Б</label>
|
||||
<input id="m-protein" type="number" inputmode="decimal" step="0.1" placeholder="7" />
|
||||
</div>
|
||||
<div class="form-group" style="flex:1">
|
||||
<label>Ж</label>
|
||||
<input id="m-fat" type="number" inputmode="decimal" step="0.1" placeholder="4" />
|
||||
</div>
|
||||
<div class="form-group" style="flex:1">
|
||||
<label>У</label>
|
||||
<input id="m-carbs" type="number" inputmode="decimal" step="0.1" placeholder="48" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
@ -925,9 +941,22 @@ function showFoodModal() {
|
|||
await DB.addFoodEntry(state.date, time, selectedProduct.id, amountG, null, unitId, unitAmount);
|
||||
} else {
|
||||
const desc = overlay.querySelector("#m-desc").value.trim();
|
||||
const kcal = parseFloat(overlay.querySelector("#m-kcal").value) || null;
|
||||
if (!desc) return false;
|
||||
await DB.addFoodManual(state.date, time, desc, kcal);
|
||||
const kcal = parseFloat(overlay.querySelector("#m-kcal").value) || null;
|
||||
const protein = parseFloat(overlay.querySelector("#m-protein").value) || null;
|
||||
const fat = parseFloat(overlay.querySelector("#m-fat").value) || null;
|
||||
const carbs = parseFloat(overlay.querySelector("#m-carbs").value) || null;
|
||||
const amount = rawAmount;
|
||||
if (kcal || protein || fat || carbs) {
|
||||
// Create a custom product in DB, then log it
|
||||
const pid = await DB.addProduct({
|
||||
name: desc, kcal_per_100g: kcal, protein_per_100g: protein,
|
||||
fat_per_100g: fat, carbs_per_100g: carbs, source: "manual",
|
||||
});
|
||||
await DB.addFoodEntry(state.date, time, pid, amount, null);
|
||||
} else {
|
||||
await DB.addFoodManual(state.date, time, desc, kcal);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1242,6 +1271,18 @@ async function renderProducts(container) {
|
|||
|
||||
function renderProdDetails(p) {
|
||||
const r = v => v != null ? v : "—";
|
||||
// Micronutrient chips for this product (per 100g)
|
||||
const RDI = BMR.RDI;
|
||||
const microChips = [];
|
||||
for (const [key, rdi] of Object.entries(RDI)) {
|
||||
if (!p[key]) continue;
|
||||
const pct = Math.round(p[key] / rdi * 100);
|
||||
if (pct < 1) continue;
|
||||
const label = NUTRIENT_LABELS[key] || key;
|
||||
const cls = pct < 30 ? "nut-low" : pct < 80 ? "nut-mid" : "nut-ok";
|
||||
microChips.push({ pct, html: `<div class="nut-item ${cls}">${label} ${pct}%</div>` });
|
||||
}
|
||||
microChips.sort((a, b) => a.pct - b.pct);
|
||||
return `
|
||||
<div class="prod-details">
|
||||
<div class="prod-macros">
|
||||
|
|
@ -1250,6 +1291,7 @@ async function renderProducts(container) {
|
|||
<div class="macro"><span class="macro-val">${r(p.carbs_per_100g)}</span><span class="macro-lbl">У</span></div>
|
||||
<div class="macro"><span class="macro-val">${r(p.fiber_per_100g)}</span><span class="macro-lbl">Кл</span></div>
|
||||
</div>
|
||||
${microChips.length ? `<div class="nutrient-strip" style="padding:4px 0">${microChips.map(c => c.html).join("")}</div>` : ""}
|
||||
${p.composition ? `<div class="prod-composition">${esc(p.composition)}</div>` : ""}
|
||||
<button class="btn-primary prod-add-btn" data-pid="${p.id}">Добавить в дневник</button>
|
||||
</div>`;
|
||||
|
|
@ -3245,25 +3287,40 @@ async function showLogSupplementModal() {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
const MICRO_FIELDS = [
|
||||
{ key: "vit_a_mcg", label: "Вит A (мкг)" },
|
||||
{ key: "vit_c_mg", label: "Вит C (мг)" },
|
||||
{ key: "vit_d_mcg", label: "Вит D (мкг)" },
|
||||
{ key: "vit_e_mg", label: "Вит E (мг)" },
|
||||
{ key: "vit_k_mcg", label: "Вит K (мкг)" },
|
||||
{ key: "vit_b1_mg", label: "B1 (мг)" },
|
||||
{ key: "vit_b2_mg", label: "B2 (мг)" },
|
||||
{ key: "vit_b3_mg", label: "B3 (мг)" },
|
||||
{ key: "vit_b6_mg", label: "B6 (мг)" },
|
||||
{ key: "vit_b12_mcg", label: "B12 (мкг)" },
|
||||
{ key: "folate_mcg", label: "Фолат (мкг)" },
|
||||
{ key: "calcium_mg", label: "Ca (мг)" },
|
||||
{ key: "iron_mg", label: "Fe (мг)" },
|
||||
{ key: "magnesium_mg", label: "Mg (мг)" },
|
||||
{ key: "zinc_mg", label: "Zn (мг)" },
|
||||
{ key: "copper_mg", label: "Cu (мг)" },
|
||||
{ key: "selenium_mcg", label: "Se (мкг)" },
|
||||
{ key: "potassium_mg", label: "K (мг)" },
|
||||
{ key: "sodium_mg", label: "Na (мг)" },
|
||||
{ key: "vit_a_mcg", label: "Вит A (мкг РЭ)" },
|
||||
{ key: "vit_c_mg", label: "Вит C (мг)" },
|
||||
{ key: "vit_d_mcg", label: "Вит D3 (мкг)" },
|
||||
{ key: "vit_e_mg", label: "Вит E (мг ТЭ)" },
|
||||
{ key: "vit_k_mcg", label: "Вит K (мкг)" },
|
||||
{ key: "vit_b1_mg", label: "B1 (мг)" },
|
||||
{ key: "vit_b2_mg", label: "B2 (мг)" },
|
||||
{ key: "vit_b3_mg", label: "B3/PP (мг)" },
|
||||
{ key: "vit_b5_mg", label: "B5 (мг)" },
|
||||
{ key: "vit_b6_mg", label: "B6 (мг)" },
|
||||
{ key: "vit_b7_mcg", label: "B7/биотин (мкг)" },
|
||||
{ key: "folate_mcg", label: "B9/фолат (мкг)" },
|
||||
{ key: "vit_b12_mcg", label: "B12 (мкг)" },
|
||||
{ key: "calcium_mg", label: "Ca (мг)" },
|
||||
{ key: "magnesium_mg", label: "Mg (мг)" },
|
||||
{ key: "iron_mg", label: "Fe (мг)" },
|
||||
{ key: "zinc_mg", label: "Zn (мг)" },
|
||||
{ key: "copper_mg", label: "Cu (мг)" },
|
||||
{ key: "selenium_mcg", label: "Se (мкг)" },
|
||||
{ key: "potassium_mg", label: "K (мг)" },
|
||||
{ key: "sodium_mg", label: "Na (мг)" },
|
||||
{ key: "phosphorus_mg", label: "P (мг)" },
|
||||
{ key: "iodine_mg", label: "Йод (мг)" },
|
||||
{ key: "chromium_mg", label: "Хром (мг)" },
|
||||
{ key: "manganese_mg", label: "Mn (мг)" },
|
||||
{ key: "molybdenum_mcg", label: "Mo (мкг)" },
|
||||
{ key: "carotene_mcg", label: "b-каротин (мкг)" },
|
||||
{ key: "omega3_mg", label: "Омега-3 (мг)" },
|
||||
{ key: "omega6_mg", label: "Омега-6 (мг)" },
|
||||
{ key: "omega9_mg", label: "Омега-9 (мг)" },
|
||||
{ key: "silicon_mg", label: "Кремний (мг)" },
|
||||
{ key: "vanadium_mg", label: "Ванадий (мг)" },
|
||||
{ key: "lutein_mg", label: "Лютеин (мг)" },
|
||||
{ key: "lycopene_mg", label: "Ликопин (мг)" },
|
||||
];
|
||||
|
||||
async function renderSupplements(container) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue