NonInventPurchasingSystem/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/InventoryTransaction.cshtml

1003 lines
41 KiB
Plaintext

@* ── FILTER BAR ── *@
<div class="inv-filters">
<div class="inv-search-box">
<i class="fas fa-hashtag"></i>
<input type="text" id="ris-srchRISNo" placeholder="RIS Number..." />
</div>
<div class="inv-search-box">
<i class="fas fa-box"></i>
<input type="text" id="ris-srchItemName" placeholder="Item Name..." />
</div>
<div class="inv-search-box">
<i class="fas fa-user"></i>
<input type="text" id="ris-srchIssuedTo" placeholder="Issued To..." />
</div>
@* ── Discipline dropdown ── *@
<div class="inv-department-wrap" id="ris-disciplineWrap">
<div class="inv-dep-trigger">
<span class="inv-dep-left">
<i class="fas fa-tools"></i>
<span class="inv-dep-lbl">All Disciplines</span>
</span>
<i class="fas fa-chevron-down inv-dep-caret"></i>
</div>
<div class="inv-dep-dropdown">
<div class="inv-dep-searchbox">
<i class="fas fa-search"></i>
<input type="text" placeholder="Search discipline..." autocomplete="off" />
</div>
<div class="inv-dep-list">
<div class="inv-dep-opt active" data-value="">
<i class="fas fa-th-large"></i> All Disciplines
</div>
</div>
</div>
</div>
@* ── Status filter ── *@
<div class="inv-department-wrap" id="ris-statusWrap" style="max-width:160px">
<div class="inv-dep-trigger">
<span class="inv-dep-left">
<i class="fas fa-circle-dot"></i>
<span class="inv-dep-lbl">All Status</span>
</span>
<i class="fas fa-chevron-down inv-dep-caret"></i>
</div>
<div class="inv-dep-dropdown">
<div class="inv-dep-searchbox">
<i class="fas fa-search"></i>
<input type="text" placeholder="Search status..." autocomplete="off" />
</div>
<div class="inv-dep-list">
<div class="inv-dep-opt active" data-value="">
<i class="fas fa-th-large"></i> All Status
</div>
</div>
</div>
</div>
<div class="inv-filter-right">
<span class="inv-pgsz-lbl">Show</span>
<select class="inv-pgsz-sel" id="ris-pageSize">
<option value="6">6 per page</option>
<option value="12" selected>12 per page</option>
<option value="24">24 per page</option>
<option value="48">48 per page</option>
</select>
<span class="inv-result-count" id="ris-resultCount">0 results</span>
</div>
</div>
@* ── CARD GRID ── *@
<div class="ris-grid" id="ris-grid">
<div class="inv-state">
<div class="inv-spinner"></div>
<p>Loading…</p>
</div>
</div>
@* ── PAGINATION ── *@
<div class="inv-pagination">
<span class="inv-pg-info" id="ris-pageInfo"></span>
<div class="inv-pg-btns" id="ris-pageButtons"></div>
</div>
@* ── APPROVE CONFIRMATION MODAL ── *@
<div id="ris-approve-modal-overlay" style="display:none;position:fixed;inset:0;
z-index:1080;background:rgba(0,0,0,.45);
display:none;align-items:center;justify-content:center;padding:16px">
<div style="background:var(--card-bg,#fff);border-radius:14px;
border:1px solid var(--border,#d6eaec);width:100%;max-width:440px;
overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.2)">
<div style="background:linear-gradient(135deg,#0d5c63,#0e7c86);
padding:16px 18px;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="fas fa-check-circle" style="color:#fff;font-size:16px"></i>
</div>
<div>
<div style="font-size:14px;font-weight:600;color:#fff">Approve RIS</div>
<div id="ris-approve-subtitle"
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
Loading…
</div>
</div>
</div>
<div style="padding:20px 18px">
<div id="ris-approve-detail"
style="background:var(--teal-pale,#e6f7f8);border:1px solid var(--border,#d6eaec);
border-radius:10px;padding:14px 16px;margin-bottom:16px;
display:grid;grid-template-columns:1fr 1fr;gap:10px">
</div>
<p style="font-size:13px;color:var(--text-dark,#1a2e35);line-height:1.6">
Approving this slip will confirm the issuance and update the inventory.
<strong>This action cannot be undone.</strong>
</p>
</div>
<div style="padding:12px 18px;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="ris-approve-cancel-btn" class="tm-btn-cancel">Cancel</button>
<button id="ris-approve-confirm-btn"
style="padding:8px 20px;border-radius:8px;border:none;
background:#0e7c86;color:#fff;font-family:'DM Sans',sans-serif;
font-size:.85rem;font-weight:600;cursor:pointer;
display:flex;align-items:center;gap:7px">
<i class="fas fa-check"></i> Approve RIS
</button>
</div>
</div>
</div>
@* ── CANCEL CONFIRMATION MODAL ── *@
<div id="ris-cancel-modal-overlay" style="display:none;position:fixed;inset:0;
z-index:1080;background:rgba(0,0,0,.45);
display:none;align-items:center;justify-content:center;padding:16px">
<div style="background:var(--card-bg,#fff);border-radius:14px;
border:1px solid var(--border,#d6eaec);width:100%;max-width:440px;
overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.2)">
<div style="background:linear-gradient(135deg,#7f1d1d,#b91c1c);
padding:16px 18px;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="fas fa-ban" style="color:#fff;font-size:16px"></i>
</div>
<div>
<div style="font-size:14px;font-weight:600;color:#fff">Cancel RIS</div>
<div id="ris-cancel-subtitle"
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
Loading…
</div>
</div>
</div>
<div style="padding:20px 18px">
<div id="ris-cancel-detail"
style="background:#fef2f2;border:1px solid #fecaca;
border-radius:10px;padding:14px 16px;margin-bottom:16px;
display:grid;grid-template-columns:1fr 1fr;gap:10px">
</div>
<div style="display:flex;gap:8px;padding:10px 12px;background:#fef2f2;
border:1px solid #fecaca;border-radius:8px;margin-bottom:12px">
<i class="fas fa-exclamation-triangle"
style="color:#dc2626;flex-shrink:0;margin-top:2px"></i>
<p style="font-size:12px;color:#7f1d1d;line-height:1.6;margin:0">
Cancelling this slip will <strong>reverse the inventory deduction</strong>
and restore the qty back to on-hand. This cannot be undone.
</p>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-sticky-note"></i> Reason for cancellation
<span class="tm-req">*</span>
</label>
<input id="ris-cancel-reason" class="tm-input"
type="text" placeholder="Enter reason…" maxlength="500">
</div>
</div>
<div style="padding:12px 18px;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="ris-cancel-dismiss-btn" class="tm-btn-cancel">Dismiss</button>
<button id="ris-cancel-confirm-btn"
style="padding:8px 20px;border-radius:8px;border:none;
background:#dc2626;color:#fff;font-family:'DM Sans',sans-serif;
font-size:.85rem;font-weight:600;cursor:pointer;
display:flex;align-items:center;gap:7px">
<i class="fas fa-ban"></i> Cancel RIS
</button>
</div>
</div>
</div>
<style>
/* ── RIS card grid ── */
.ris-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 16px;
min-height: 220px;
}
@@media (max-width: 768px) {
.ris-grid {
grid-template-columns: 1fr;
}
}
@@media (min-width: 769px) and (max-width: 1100px) {
.ris-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* ── RIS card ── */
.ris-card {
background: var(--card-bg, #fff);
border-radius: var(--radius-lg, 14px);
box-shadow: var(--shadow-card);
border: 1px solid var(--border, #d6eaec);
overflow: hidden;
display: flex;
flex-direction: column;
transition: box-shadow .25s, transform .25s;
}
.ris-card:hover {
box-shadow: var(--shadow-hover);
transform: translateY(-2px);
}
/* ── Card header ── */
.ris-card-hd {
background: linear-gradient(135deg, var(--teal-dark, #0d5c63), var(--teal-mid, #0e7c86));
padding: 14px 16px 12px;
position: relative;
}
.ris-card-no {
font-size: .7rem;
color: rgba(255,255,255,.6);
font-weight: 700;
letter-spacing: .07em;
text-transform: uppercase;
margin-bottom: 3px;
}
.ris-card-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1rem;
font-weight: 700;
color: #fff;
line-height: 1.25;
word-break: break-word;
margin-bottom: 6px;
}
.ris-card-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 11px;
color: rgba(255,255,255,.65);
}
.ris-card-meta span {
display: flex;
align-items: center;
gap: 4px;
}
/* ── Status badge positioned top-right ── */
.ris-status-badge {
position: absolute;
top: 12px;
right: 14px;
font-size: 10px;
font-weight: 700;
padding: 3px 10px;
border-radius: 50px;
letter-spacing: .05em;
text-transform: uppercase;
}
.ris-status-draft {
background: rgba(255,255,255,.18);
color: #fff;
}
.ris-status-approved {
background: #dcfce7;
color: #166534;
}
.ris-status-cancelled {
background: #fee2e2;
color: #991b1b;
}
/* ── Stats row ── */
.ris-stats-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border, #d6eaec);
border-bottom: 1px solid var(--border, #d6eaec);
}
.ris-stat {
background: var(--card-bg, #fff);
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 2px;
}
.ris-stat-lbl {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
color: var(--text-muted, #6b8890);
display: flex;
align-items: center;
gap: 4px;
}
.ris-stat-lbl i {
font-size: 11px;
}
.ris-stat-val {
font-size: 18px;
font-weight: 700;
color: var(--text-dark, #1a2e35);
line-height: 1.2;
}
.ris-stat-val.teal {
color: var(--teal-mid, #0e7c86);
}
.ris-stat-val.red {
color: #dc2626;
}
.ris-stat-val.amber {
color: #92400e;
}
/* ── Body ── */
.ris-card-body {
padding: 12px 16px;
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.ris-field-row {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 12px;
}
.ris-field-lbl {
min-width: 80px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
color: var(--text-muted, #6b8890);
padding-top: 1px;
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.ris-field-lbl i {
font-size: 11px;
}
.ris-field-val {
color: var(--text-dark, #1a2e35);
line-height: 1.5;
word-break: break-word;
}
/* ── Discipline chip ── */
.ris-discipline-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px;
border-radius: 50px;
font-size: 11px;
font-weight: 600;
background: var(--teal-pale, #e6f7f8);
color: var(--teal-dark, #0d5c63);
border: 1px solid var(--border, #d6eaec);
}
/* ── Footer ── */
.ris-card-ft {
padding: 10px 16px 14px;
display: flex;
gap: 8px;
border-top: 1px solid var(--border, #d6eaec);
}
.ris-btn {
flex: 1;
padding: 8px 12px;
border-radius: var(--radius-sm, 8px);
border: none;
cursor: pointer;
font-family: 'DM Sans', sans-serif;
font-size: .82rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
transition: all .2s;
}
.ris-btn-approve {
background: var(--teal-mid, #0e7c86);
color: #fff;
}
.ris-btn-approve:hover {
background: var(--teal-dark, #0d5c63);
}
.ris-btn-approve:disabled {
opacity: .4;
cursor: default;
}
.ris-btn-cancel {
background: transparent;
color: #dc2626;
border: 1.5px solid #fca5a5;
}
.ris-btn-cancel:hover {
background: #fef2f2;
border-color: #dc2626;
}
.ris-btn-cancel:disabled {
opacity: .4;
cursor: default;
}
/* ── Approve modal detail field ── */
.am-field {
display: flex;
flex-direction: column;
gap: 3px;
}
.am-lbl {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
color: var(--text-muted, #6b8890);
display: flex;
align-items: center;
gap: 4px;
}
.am-lbl i {
font-size: 11px;
}
.am-val {
font-size: 13px;
font-weight: 600;
color: var(--teal-dark, #0d5c63);
}
</style>
<script>
(function () {
"use strict";
const H = window.InventoryHelpers;
// ── State ──────────────────────────────────────────────────────────────
const s = {
page: 1, pageSize: 12, totalCount: 0,
searchRISNo: "", searchItemName: "", searchIssuedTo: "",
discipline: "", status: "",
timer: null
};
// ── Elements ───────────────────────────────────────────────────────────
const grid = document.getElementById("ris-grid");
const countEl = document.getElementById("ris-resultCount");
const pageInfo = document.getElementById("ris-pageInfo");
const pageBtns = document.getElementById("ris-pageButtons");
const inRISNo = document.getElementById("ris-srchRISNo");
const inItem = document.getElementById("ris-srchItemName");
const inIssued = document.getElementById("ris-srchIssuedTo");
const inSize = document.getElementById("ris-pageSize");
const discWrap = document.getElementById("ris-disciplineWrap");
const statusWrap = document.getElementById("ris-statusWrap");
// ── Guard ──────────────────────────────────────────────────────────────
if (!grid || !discWrap) {
console.error("RIS tab init failed — missing elements.");
return;
}
// ── Discipline dropdown ────────────────────────────────────────────────
const discDropdown = H.initSearchDropdown(discWrap, val => {
s.discipline = val;
s.page = 1;
fetchData();
}, {
cssPrefix: "inv-dep",
allLabel: "All Disciplines",
allIcon: "fas fa-th-large",
itemIcon: "fas fa-tools"
});
// ── Status dropdown (static options) ──────────────────────────────────
const statusDropdown = H.initSearchDropdown(statusWrap, val => {
s.status = val;
s.page = 1;
fetchData();
}, {
cssPrefix: "inv-dep",
allLabel: "All Status",
allIcon: "fas fa-th-large",
itemIcon: "fas fa-circle-dot"
});
// Seed static status options immediately
statusDropdown.setItems(["Draft", "Approved", "Cancelled"]);
// ── Search inputs ──────────────────────────────────────────────────────
[inRISNo, inItem, inIssued].forEach(el => {
if (!el) return;
el.addEventListener("input", () => {
clearTimeout(s.timer);
s.timer = setTimeout(() => {
s.searchRISNo = inRISNo?.value.trim() ?? "";
s.searchItemName = inItem?.value.trim() ?? "";
s.searchIssuedTo = inIssued?.value.trim() ?? "";
s.page = 1;
fetchData();
}, 350);
});
});
if (inSize) {
inSize.addEventListener("change", () => {
s.pageSize = parseInt(inSize.value, 10);
s.page = 1;
fetchData();
});
}
// ── Status value → short code for API ─────────────────────────────────
function statusToCode(label) {
return { "Draft": "0", "Approved": "1", "Cancelled": "2" }[label] ?? "";
}
// ══════════════════════════════════════════════════════════════════════
// FETCH
// ══════════════════════════════════════════════════════════════════════
async function fetchData() {
grid.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
<div class="inv-spinner"></div><p>Loading…</p></div>`;
const p = new URLSearchParams({
searchRISNo: s.searchRISNo,
searchItemName: s.searchItemName,
searchIssuedTo: s.searchIssuedTo,
discipline: s.discipline,
status: statusToCode(s.status),
pageNumber: s.page,
pageSize: s.pageSize,
draw: Date.now()
});
try {
const res = await fetch(`/RISMgmt/GetRIS?${p}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
const data = json.data ?? json;
s.totalCount = json.recordsTotal ?? 0;
if (json.disciplineList?.length)
discDropdown.setItems(json.disciplineList.map(d => d.disciplineName));
renderCards(json.data ?? []);
H.renderPagination(
document.getElementById("ris-pageButtons"),
document.getElementById("ris-pageInfo"),
s,
pg => { s.page = pg; fetchData(); }
);
const c = document.getElementById("ris-resultCount");
if (c) c.textContent =
`${s.totalCount.toLocaleString()} result${s.totalCount !== 1 ? "s" : ""}`;
} catch (err) {
console.error("GetRIS error:", err);
grid.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
<i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i>
<p>Failed to load data.</p></div>`;
}
}
// ══════════════════════════════════════════════════════════════════════
// RENDER CARDS
// ══════════════════════════════════════════════════════════════════════
function renderCards(data) {
const g = document.getElementById("ris-grid");
if (!data.length) {
g.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
<i class="fas fa-inbox"></i>
<p>No RIS records found.</p></div>`;
return;
}
g.innerHTML = data.map(item => buildRISCardHtml(item)).join("");
// ── Wire Approve buttons ──
g.querySelectorAll(".btn-ris-approve").forEach(btn =>
btn.addEventListener("click", () => {
const risId = parseInt(btn.dataset.risid, 10);
const risNo = btn.dataset.risno;
openApproveModal(risId, risNo, btn.dataset);
})
);
// ── Wire Cancel buttons ──
g.querySelectorAll(".btn-ris-cancel").forEach(btn =>
btn.addEventListener("click", () => {
const risId = parseInt(btn.dataset.risid, 10);
const risNo = btn.dataset.risno;
openCancelModal(risId, risNo, btn.dataset);
})
);
}
// ══════════════════════════════════════════════════════════════════════
// CARD HTML BUILDER
// ══════════════════════════════════════════════════════════════════════
function buildRISCardHtml(item) {
const risId = item.risId ?? item.RISId ?? 0;
const risNo = item.risNo ?? item.RISNo ?? "—";
const itemName = item.itemName ?? item.ItemName ?? "—";
const itemNo = item.itemNo ?? item.ItemNo ?? "—";
const issuedTo = item.issuedTo ?? item.IssuedTo ?? "—";
const discipline = item.disciplineName ?? item.DisciplineName ?? "—";
const qtyIssued = item.qtyIssued ?? item.QtyIssued ?? 0;
const totalReturned= item.totalReturned ?? item.TotalReturned ?? 0;
const netIssued = qtyIssued - totalReturned;
const status = item.status ?? item.Status ?? 0;
const statusLabel = item.statusLabel ?? item.StatusLabel ?? _statusLabel(status);
const remarks = item.remarks ?? item.Remarks ?? null;
const createdDate = item.createdDate ?? item.CreatedDate ?? null;
const approvedBy = item.approvedBy ?? item.ApprovedBy ?? null;
const approvedDate = item.approvedDate ?? item.ApprovedDate ?? null;
const mrsCount = item.mrsCount ?? item.MRSCount ?? 0;
const isDraft = status === 0;
const isApproved = status === 1;
const isCancelled = status === 2;
const statusCls = isDraft ? "ris-status-draft"
: isApproved ? "ris-status-approved"
: "ris-status-cancelled";
const statusIcon = isDraft ? "fas fa-clock"
: isApproved ? "fas fa-check-circle"
: "fas fa-ban";
return `
<div class="ris-card">
<!-- HEAD -->
<div class="ris-card-hd">
<span class="${statusCls} ris-status-badge">
<i class="${statusIcon}"></i> ${H.escHtml(statusLabel)}
</span>
<div class="ris-card-no">RIS NO</div>
<div class="ris-card-title">${H.escHtml(risNo)}</div>
<div class="ris-card-meta">
<span><i class="fas fa-box"></i> ${H.escHtml(itemName)}</span>
<span><i class="fas fa-hashtag"></i> #${H.escHtml(String(itemNo))}</span>
</div>
<div class="ris-card-meta" style="margin-top:4px">
<span><i class="fas fa-user"></i> ${H.escHtml(issuedTo)}</span>
<span><i class="fas fa-clock"></i> ${_formatDate(createdDate)}</span>
</div>
</div>
<!-- STATS -->
<div class="ris-stats-row">
<div class="ris-stat">
<span class="ris-stat-lbl">
<i class="fas fa-cubes"></i> Qty Issued
</span>
<span class="ris-stat-val teal">${qtyIssued}</span>
</div>
<div class="ris-stat">
<span class="ris-stat-lbl">
<i class="fas fa-undo"></i> Returned
</span>
<span class="ris-stat-val amber">${totalReturned}</span>
</div>
<div class="ris-stat">
<span class="ris-stat-lbl">
<i class="fas fa-layer-group"></i> Net Issued
</span>
<span class="ris-stat-val ${netIssued > 0 ? "red" : ""}">${netIssued}</span>
</div>
</div>
<!-- BODY -->
<div class="ris-card-body">
<div class="ris-field-row">
<span class="ris-field-lbl">
<i class="fas fa-tools"></i> Discipline
</span>
<span class="ris-field-val">
<span class="ris-discipline-chip">
<i class="fas fa-tools"></i> ${H.escHtml(discipline)}
</span>
</span>
</div>
${mrsCount > 0 ? `
<div class="ris-field-row">
<span class="ris-field-lbl">
<i class="fas fa-file-import"></i> MRS Count
</span>
<span class="ris-field-val">
<span style="background:#E6F1FB;color:#185FA5;padding:2px 8px;
border-radius:50px;font-size:11px;font-weight:600">
${mrsCount} return slip${mrsCount !== 1 ? "s" : ""}
</span>
</span>
</div>` : ""}
${remarks ? `
<div class="ris-field-row">
<span class="ris-field-lbl">
<i class="fas fa-sticky-note"></i> Remarks
</span>
<span class="ris-field-val" style="color:var(--text-muted,#6b8890)">
${H.escHtml(remarks)}
</span>
</div>` : ""}
${isApproved && approvedBy ? `
<div class="ris-field-row">
<span class="ris-field-lbl">
<i class="fas fa-user-check"></i> Approved by
</span>
<span class="ris-field-val">
${H.escHtml(approvedBy)}
<span style="color:var(--text-muted,#6b8890);font-size:11px;margin-left:4px">
${_formatDate(approvedDate)}
</span>
</span>
</div>` : ""}
</div>
<!-- FOOTER -->
<div class="ris-card-ft">
${isDraft ? `
<button class="ris-btn ris-btn-approve btn-ris-approve"
data-risid="${risId}"
data-risno="${H.escAttr(risNo)}"
data-itemname="${H.escAttr(itemName)}"
data-issuedto="${H.escAttr(issuedTo)}"
data-discipline="${H.escAttr(discipline)}"
data-qtyissued="${qtyIssued}">
<i class="fas fa-check-circle"></i> Approve
</button>
<button class="ris-btn ris-btn-cancel btn-ris-cancel"
data-risid="${risId}"
data-risno="${H.escAttr(risNo)}"
data-itemname="${H.escAttr(itemName)}"
data-issuedto="${H.escAttr(issuedTo)}"
data-discipline="${H.escAttr(discipline)}"
data-qtyissued="${qtyIssued}">
<i class="fas fa-ban"></i> Cancel
</button>` : `
<button class="ris-btn ris-btn-cancel btn-ris-cancel"
${isCancelled ? "disabled" : ""}
data-risid="${risId}"
data-risno="${H.escAttr(risNo)}"
data-itemname="${H.escAttr(itemName)}"
data-issuedto="${H.escAttr(issuedTo)}"
data-discipline="${H.escAttr(discipline)}"
data-qtyissued="${qtyIssued}"
style="flex:1">
<i class="fas fa-ban"></i>
${isCancelled ? "Cancelled" : "Cancel"}
</button>`}
</div>
</div>`;
}
// ══════════════════════════════════════════════════════════════════════
// APPROVE MODAL
// ══════════════════════════════════════════════════════════════════════
function openApproveModal(risId, risNo, dataset) {
const overlay = document.getElementById("ris-approve-modal-overlay");
document.getElementById("ris-approve-subtitle").textContent =
`${risNo} — ${dataset.itemname ?? ""}`;
document.getElementById("ris-approve-detail").innerHTML = `
<div class="am-field">
<span class="am-lbl"><i class="fas fa-hashtag"></i> RIS No</span>
<span class="am-val">${H.escHtml(risNo)}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-cubes"></i> Qty Issued</span>
<span class="am-val">${dataset.qtyissued ?? 0} pcs</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-box"></i> Item</span>
<span class="am-val">${H.escHtml(dataset.itemname ?? "—")}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-user"></i> Issued To</span>
<span class="am-val">${H.escHtml(dataset.issuedto ?? "—")}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-tools"></i> Discipline</span>
<span class="am-val">${H.escHtml(dataset.discipline ?? "—")}</span>
</div>`;
overlay.style.display = "flex";
// ── Wire buttons fresh each open ──
const confirmBtn = document.getElementById("ris-approve-confirm-btn");
const cancelBtn = document.getElementById("ris-approve-cancel-btn");
const closeModal = () => { overlay.style.display = "none"; };
// Clone to remove stale listeners
const newConfirm = confirmBtn.cloneNode(true);
const newCancel = cancelBtn.cloneNode(true);
confirmBtn.replaceWith(newConfirm);
cancelBtn.replaceWith(newCancel);
newCancel.addEventListener("click", closeModal);
overlay.addEventListener("click", e => { if (e.target === overlay) closeModal(); });
newConfirm.addEventListener("click", async () => {
newConfirm.disabled = true;
newConfirm.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Approving…`;
try {
const res = await fetch(`/RISMgmt/ApproveRIS`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ risId })
});
const json = await res.json();
const d = json.data ?? json;
if (!res.ok || !d.success) {
showToast("error", d.message ?? "Could not approve RIS.", "Failed", 4000);
return;
}
showToast("success", d.message ?? `RIS ${risNo} approved.`, "Approved!", 3500);
closeModal();
fetchData();
} catch (err) {
console.error("ApproveRIS error:", err);
showToast("error", "Request failed. Please try again.", "Error", 4000);
} finally {
newConfirm.disabled = false;
newConfirm.innerHTML = `<i class="fas fa-check"></i> Approve RIS`;
}
});
}
// ══════════════════════════════════════════════════════════════════════
// CANCEL MODAL
// ══════════════════════════════════════════════════════════════════════
function openCancelModal(risId, risNo, dataset) {
const overlay = document.getElementById("ris-cancel-modal-overlay");
const reasonEl = document.getElementById("ris-cancel-reason");
reasonEl.value = "";
reasonEl.classList.remove("error");
document.getElementById("ris-cancel-subtitle").textContent =
`${risNo} — ${dataset.itemname ?? ""}`;
document.getElementById("ris-cancel-detail").innerHTML = `
<div class="am-field">
<span class="am-lbl"><i class="fas fa-hashtag"></i> RIS No</span>
<span class="am-val" style="color:#991b1b">${H.escHtml(risNo)}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-cubes"></i> Qty Issued</span>
<span class="am-val" style="color:#991b1b">${dataset.qtyissued ?? 0} pcs</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-box"></i> Item</span>
<span class="am-val">${H.escHtml(dataset.itemname ?? "—")}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-user"></i> Issued To</span>
<span class="am-val">${H.escHtml(dataset.issuedto ?? "—")}</span>
</div>`;
overlay.style.display = "flex";
const confirmBtn = document.getElementById("ris-cancel-confirm-btn");
const dismissBtn = document.getElementById("ris-cancel-dismiss-btn");
const closeModal = () => { overlay.style.display = "none"; };
const newConfirm = confirmBtn.cloneNode(true);
const newDismiss = dismissBtn.cloneNode(true);
confirmBtn.replaceWith(newConfirm);
dismissBtn.replaceWith(newDismiss);
newDismiss.addEventListener("click", closeModal);
overlay.addEventListener("click", e => { if (e.target === overlay) closeModal(); });
newConfirm.addEventListener("click", async () => {
const reason = reasonEl.value.trim();
if (!reason) {
reasonEl.classList.add("error");
reasonEl.focus();
showToast("warning", "Please enter a reason for cancellation.", "Required", 3000);
return;
}
newConfirm.disabled = true;
newConfirm.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Cancelling…`;
try {
const res = await fetch(`/RISMgmt/CancelRIS`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ risId, reason })
});
const json = await res.json();
const d = json.data ?? json;
if (!res.ok || !d.success) {
showToast("error", d.message ?? "Could not cancel RIS.", "Failed", 4000);
return;
}
showToast("success", d.message ?? `RIS ${risNo} cancelled.`, "Cancelled", 3500);
closeModal();
fetchData();
} catch (err) {
console.error("CancelRIS error:", err);
showToast("error", "Request failed. Please try again.", "Error", 4000);
} finally {
newConfirm.disabled = false;
newConfirm.innerHTML = `<i class="fas fa-ban"></i> Cancel RIS`;
}
});
}
// ══════════════════════════════════════════════════════════════════════
// HELPERS
// ══════════════════════════════════════════════════════════════════════
function _statusLabel(code) {
return { 0: "Draft", 1: "Approved", 2: "Cancelled" }[code] ?? "Unknown";
}
function _formatDate(raw) {
if (!raw) return "—";
const d = new Date(raw);
return isNaN(d) ? raw : d.toLocaleDateString("en-US", {
month: "short", day: "numeric", year: "numeric"
});
}
// ── Initial load ───────────────────────────────────────────────────────
fetchData();
})();
</script>