(function (global) {
"use strict";
let diffByComponent = new Map();
let hooked = false;
function getActiveRecipeId() {
const fromEdit = document.getElementById("recipeEdit")?.dataset?.recipeId;
if (fromEdit) return fromEdit;
return document.querySelector(".list-item.active[data-recipe-id]")?.dataset?.recipeId || null;
}
function ensureToolbarButtons() {
const toolbar = document.querySelector("#recipeEdit .recipe-section-toolbar");
if (!toolbar) return;
if (!toolbar.querySelector('[data-action="sync-to-master"]')) {
const syncBtn = document.createElement("button");
syncBtn.type = "button";
syncBtn.className = "btn btn-outline-secondary";
syncBtn.setAttribute("data-action", "sync-to-master");
syncBtn.innerHTML = 'В мастер';
toolbar.insertBefore(syncBtn, toolbar.firstChild);
}
if (!toolbar.querySelector('[data-action="apply-from-master"]')) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "btn btn-outline-primary";
btn.setAttribute("data-action", "apply-from-master");
btn.hidden = true;
btn.innerHTML = 'Из мастера';
const addBtn = toolbar.querySelector('[data-action="add-ingredient"]');
if (addBtn) toolbar.insertBefore(btn, addBtn);
else toolbar.appendChild(btn);
}
}
function ensureKHubButton() {
const actions = document.querySelector("#recipeEdit .recipe-edit-header-actions .d-flex");
if (!actions || actions.querySelector('[data-action="open-lab-k-hub"]')) return;
const btn = document.createElement("button");
btn.type = "button";
btn.className = "btn btn-outline-secondary recipe-edit-header-icon-btn";
btn.setAttribute("data-action", "open-lab-k-hub");
btn.title = "Открыть зоотех-мастер в K-hub";
btn.setAttribute("aria-label", "K-hub");
btn.innerHTML = '';
actions.insertBefore(btn, actions.firstChild);
}
function ensureRationTypeField() {
const grid = document.querySelector("#recipeEdit .recipe-form-grid");
if (!grid || document.getElementById("rationType")) return;
const wrap = document.createElement("div");
wrap.className = "form-group";
wrap.innerHTML =
'' +
'";
const tripWrap = document.getElementById("tripPercent")?.closest(".form-group");
if (tripWrap?.parentElement === grid) {
tripWrap.insertAdjacentElement("afterend", wrap);
} else {
grid.appendChild(wrap);
}
}
function ensureDiffStyles() {
if (document.getElementById("wesp-lab-diff-styles")) return;
const style = document.createElement("style");
style.id = "wesp-lab-diff-styles";
style.textContent =
".lab-diff-badge{margin-left:6px;font-size:.7rem;vertical-align:middle;}" +
".lab-diff-badge--warn{background:#fff3cd;color:#664d03;}" +
".lab-diff-badge--miss{background:#f8d7da;color:#842029;}" +
".lab-recipe-quality{margin-top:4px;font-size:.78rem;color:var(--bs-secondary-color,#6c757d);}" +
".lab-recipe-quality__label{font-weight:600;margin-right:4px;}";
document.head.appendChild(style);
}
function diffBadgeHtml(reasons) {
if (!reasons?.length) return "";
const title = reasons.join(", ");
const cls = reasons.includes("missing_in_master") || reasons.includes("missing_in_execution")
? "lab-diff-badge--miss"
: "lab-diff-badge--warn";
return `мастер`;
}
function applyDiffBadges() {
document.querySelectorAll("#ingredientsTableBody .ingredient-row").forEach((row) => {
const select = row.querySelector(".ingredient-select");
const cell = row.querySelector('.ingredient-detail-cell[data-label="Ингредиент"] .recipe-row-name-with-skip');
if (!select || !cell) return;
cell.querySelectorAll(".lab-diff-badge").forEach((el) => el.remove());
const compId = select.value;
if (!compId) return;
const reasons = diffByComponent.get(String(compId));
if (!reasons?.length) return;
cell.insertAdjacentHTML("beforeend", diffBadgeHtml(reasons));
});
}
function ensureQualityBlock() {
const headerText = document.querySelector("#recipeEdit .recipe-edit-header-text");
if (!headerText || document.getElementById("labRecipeQuality")) return;
const block = document.createElement("div");
block.id = "labRecipeQuality";
block.className = "lab-recipe-quality";
block.hidden = true;
block.innerHTML =
'Зоотех:' +
'';
headerText.appendChild(block);
}
async function refreshQuality(recipeId) {
ensureQualityBlock();
const block = document.getElementById("labRecipeQuality");
const items = block?.querySelector("[data-lab-quality-items]");
if (!block || !items || !recipeId) return;
try {
const resp = await fetch(`/api/lab/rations/${encodeURIComponent(recipeId)}`);
if (!resp.ok) throw new Error("no ration");
const data = await resp.json();
const indicators = (data.rationResults?.indicators || []).slice(0, 4);
if (!data.exists || !indicators.length) {
block.hidden = true;
return;
}
items.textContent = indicators
.map((ind) => `${ind.label}: ${ind.content ?? "—"} ${ind.unit || ""}`.trim())
.join(" · ");
block.hidden = false;
} catch (_) {
block.hidden = true;
}
}
async function refreshDiff(recipeId) {
const btn = document.querySelector('[data-action="apply-from-master"]');
if (!recipeId) return;
await refreshQuality(recipeId);
if (!btn) return;
try {
const resp = await fetch(`/api/lab/rations/${encodeURIComponent(recipeId)}/diff`);
const data = await resp.json();
diffByComponent = new Map();
(data.lines || []).forEach((line) => {
if (line.componentId) diffByComponent.set(String(line.componentId), line.reasons || []);
});
btn.hidden = !data.hasChanges;
applyDiffBadges();
} catch (_) {
btn.hidden = true;
diffByComponent = new Map();
}
}
function patchRecipeLoad() {
if (hooked) return;
const orig = global.loadRecipeDetails;
if (typeof orig !== "function") return;
global.loadRecipeDetails = async function (recipeId, options) {
const recipeEdit = document.getElementById("recipeEdit");
if (recipeEdit && recipeId) recipeEdit.dataset.recipeId = String(recipeId);
await orig(recipeId, options);
await refreshDiff(recipeId);
global.WespLabRecipesPlanOverlay?.decorateRows?.();
};
hooked = true;
}
function patchRecipeSave() {
const orig = global.doSaveRecipe;
if (typeof orig !== "function" || orig.__labPatched) return;
global.doSaveRecipe = async function (closeAfterSave) {
const rationEl = document.getElementById("rationType");
const prev = global.__wespLabRationTypeForSave;
if (rationEl) {
global.__wespLabRationTypeForSave = rationEl.value || null;
}
try {
return await orig(closeAfterSave);
} finally {
global.__wespLabRationTypeForSave = prev;
}
};
const origFetch = global.fetch.bind(global);
global.fetch = async function (input, init) {
const url = typeof input === "string" ? input : input?.url;
const method = (init?.method || "GET").toUpperCase();
if (
method === "PUT" &&
url &&
/\/api\/recipes\/[^/]+$/.test(url) &&
init?.body &&
global.__wespLabRationTypeForSave !== undefined
) {
try {
const body = JSON.parse(init.body);
body.ration_type = global.__wespLabRationTypeForSave || null;
init = { ...init, body: JSON.stringify(body) };
} catch (_) {
/* ignore */
}
}
return origFetch(input, init);
};
global.doSaveRecipe.__labPatched = true;
}
document.addEventListener("click", async (ev) => {
const btn = ev.target.closest("[data-action]");
if (!btn) return;
const action = btn.getAttribute("data-action");
const recipeId = getActiveRecipeId();
if (action === "open-lab-k-hub") {
global.WespZootechNotificationCenter?.openDailyPlan?.(recipeId || undefined);
return;
}
if (action === "sync-to-master") {
if (!recipeId) return;
const ok = await global.WespDialog?.confirm?.(
"Сохранить текущий рецепт в зоотех-мастер?",
{ title: "В мастер" }
);
if (!ok) return;
try {
const resp = await fetch(
`/api/lab/rations/${encodeURIComponent(recipeId)}/sync-from-execution`,
{ method: "POST" }
);
const data = await resp.json();
if (!resp.ok) throw new Error(data.message || "Ошибка");
global.WespZootechNotify?.createAdapter?.()?.success?.("Мастер обновлён из рецепта");
await refreshDiff(recipeId);
} catch (e) {
global.WespZootechNotify?.createAdapter?.()?.error?.(e.message, { fallback: "Не удалось выполнить операцию" });
}
return;
}
if (action !== "apply-from-master") return;
if (!recipeId) return;
const ok = await global.WespDialog?.confirm?.("Перенести ингредиенты из зоотех-мастера?", {
title: "Из мастера",
});
if (!ok) return;
try {
const resp = await fetch(`/api/lab/rations/${encodeURIComponent(recipeId)}/apply-from-master`, {
method: "POST",
});
const data = await resp.json();
if (!resp.ok) throw new Error(data.message || "Ошибка");
if (typeof global.reloadRecipeEditor === "function") {
await global.reloadRecipeEditor(recipeId);
} else if (typeof global.loadRecipeDetails === "function") {
await global.loadRecipeDetails(recipeId);
}
await refreshDiff(recipeId);
global.WespZootechNotify?.createAdapter?.()?.success?.("Мастер применён к рецепту");
} catch (e) {
global.WespZootechNotify?.createAdapter?.()?.error?.(e.message, { fallback: "Не удалось применить мастер" });
}
});
document.addEventListener("change", (ev) => {
if (!ev.target?.matches?.(".ingredient-select")) return;
applyDiffBadges();
});
global.WespLabRecipesOverlay = {
init() {
ensureDiffStyles();
ensureToolbarButtons();
ensureKHubButton();
ensureRationTypeField();
patchRecipeLoad();
patchRecipeSave();
},
refreshDiff,
refreshQuality,
applyDiffBadges,
};
function boot() {
global.WespLabRecipesOverlay.init();
if (typeof global.loadRecipeDetails === "function") patchRecipeLoad();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", boot);
} else {
boot();
}
})(window);