NonInventPurchasingSystem/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryTransactModal.cshtml

907 lines
39 KiB
Plaintext

<style>
.tm-type-opt {
border: 1.5px solid var(--border, #d6eaec);
border-radius: 12px;
padding: 12px;
cursor: pointer;
transition: all .15s;
display: flex;
flex-direction: column;
gap: 6px;
}
.tm-type-opt:hover {
border-color: var(--teal-mid, #0e7c86);
background: var(--teal-pale, #e6f7f8);
}
.tm-type-opt.selected {
border-color: var(--teal-mid, #0e7c86);
background: var(--teal-pale, #e6f7f8);
}
.tm-type-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
}
.tm-icon-ris {
background: #e6f7f8;
color: #0e7c86;
}
.tm-icon-mrs {
background: #E6F1FB;
color: #185FA5;
}
.tm-type-opt.selected .tm-icon-ris {
background: #0e7c86;
color: #fff;
}
.tm-type-opt.selected .tm-icon-mrs {
background: #185FA5;
color: #fff;
}
.tm-stock-badge {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 12px;
background: var(--teal-pale, #e6f7f8);
border: 1px solid var(--border, #d6eaec);
border-radius: 8px;
font-size: 13px;
color: var(--text-dark, #1a2e35);
}
.tm-stock-badge i {
color: var(--teal-mid, #0e7c86);
}
.tm-stock-badge strong {
margin-left: auto;
color: var(--teal-dark, #0d5c63);
}
.tm-form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.tm-label {
font-size: 11px;
font-weight: 600;
color: var(--text-muted, #6b8890);
text-transform: uppercase;
letter-spacing: .05em;
display: flex;
align-items: center;
gap: 5px;
}
.tm-label i {
font-size: 11px;
}
.tm-req {
color: #e53e3e;
}
.tm-input {
padding: 8px 10px;
border: 1.5px solid var(--border, #d6eaec);
border-radius: 8px;
background: #fff;
color: var(--text-dark, #1a2e35);
font-family: 'DM Sans', sans-serif;
font-size: 13px;
width: 100%;
outline: none;
transition: border-color .2s;
}
.tm-input:focus {
border-color: var(--teal-mid, #0e7c86);
}
.tm-input.error {
border-color: #e53e3e;
}
.tm-warn {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: #c53030;
}
.tm-hint {
font-size: 11px;
color: var(--text-muted, #6b8890);
}
.tm-info-box {
display: flex;
gap: 8px;
align-items: flex-start;
padding: 10px 12px;
background: #E6F1FB;
border: 1px solid #B5D4F4;
border-radius: 8px;
font-size: 12px;
color: #0C447C;
line-height: 1.5;
}
.tm-btn-cancel {
padding: 8px 18px;
border-radius: 8px;
border: 1.5px solid var(--border, #d6eaec);
background: transparent;
color: var(--text-muted, #6b8890);
font-family: 'DM Sans', sans-serif;
font-size: .85rem;
font-weight: 600;
cursor: pointer;
}
.tm-btn-submit {
padding: 8px 20px;
border-radius: 8px;
border: none;
background: var(--teal-mid, #0e7c86);
color: #fff;
font-family: 'DM Sans', sans-serif;
font-size: .85rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 7px;
transition: background .18s;
}
.tm-btn-submit:hover:not(:disabled) {
background: var(--teal-dark, #0d5c63);
}
.tm-btn-submit:disabled {
opacity: .5;
cursor: default;
}
.tm-search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1090;
margin-top: 4px;
max-height: 220px;
overflow-y: auto;
background: #fff;
border: 1.5px solid var(--border, #d6eaec);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,.12);
display: none;
}
.tm-search-dropdown.open {
display: block;
}
.tm-search-item {
padding: 8px 10px;
font-size: 13px;
color: var(--text-dark, #1a2e35);
cursor: pointer;
}
.tm-search-item:hover,
.tm-search-item.active {
background: var(--teal-pale, #e6f7f8);
color: var(--teal-dark, #0d5c63);
}
.tm-search-empty {
padding: 10px;
font-size: 12px;
color: var(--text-muted, #6b8890);
text-align: center;
}
</style>
<script>
window.TransactModal = (function () {
"use strict";
const H = window.InventoryHelpers;
// ── State ────────────────────────────────────────────────────────────────
let _ctx = null; // TransactContextDto from server
let _activeType = "ris"; // "ris" | "mrs"
let _onSuccess = null; // callback after successful submit
// ── DOM refs (resolved once modal is injected) ────────────────────────────
let modal, overlay, form, btnSubmit, submitLabel;
let optRIS, optMRS, formRIS, formMRS;
// ── RIS fields ────────────────────────────────────────────────────────────
let risItemBadge, risOnHand, risDiscipline, risQty,
risPRRef, risRemarks, risQtyWarn;
// ── MRS fields ────────────────────────────────────────────────────────────
let mrsRISSelect, mrsQty, mrsCondition, mrsRemarks,
mrsQtyWarn, mrsMaxHint;
// ════════════════════════════════════════════════════════════════════════
// PUBLIC API
// ════════════════════════════════════════════════════════════════════════
async function open(inventoryId, onSuccessCallback) {
_onSuccess = onSuccessCallback || null;
_injectModalHtml();
_bindRefs();
_showLoading(true);
_showOverlay(true);
try {
const res = await fetch(
`/InventoryMgmt/GetTransactContext?inventoryId=${inventoryId}`
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
_ctx = json.data ?? json;
} catch (err) {
showToast("error", "Could not load item context.", "Error", 4000);
_showOverlay(false);
return;
}
_populateContext();
_selectType("ris");
_showLoading(false);
}
// ════════════════════════════════════════════════════════════════════════
// MODAL HTML INJECTION
// ════════════════════════════════════════════════════════════════════════
function _injectModalHtml() {
const existing = document.getElementById("transact-modal-overlay");
if (existing) existing.remove();
document.body.insertAdjacentHTML("beforeend", `
<div id="transact-modal-overlay" style="
position:fixed;inset:0;z-index:1080;
background:rgba(0,0,0,.45);
display:flex;align-items:center;justify-content:center;
padding:16px">
<div id="transact-modal" style="
background:var(--card-bg,#fff);
border-radius:14px;
border:1px solid var(--border,#d6eaec);
width:100%;max-width:520px;
overflow:hidden;
box-shadow:0 20px 60px rgba(0,0,0,.2)">
<!-- HEAD -->
<div style="background:linear-gradient(135deg,#0d5c63,#0e7c86);
padding:16px 18px;display:flex;align-items:center;
justify-content:space-between">
<div style="display:flex;align-items:center;gap:12px">
<div style="width:38px;height:38px;border-radius:10px;
background:rgba(255,255,255,.15);
display:flex;align-items:center;justify-content:center">
<i class="fa-solid fa-arrow-right-arrow-left" style="color:#fff;font-size:16px"></i>
</div>
<div>
<div style="font-size:14px;font-weight:600;color:#fff">
New Transaction
</div>
<div id="tm-subtitle" style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
Loading…
</div>
</div>
</div>
<button id="tm-close" style="
width:30px;height:30px;border-radius:8px;
background:rgba(255,255,255,.12);
border:1px solid rgba(255,255,255,.2);
color:rgba(255,255,255,.85);cursor:pointer;
display:flex;align-items:center;justify-content:center">
<i class="fas fa-times" style="font-size:13px"></i>
</button>
</div>
<!-- LOADING STATE -->
<div id="tm-loading" style="
display:flex;align-items:center;justify-content:center;
gap:12px;padding:60px 20px;color:var(--text-muted,#6b8890)">
<div class="inv-spinner"></div>
<span style="font-size:.9rem">Loading…</span>
</div>
<!-- BODY (hidden until loaded) -->
<div id="tm-body" style="display:none">
<!-- TYPE PICKER -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;
padding:14px 16px;border-bottom:1px solid var(--border,#d6eaec)">
<div id="tm-opt-ris" class="tm-type-opt" data-type="ris">
<div class="tm-type-icon tm-icon-ris">
<i class="fas fa-file-export"></i>
</div>
<div style="font-size:13px;font-weight:600;color:var(--text-dark,#1a2e35)">
Return Issuance Slip
</div>
<div style="font-size:11px;color:var(--text-muted,#6b8890);line-height:1.4">
Issue items out of inventory
</div>
</div>
<div id="tm-opt-mrs" class="tm-type-opt" data-type="mrs">
<div class="tm-type-icon tm-icon-mrs">
<i class="fas fa-file-import"></i>
</div>
<div style="font-size:13px;font-weight:600;color:var(--text-dark,#1a2e35)">
Material Return Slip
</div>
<div style="font-size:11px;color:var(--text-muted,#6b8890);line-height:1.4">
Return unused items to stock
</div>
</div>
</div>
<!-- RIS FORM -->
<div id="tm-form-ris" style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
<div id="tm-ris-stock-badge" class="tm-stock-badge"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-tools"></i> TRADE <span class="tm-req">*</span>
</label>
<select id="tm-ris-discipline" class="tm-input">
<option value="">Select trade…</option>
</select>
</div>
<div class="tm-form-group" style="position:relative">
<label class="tm-label">
<i class="fas fa-diagram-project"></i> Project Name <span class="tm-req">*</span>
</label>
<input id="tm-ris-project-code-name-search" class="tm-input"
type="text" placeholder="Search project name…" autocomplete="off">
<input type="hidden" id="tm-ris-project-code-name">
<div id="tm-ris-project-code-name-list" class="tm-search-dropdown"></div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-cubes"></i> Qty to issue <span class="tm-req">*</span>
</label>
<input id="tm-ris-qty" class="tm-input"
type="number" min="1" placeholder="0">
<span id="tm-ris-qty-warn" class="tm-warn" style="display:none">
<i class="fas fa-exclamation-triangle"></i>
<span id="tm-ris-qty-warn-text"></span>
</span>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-file-alt"></i> PR reference
</label>
<input id="tm-ris-prref" class="tm-input"
type="text" readOnly placeholder="PR-2026-XXXX">
</div>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-sticky-note"></i> Remarks
</label>
<input id="tm-ris-remarks" class="tm-input"
type="text" placeholder="Optional notes…">
</div>
</div>
<!-- MRS FORM -->
<div id="tm-form-mrs" style="padding:14px 16px;display:none;flex-direction:column;gap:12px">
<div class="tm-info-box">
<i class="fas fa-info-circle" style="color:#185FA5;flex-shrink:0;margin-top:2px"></i>
<span>Select the original RIS for this return.
Only the qty issued on that slip can be returned.</span>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-receipt"></i> Original RIS reference <span class="tm-req">*</span>
</label>
<select id="tm-mrs-ris" class="tm-input">
<option value="">Select RIS…</option>
</select>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-cubes"></i> Qty to return <span class="tm-req">*</span>
</label>
<input id="tm-mrs-qty" class="tm-input"
type="number" min="1" placeholder="0">
<span id="tm-mrs-max-hint" class="tm-hint" style="display:none"></span>
<span id="tm-mrs-qty-warn" class="tm-warn" style="display:none">
<i class="fas fa-exclamation-triangle"></i>
<span id="tm-mrs-qty-warn-text"></span>
</span>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-tag"></i> Condition
</label>
<select id="tm-mrs-condition" class="tm-input">
<option value="Good">Good</option>
<option value="Damaged">Damaged</option>
<option value="Partial">Partial</option>
</select>
</div>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-sticky-note"></i> Remarks
</label>
<input id="tm-mrs-remarks" class="tm-input"
type="text" placeholder="Reason for return…">
</div>
</div>
</div><!-- /tm-body -->
<!-- FOOTER -->
<div style="
padding:12px 16px;
border-top:1px solid var(--border,#d6eaec);
background:var(--bg-page,#f0f6f7);
display:flex;align-items:center;justify-content:flex-end;gap:8px">
<button id="tm-cancel" class="tm-btn-cancel">Cancel</button>
<button id="tm-submit" class="tm-btn-submit" disabled>
<i class="fas fa-check"></i>
<span id="tm-submit-label">Create RIS</span>
</button>
</div>
</div>
</div>`);
}
// ════════════════════════════════════════════════════════════════════════
// BIND DOM REFS + EVENTS
// ════════════════════════════════════════════════════════════════════════
function _bindRefs() {
overlay = document.getElementById("transact-modal-overlay");
modal = document.getElementById("transact-modal");
btnSubmit = document.getElementById("tm-submit");
submitLabel = document.getElementById("tm-submit-label");
optRIS = document.getElementById("tm-opt-ris");
optMRS = document.getElementById("tm-opt-mrs");
formRIS = document.getElementById("tm-form-ris");
formMRS = document.getElementById("tm-form-mrs");
// RIS fields
risItemBadge = document.getElementById("tm-ris-stock-badge");
risDiscipline = document.getElementById("tm-ris-discipline");
risProjectCode = document.getElementById("tm-ris-project-code-name");
risProjectCodeSearch = document.getElementById("tm-ris-project-code-name-search");
risProjectCodeList = document.getElementById("tm-ris-project-code-name-list");
_bindProjectCodeSearch();
risQty = document.getElementById("tm-ris-qty");
risPRRef = document.getElementById("tm-ris-prref");
risRemarks = document.getElementById("tm-ris-remarks");
risQtyWarn = document.getElementById("tm-ris-qty-warn");
// MRS fields
mrsRISSelect = document.getElementById("tm-mrs-ris");
mrsQty = document.getElementById("tm-mrs-qty");
mrsCondition = document.getElementById("tm-mrs-condition");
mrsRemarks = document.getElementById("tm-mrs-remarks");
mrsQtyWarn = document.getElementById("tm-mrs-qty-warn");
mrsMaxHint = document.getElementById("tm-mrs-max-hint");
// Type picker
[optRIS, optMRS].forEach(el =>
el.addEventListener("click", () => _selectType(el.dataset.type))
);
// Close / Cancel
document.getElementById("tm-close").addEventListener("click", _close);
document.getElementById("tm-cancel").addEventListener("click", _close);
overlay.addEventListener("click", e => { if (e.target === overlay) _close(); });
// Qty live validation
risQty.addEventListener("input", _validateRISQty);
mrsRISSelect.addEventListener("change", _onMRSRISChange);
mrsQty.addEventListener("input", _validateMRSQty);
// Submit
btnSubmit.addEventListener("click", _handleSubmit);
}
// ════════════════════════════════════════════════════════════════════════
// POPULATE FROM CONTEXT
// ════════════════════════════════════════════════════════════════════════
function _populateContext() {
// Header subtitle
document.getElementById("tm-subtitle").textContent =
`${H.escHtml(_ctx.itemName)} · Item #${_ctx.itemNo}`;
document.getElementById("tm-ris-prref").value = _ctx.prNo ?? "";
// Stock badge
risItemBadge.innerHTML = `
<i class="fas fa-layer-group"></i>
<span>On hand</span>
<strong>${_ctx.qtyOnHand} pcs</strong>
<span style="margin-left:8px;color:var(--text-muted,#6b8890)">
In: ${_ctx.qtyIn} · Out: ${_ctx.qtyOut}
</span>`;
// Discipline dropdown
risDiscipline.innerHTML = `
<option value="">Select trade…</option>` +
(_ctx.disciplines || []).map(d =>
`
<option value="${d.disciplineId}">${H.escHtml(d.disciplineName)}</option>`
).join("");
// MRS — open RIS list
const hasRIS = _ctx.openRISList && _ctx.openRISList.length > 0;
mrsRISSelect.innerHTML = `
<option value="">Select RIS…</option>` +
(hasRIS
? _ctx.openRISList.map(r =>
`
<option value="${r.risId}"
data-max="${r.qtyAvailableToReturn}"
data-discipline="${H.escAttr(r.disciplineName)}">
${H.escHtml(r.risNo)} — ${r.qtyAvailableToReturn} pcs avail — ${H.escHtml(r.disciplineName)}
</option>`)
.join("")
: "");
//Project name
_setProjectCodeOptions(_ctx.projectCodes);
risProjectCodeSearch.value = "";
risProjectCode.value = "";
if (!hasRIS) {
optMRS.style.opacity = ".45";
optMRS.style.cursor = "not-allowed";
optMRS.title = "No approved RIS records with remaining qty for this item.";
optMRS.onclick = null;
}
}
// ════════════════════════════════════════════════════════════════════════
// TYPE SWITCHING
// ════════════════════════════════════════════════════════════════════════
function _selectType(type) {
_activeType = type;
const isRIS = type === "ris";
optRIS.classList.toggle("selected", isRIS);
optMRS.classList.toggle("selected", !isRIS);
formRIS.style.display = isRIS ? "flex" : "none";
formMRS.style.display = !isRIS ? "flex" : "none";
submitLabel.textContent = isRIS ? "Create RIS" : "Create MRS";
btnSubmit.disabled = false;
_clearErrors();
}
// ════════════════════════════════════════════════════════════════════════
// VALIDATION
// ════════════════════════════════════════════════════════════════════════
function _validateRISQty() {
const val = parseInt(risQty.value, 10);
const max = _ctx?.qtyOnHand ?? 0;
const over = !isNaN(val) && val > max;
const zero = !isNaN(val) && val < 1;
document.getElementById("tm-ris-qty-warn-text").textContent =
over ? `Cannot exceed ${max} on hand.`
: zero ? "Must be at least 1." : "";
risQtyWarn.style.display = (over || zero) ? "flex" : "none";
risQty.classList.toggle("error", over || zero);
}
function _onMRSRISChange() {
const opt = mrsRISSelect.selectedOptions[0];
const max = opt ? parseInt(opt.dataset.max, 10) : 0;
if (opt && opt.value) {
mrsMaxHint.style.display = "block";
mrsMaxHint.textContent = `Max returnable: ${max} pcs`;
mrsQty.max = max;
} else {
mrsMaxHint.style.display = "none";
mrsQty.max = "";
}
_validateMRSQty();
}
function _validateMRSQty() {
const val = parseInt(mrsQty.value, 10);
const opt = mrsRISSelect.selectedOptions[0];
const max = opt ? parseInt(opt.dataset.max, 10) : Infinity;
const over = !isNaN(val) && val > max;
const zero = !isNaN(val) && val < 1;
document.getElementById("tm-mrs-qty-warn-text").textContent =
over ? `Cannot exceed ${max} available.`
: zero ? "Must be at least 1." : "";
mrsQtyWarn.style.display = (over || zero) ? "flex" : "none";
mrsQty.classList.toggle("error", over || zero);
}
function _clearErrors() {
[risQty, risDiscipline, risProjectCode, mrsRISSelect, mrsQty].forEach(el => {
if (el) el.classList.remove("error");
});
risProjectCodeSearch?.classList.remove("error");
[risQtyWarn, mrsQtyWarn].forEach(el => {
if (el) el.style.display = "none";
});
}
function _validateForm() {
let valid = true;
if (_activeType === "ris") {
if (!risDiscipline.value) { risDiscipline.classList.add("error"); valid = false; }
const qty = parseInt(risQty.value, 10);
if (isNaN(qty) || qty < 1 || qty > _ctx.qtyOnHand) {
risQty.classList.add("error"); valid = false;
}
if (!risProjectCode.value) {
risProjectCode.classList.add("error");
risProjectCodeSearch.classList.add("error");
valid = false;
}
} else {
if (!mrsRISSelect.value) { mrsRISSelect.classList.add("error"); valid = false; }
const qty = parseInt(mrsQty.value, 10);
const opt = mrsRISSelect.selectedOptions[0];
const maxRet = opt ? parseInt(opt.dataset.max, 10) : 0;
if (isNaN(qty) || qty < 1 || qty > maxRet) {
mrsQty.classList.add("error"); valid = false;
}
}
return valid;
}
// ── Searchable dropdown refs ────────────────────────────────────────────────
let risProjectCode, risProjectCodeSearch, risProjectCodeList;
let _projectCodeOptions = [];
let _projectCodeActiveIndex = -1;
function _bindIssuedToSearch() {
risIssuedToSearch = document.getElementById("tm-ris-issuedto-search");
risIssuedToList = document.getElementById("tm-ris-issuedto-list");
risIssuedToSearch.addEventListener("input", _onIssuedToInput);
risIssuedToSearch.addEventListener("focus", () => _renderIssuedToList(risIssuedToSearch.value));
risIssuedToSearch.addEventListener("keydown", _onIssuedToKeydown);
document.addEventListener("click", (e) => {
if (!risIssuedToSearch.contains(e.target) && !risIssuedToList.contains(e.target)) {
_closeIssuedToList();
}
});
}
function _bindProjectCodeSearch() {
risProjectCodeSearch.addEventListener("input", _onProjectCodeInput);
risProjectCodeSearch.addEventListener("focus", () => _renderProjectCodeList(risProjectCodeSearch.value));
risProjectCodeSearch.addEventListener("keydown", _onProjectCodeKeydown);
document.addEventListener("click", (e) => {
if (!risProjectCodeSearch.contains(e.target) && !risProjectCodeList.contains(e.target)) {
_closeProjectCodeList();
}
});
}
function _setProjectCodeOptions(projectCodes) {
_projectCodeOptions = (projectCodes || []).map(p => ({
value: p.projectCodeId,
code: p.projectCode,
name: p.projectName,
label: `${p.projectCode} — ${p.projectName}`
}));
}
function _onProjectCodeInput() {
risProjectCode.value = ""; // invalidate committed selection until re-picked
_renderProjectCodeList(risProjectCodeSearch.value);
}
function _renderProjectCodeList(query) {
const q = (query || "").trim().toLowerCase();
const filtered = q
? _projectCodeOptions.filter(o =>
o.name.toLowerCase().includes(q) || o.code.toLowerCase().includes(q))
: _projectCodeOptions;
_projectCodeActiveIndex = -1;
risProjectCodeList.innerHTML = filtered.length
? filtered.map((o, i) =>
`<div class="tm-search-item" data-index="${i}" data-value="${o.value}">
<div>${H.escHtml(o.name)}</div>
<div style="font-size:11px;color:var(--text-muted,#6b8890)">${H.escHtml(o.code)}</div>
</div>`).join("")
: `<div class="tm-search-empty">No matching projects</div>`;
risProjectCodeList.querySelectorAll(".tm-search-item").forEach(item => {
item.addEventListener("click", () => {
const opt = _projectCodeOptions[parseInt(item.dataset.index, 10)];
_selectProjectCode(opt);
});
});
risProjectCodeList.classList.add("open");
}
function _selectProjectCode(opt) {
risProjectCode.value = opt.value; // ProjectCodeId — this is what gets posted
risProjectCodeSearch.value = opt.label; // visible text
_closeProjectCodeList();
risProjectCode.classList.remove("error");
risProjectCodeSearch.classList.remove("error");
}
function _closeProjectCodeList() {
risProjectCodeList.classList.remove("open");
_projectCodeActiveIndex = -1;
}
function _onProjectCodeKeydown(e) {
const items = risProjectCodeList.querySelectorAll(".tm-search-item");
if (!items.length) return;
if (e.key === "ArrowDown") {
e.preventDefault();
_projectCodeActiveIndex = Math.min(_projectCodeActiveIndex + 1, items.length - 1);
_highlightProjectCode(items);
} else if (e.key === "ArrowUp") {
e.preventDefault();
_projectCodeActiveIndex = Math.max(_projectCodeActiveIndex - 1, 0);
_highlightProjectCode(items);
} else if (e.key === "Enter") {
e.preventDefault();
const idx = _projectCodeActiveIndex >= 0 ? _projectCodeActiveIndex : 0;
const item = items[idx];
if (item) _selectProjectCode(_projectCodeOptions[parseInt(item.dataset.index, 10)]);
} else if (e.key === "Escape") {
_closeProjectCodeList();
}
}
function _highlightProjectCode(items) {
items.forEach((el, i) => el.classList.toggle("active", i === _projectCodeActiveIndex));
items[_projectCodeActiveIndex]?.scrollIntoView({ block: "nearest" });
}
// ════════════════════════════════════════════════════════════════════════
// SUBMIT
// ════════════════════════════════════════════════════════════════════════
async function _handleSubmit() {
_clearErrors();
if (!_validateForm()) {
showToast("warning", "Please fix the highlighted fields.", "Validation", 3000);
return;
}
const confirmed = await showConfirmation({
title: _activeType === "ris" ? "Create Return Issuance Slip" : "Create Material Return Slip",
message: _activeType === "ris"
? `Issue <strong>${risQty.value} pcs</strong> from inventory?`
: `Return <strong>${mrsQty.value} pcs</strong> back to stock?`,
type: "warning",
confirmText: _activeType === "ris" ? "Create RIS" : "Create MRS",
cancelText: "Cancel"
});
if (!confirmed) return;
btnSubmit.disabled = true;
btnSubmit.querySelector("i").className = "fas fa-spinner fa-spin";
try {
const payload = _activeType === "ris"
? {
InventoryId: _ctx.inventoryId,
PRDetailId: parseInt(risPRRef.value, 10) || 0,
ProjectCodeId: parseInt(risProjectCode.value, 10),
DisciplineId: parseInt(risDiscipline.value, 10),
QtyIssued: parseInt(risQty.value, 10),
Remarks: risRemarks.value.trim() || null
}
: {
RISId: parseInt(mrsRISSelect.value, 10),
QtyReturned: parseInt(mrsQty.value, 10),
ReturnedBy: _ctx.department || "N/A",
Condition: mrsCondition.value,
Remarks: mrsRemarks.value.trim() || null
};
const endpoint = _activeType === "ris"
? "/RISMgmt/CreateRIS"
: "/MRSMgmt/CreateMRS";
const res = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
const json = await res.json();
if (!res.ok || !json.success) {
showToast("error", json.message ?? "An error occurred.", "Failed", 4000);
return;
}
showToast("success", json.message, "Done!", 3500);
const onSuccess = _onSuccess;
_close();
if (typeof onSuccess === "function") onSuccess();
} catch (err) {
showToast("error", "Request failed. Please try again.", "Error", 4000);
} finally {
btnSubmit.disabled = false;
btnSubmit.querySelector("i").className = "fas fa-check";
}
}
// ════════════════════════════════════════════════════════════════════════
// HELPERS
// ════════════════════════════════════════════════════════════════════════
function _showOverlay(show) {
const el = document.getElementById("transact-modal-overlay");
if (el) el.style.display = show ? "flex" : "none";
}
function _showLoading(loading) {
document.getElementById("tm-loading").style.display = loading ? "flex" : "none";
document.getElementById("tm-body").style.display = loading ? "none" : "block";
if (btnSubmit) btnSubmit.disabled = loading;
}
function _close() {
document.getElementById("transact-modal-overlay")?.remove();
_ctx = null;
_onSuccess = null;
}
return { open };
})();
</script>