NonInventPurchasingSystem/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/MRS.cshtml
2026-06-18 16:51:31 +08:00

1273 lines
59 KiB
Plaintext

@* ── FILTER BAR ── *@
<div class="inv-filters">
<div class="inv-search-box">
<i class="fas fa-hashtag"></i>
<input type="text" id="mrs-srchMRSNo" placeholder="MRS Number..." />
</div>
<div class="inv-search-box">
<i class="fas fa-receipt"></i>
<input type="text" id="mrs-srchRISNo" placeholder="RIS Number..." />
</div>
<div class="inv-search-box">
<i class="fas fa-box"></i>
<input type="text" id="mrs-srchItemName" placeholder="Item Name..." />
</div>
<div class="inv-search-box">
<i class="fas fa-user"></i>
<input type="text" id="mrs-srchReturnedBy" placeholder="Returned By..." />
</div>
@* ── Status dropdown ── *@
<div class="inv-department-wrap" id="mrs-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>
@* ── Condition dropdown ── *@
<div class="inv-department-wrap" id="mrs-conditionWrap" style="max-width:160px">
<div class="inv-dep-trigger">
<span class="inv-dep-left">
<i class="fas fa-tag"></i>
<span class="inv-dep-lbl">All Conditions</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 condition..." autocomplete="off" />
</div>
<div class="inv-dep-list">
<div class="inv-dep-opt active" data-value="">
<i class="fas fa-th-large"></i> All Conditions
</div>
</div>
</div>
</div>
<div class="inv-filter-right">
<span class="inv-pgsz-lbl">Show</span>
<select class="inv-pgsz-sel" id="mrs-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="mrs-resultCount">0 results</span>
</div>
</div>
@* ── CARD GRID ── *@
<div class="mrs-grid" id="mrs-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="mrs-pageInfo"></span>
<div class="inv-pg-btns" id="mrs-pageButtons"></div>
</div>
@* ══════════════════════════════════════════════════════════════════════════
CREATE MRS MODAL
══════════════════════════════════════════════════════════════════════════ *@
<div id="mrs-create-overlay" style="display:none;position:fixed;inset:0;z-index:1080;
background:rgba(0,0,0,.45);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:520px;
overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.2)">
<!-- HEAD -->
<div style="background:linear-gradient(135deg,#185FA5,#1a7abf);
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="fas fa-file-import" style="color:#fff;font-size:16px"></i>
</div>
<div>
<div style="font-size:14px;font-weight:600;color:#fff">New Material Return Slip</div>
<div id="mrs-create-subtitle"
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
Select an approved RIS to return against
</div>
</div>
</div>
<button id="mrs-create-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>
<!-- BODY -->
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:14px">
<!-- RIS reference search -->
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-receipt"></i> RIS Reference <span class="tm-req">*</span>
</label>
<div style="position:relative">
<input id="mrs-ris-search" class="tm-input"
type="text" placeholder="Type RIS number to search…"
autocomplete="off">
<div id="mrs-ris-results" style="
display:none;position:absolute;top:100%;left:0;right:0;z-index:10;
background:#fff;border:1.5px solid var(--teal-mid,#0e7c86);
border-top:none;border-radius:0 0 8px 8px;
max-height:200px;overflow-y:auto;
box-shadow:0 8px 20px rgba(0,0,0,.12)">
</div>
</div>
</div>
<!-- Selected RIS detail badge -->
<div id="mrs-selected-ris-badge" style="display:none;
background:var(--teal-pale,#e6f7f8);border:1px solid var(--border,#d6eaec);
border-radius:10px;padding:12px 14px;
display:none;flex-direction:column;gap:8px">
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px">
<div class="am-field">
<span class="am-lbl"><i class="fas fa-hashtag"></i> RIS No</span>
<span class="am-val" id="mrs-sel-risno">—</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-cubes"></i> Qty Issued</span>
<span class="am-val" id="mrs-sel-qtyissued">—</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-undo"></i> Max Return</span>
<span class="am-val" id="mrs-sel-maxreturn"
style="color:#0e7c86">—</span>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<div class="am-field">
<span class="am-lbl"><i class="fas fa-box"></i> Item</span>
<span class="am-val" id="mrs-sel-itemname">—</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-tools"></i> Discipline</span>
<span class="am-val" id="mrs-sel-discipline">—</span>
</div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<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="mrs-create-qty" class="tm-input"
type="number" min="1" placeholder="0" disabled>
<span id="mrs-create-qty-hint" class="tm-hint" style="display:none"></span>
<span id="mrs-create-qty-warn" class="tm-warn" style="display:none">
<i class="fas fa-exclamation-triangle"></i>
<span id="mrs-create-qty-warn-text"></span>
</span>
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-tag"></i> Condition <span class="tm-req">*</span>
</label>
<select id="mrs-create-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-user"></i> Returned By <span class="tm-req">*</span>
</label>
<input id="mrs-create-returnedby" class="tm-input"
type="text" placeholder="Name or user ID…">
</div>
<div class="tm-form-group">
<label class="tm-label">
<i class="fas fa-sticky-note"></i> Remarks
</label>
<input id="mrs-create-remarks" class="tm-input"
type="text" placeholder="Reason for return…">
</div>
</div>
<!-- FOOTER -->
<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="mrs-create-cancel-btn" class="tm-btn-cancel">Cancel</button>
<button id="mrs-create-submit-btn"
style="padding:8px 20px;border-radius:8px;border:none;
background:#185FA5;color:#fff;font-family:'DM Sans',sans-serif;
font-size:.85rem;font-weight:600;cursor:pointer;
display:flex;align-items:center;gap:7px" disabled>
<i class="fas fa-file-import"></i>
<span>Create MRS</span>
</button>
</div>
</div>
</div>
@* ══════════════════════════════════════════════════════════════════════════
APPROVE MRS MODAL
══════════════════════════════════════════════════════════════════════════ *@
<div id="mrs-approve-overlay" style="display:none;position:fixed;inset:0;z-index:1080;
background:rgba(0,0,0,.45);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 MRS</div>
<div id="mrs-approve-subtitle"
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">—</div>
</div>
</div>
<div style="padding:20px 18px">
<div id="mrs-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 return slip will confirm the return and
<strong>restore the qty back to inventory on-hand.</strong>
This action cannot be undone.
</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="mrs-approve-cancel-btn" class="tm-btn-cancel">Cancel</button>
<button id="mrs-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 MRS
</button>
</div>
</div>
</div>
@* ══════════════════════════════════════════════════════════════════════════
CANCEL MRS MODAL
══════════════════════════════════════════════════════════════════════════ *@
<div id="mrs-cancel-overlay" style="display:none;position:fixed;inset:0;z-index:1080;
background:rgba(0,0,0,.45);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 MRS</div>
<div id="mrs-cancel-subtitle"
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">—</div>
</div>
</div>
<div style="padding:20px 18px">
<div id="mrs-cancel-detail"
style="background:#fef2f2;border:1px solid #fecaca;
border-radius:10px;padding:14px 16px;margin-bottom:14px;
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:14px">
<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 will <strong>reverse the inventory restoration</strong>
— the returned qty will be deducted from on-hand again.
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="mrs-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="mrs-cancel-dismiss-btn" class="tm-btn-cancel">Dismiss</button>
<button id="mrs-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 MRS
</button>
</div>
</div>
</div>
<style>
/* ── MRS grid ── */
.mrs-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 16px;
min-height: 220px;
}
@@media (max-width: 768px) { .mrs-grid { grid-template-columns: 1fr; } }
@@media (min-width: 769px) and (max-width: 1100px) {
.mrs-grid { grid-template-columns: repeat(2, 1fr); }
}
/* ── MRS card ── */
.mrs-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;
}
.mrs-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }
/* ── Card header — blue theme for MRS ── */
.mrs-card-hd {
background: linear-gradient(135deg, #0c3d6b, #185FA5);
padding: 14px 16px 12px;
position: relative;
}
.mrs-card-no {
font-size: .7rem;
color: rgba(255,255,255,.6);
font-weight: 700;
letter-spacing: .07em;
text-transform: uppercase;
margin-bottom: 3px;
}
.mrs-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;
}
.mrs-card-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 11px;
color: rgba(255,255,255,.65);
}
.mrs-card-meta span { display: flex; align-items: center; gap: 4px; }
/* ── Status badge ── */
.mrs-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;
}
.mrs-status-draft { background: rgba(255,255,255,.18); color: #fff; }
.mrs-status-approved { background: #dcfce7; color: #166534; }
.mrs-status-cancelled { background: #fee2e2; color: #991b1b; }
/* ── RIS reference strip ── */
.mrs-ris-ref {
padding: 8px 14px;
background: #E6F1FB;
border-bottom: 1px solid #B5D4F4;
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #0C447C;
}
.mrs-ris-ref i { font-size: 12px; flex-shrink: 0; }
.mrs-ris-ref strong { font-weight: 700; }
/* ── Stats row ── */
.mrs-stats-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border, #d6eaec);
border-bottom: 1px solid var(--border, #d6eaec);
}
.mrs-stat {
background: var(--card-bg, #fff);
padding: 10px 12px;
display: flex; flex-direction: column; gap: 2px;
}
.mrs-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;
}
.mrs-stat-lbl i { font-size: 11px; }
.mrs-stat-val {
font-size: 18px; font-weight: 700;
color: var(--text-dark, #1a2e35); line-height: 1.2;
}
.mrs-stat-val.blue { color: #185FA5; }
.mrs-stat-val.green { color: #166534; }
.mrs-stat-val.amber { color: #92400e; }
/* ── Condition badge ── */
.mrs-condition-badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 3px 10px; border-radius: 50px;
font-size: 11px; font-weight: 600;
}
.mrs-cond-good { background: #dcfce7; color: #166534; }
.mrs-cond-damaged { background: #fee2e2; color: #991b1b; }
.mrs-cond-partial { background: #fef3c7; color: #92400e; }
/* ── Card body ── */
.mrs-card-body {
padding: 12px 16px; flex: 1;
display: flex; flex-direction: column; gap: 8px;
}
.mrs-field-row {
display: flex; align-items: flex-start;
gap: 8px; font-size: 12px;
}
.mrs-field-lbl {
min-width: 84px; 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;
}
.mrs-field-lbl i { font-size: 11px; }
.mrs-field-val {
color: var(--text-dark, #1a2e35);
line-height: 1.5; word-break: break-word;
}
/* ── Footer ── */
.mrs-card-ft {
padding: 10px 16px 14px;
display: flex; gap: 8px;
border-top: 1px solid var(--border, #d6eaec);
}
.mrs-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;
}
.mrs-btn-approve { background: #0e7c86; color: #fff; }
.mrs-btn-approve:hover { background: #0d5c63; }
.mrs-btn-approve:disabled { opacity: .4; cursor: default; }
.mrs-btn-cancel {
background: transparent; color: #dc2626;
border: 1.5px solid #fca5a5;
}
.mrs-btn-cancel:hover { background: #fef2f2; border-color: #dc2626; }
.mrs-btn-cancel:disabled { opacity: .4; cursor: default; }
/* ── Create button (top of page) ── */
.mrs-create-fab {
display: inline-flex; align-items: center; gap: 8px;
padding: 9px 18px; border-radius: 8px; border: none;
background: #185FA5; color: #fff;
font-family: 'DM Sans', sans-serif;
font-size: .875rem; font-weight: 600;
cursor: pointer; transition: background .18s;
margin-bottom: 14px;
}
.mrs-create-fab:hover { background: #0c3d6b; }
/* ── RIS search result items ── */
.mrs-ris-result-item {
padding: 10px 14px; cursor: pointer;
border-bottom: 1px solid var(--border, #d6eaec);
font-size: 13px; transition: background .12s;
display: flex; flex-direction: column; gap: 3px;
}
.mrs-ris-result-item:last-child { border-bottom: none; }
.mrs-ris-result-item:hover { background: var(--teal-pale, #e6f7f8); }
.mrs-ris-result-no { font-weight: 700; color: var(--teal-dark, #0d5c63); }
.mrs-ris-result-meta { font-size: 11px; color: var(--text-muted, #6b8890); }
/* ── am-field reuse ── */
.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,
searchMRSNo: "", searchRISNo: "", searchItemName: "",
searchReturnedBy: "", status: "", condition: "",
timer: null
};
// ── Selected RIS for create form ───────────────────────────────────────
let _selectedRIS = null;
// ── Elements ───────────────────────────────────────────────────────────
const grid = document.getElementById("mrs-grid");
const countEl = document.getElementById("mrs-resultCount");
const inMRSNo = document.getElementById("mrs-srchMRSNo");
const inRISNo = document.getElementById("mrs-srchRISNo");
const inItem = document.getElementById("mrs-srchItemName");
const inRby = document.getElementById("mrs-srchReturnedBy");
const inSize = document.getElementById("mrs-pageSize");
const statusWrap = document.getElementById("mrs-statusWrap");
const conditionWrap = document.getElementById("mrs-conditionWrap");
if (!grid || !statusWrap) {
console.error("MRS tab init failed — missing elements.");
return;
}
// ── Status dropdown (static) ───────────────────────────────────────────
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" });
statusDropdown.setItems(["Draft", "Approved", "Cancelled"]);
// ── Condition dropdown (static) ────────────────────────────────────────
const condDropdown = H.initSearchDropdown(conditionWrap, val => {
s.condition = val;
s.page = 1;
fetchData();
}, { cssPrefix: "inv-dep", allLabel: "All Conditions",
allIcon: "fas fa-th-large", itemIcon: "fas fa-tag" });
condDropdown.setItems(["Good", "Damaged", "Partial"]);
// ── Search inputs ──────────────────────────────────────────────────────
[inMRSNo, inRISNo, inItem, inRby].forEach(el => {
if (!el) return;
el.addEventListener("input", () => {
clearTimeout(s.timer);
s.timer = setTimeout(() => {
s.searchMRSNo = inMRSNo?.value.trim() ?? "";
s.searchRISNo = inRISNo?.value.trim() ?? "";
s.searchItemName = inItem?.value.trim() ?? "";
s.searchReturnedBy = inRby?.value.trim() ?? "";
s.page = 1;
fetchData();
}, 350);
});
});
if (inSize) inSize.addEventListener("change", () => {
s.pageSize = parseInt(inSize.value, 10);
s.page = 1;
fetchData();
});
function statusToCode(label) {
return { "Draft": "0", "Approved": "1", "Cancelled": "2" }[label] ?? "";
}
// ══════════════════════════════════════════════════════════════════════
// FETCH MRS LIST
// ══════════════════════════════════════════════════════════════════════
async function fetchData() {
const g = document.getElementById("mrs-grid");
g.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
<div class="inv-spinner"></div><p>Loading…</p></div>`;
const p = new URLSearchParams({
searchMRSNo: s.searchMRSNo,
searchRISNo: s.searchRISNo,
searchItemName: s.searchItemName,
searchReturnedBy: s.searchReturnedBy,
status: statusToCode(s.status),
condition: s.condition,
pageNumber: s.page,
pageSize: s.pageSize,
draw: Date.now()
});
try {
const res = await fetch(`/MRSMgmt/GetMRS?${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;
renderCards(json.data ?? []);
H.renderPagination(
document.getElementById("mrs-pageButtons"),
document.getElementById("mrs-pageInfo"),
s, pg => { s.page = pg; fetchData(); }
);
const c = document.getElementById("mrs-resultCount");
if (c) c.textContent =
`${s.totalCount.toLocaleString()} result${s.totalCount !== 1 ? "s" : ""}`;
} catch (err) {
console.error("GetMRS error:", err);
document.getElementById("mrs-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("mrs-grid");
if (!data.length) {
g.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
<i class="fas fa-inbox"></i>
<p>No MRS records found.</p></div>`;
return;
}
g.innerHTML = data.map(item => buildMRSCardHtml(item)).join("");
g.querySelectorAll(".btn-mrs-approve").forEach(btn =>
btn.addEventListener("click", () =>
openApproveModal(parseInt(btn.dataset.mrsid, 10), btn.dataset))
);
g.querySelectorAll(".btn-mrs-cancel").forEach(btn =>
btn.addEventListener("click", () =>
openCancelModal(parseInt(btn.dataset.mrsid, 10), btn.dataset))
);
}
// ══════════════════════════════════════════════════════════════════════
// CARD HTML BUILDER
// ══════════════════════════════════════════════════════════════════════
function buildMRSCardHtml(item) {
const mrsId = item.mrsId ?? item.MRSId ?? 0;
const mrsNo = item.mrsNo ?? item.MRSNo ?? "—";
const risNo = item.risNo ?? item.RISNo ?? "—";
const risId = item.risId ?? item.RISId ?? 0;
const itemName = item.itemName ?? item.ItemName ?? "—";
const returnedBy = item.returnedBy ?? item.ReturnedBy ?? "—";
const qtyReturned = item.qtyReturned ?? item.QtyReturned ?? 0;
const condition = item.condition ?? item.Condition ?? "—";
const remarks = item.remarks ?? item.Remarks ?? null;
const status = item.status ?? item.Status ?? 0;
const statusLabel = item.statusLabel ?? item.StatusLabel ?? _statusLabel(status);
const createdDate = item.createdDate ?? item.CreatedDate ?? null;
const approvedBy = item.approvedBy ?? item.ApprovedBy ?? null;
const approvedDate= item.approvedDate?? item.ApprovedDate?? null;
const isDraft = status === 0;
const isApproved = status === 1;
const isCancelled = status === 2;
const statusCls = isDraft ? "mrs-status-draft"
: isApproved ? "mrs-status-approved"
: "mrs-status-cancelled";
const statusIcon = isDraft ? "fas fa-clock"
: isApproved ? "fas fa-check-circle"
: "fas fa-ban";
const condCls = condition === "Good" ? "mrs-cond-good"
: condition === "Damaged" ? "mrs-cond-damaged"
: "mrs-cond-partial";
const condIcon = condition === "Good" ? "fas fa-check"
: condition === "Damaged" ? "fas fa-exclamation-circle"
: "fas fa-adjust";
return `
<div class="mrs-card">
<!-- HEAD -->
<div class="mrs-card-hd">
<span class="${statusCls} mrs-status-badge">
<i class="${statusIcon}"></i> ${H.escHtml(statusLabel)}
</span>
<div class="mrs-card-no">MRS NO</div>
<div class="mrs-card-title">${H.escHtml(mrsNo)}</div>
<div class="mrs-card-meta">
<span><i class="fas fa-box"></i> ${H.escHtml(itemName)}</span>
<span><i class="fas fa-clock"></i> ${_formatDate(createdDate)}</span>
</div>
<div class="mrs-card-meta" style="margin-top:4px">
<span><i class="fas fa-user"></i> ${H.escHtml(returnedBy)}</span>
</div>
</div>
<!-- RIS REFERENCE STRIP -->
<div class="mrs-ris-ref">
<i class="fas fa-receipt"></i>
<span>Against RIS: <strong>${H.escHtml(risNo)}</strong></span>
</div>
<!-- STATS -->
<div class="mrs-stats-row">
<div class="mrs-stat">
<span class="mrs-stat-lbl">
<i class="fas fa-undo"></i> Qty Returned
</span>
<span class="mrs-stat-val blue">${qtyReturned}</span>
</div>
<div class="mrs-stat">
<span class="mrs-stat-lbl">
<i class="fas fa-tag"></i> Condition
</span>
<span class="mrs-stat-val" style="font-size:13px;padding-top:3px">
<span class="mrs-condition-badge ${condCls}">
<i class="${condIcon}"></i> ${H.escHtml(condition)}
</span>
</span>
</div>
<div class="mrs-stat">
<span class="mrs-stat-lbl">
<i class="fas fa-hashtag"></i> Status
</span>
<span class="mrs-stat-val" style="font-size:13px;padding-top:3px">
<span class="${statusCls} mrs-status-badge"
style="position:static;font-size:10px">
${H.escHtml(statusLabel)}
</span>
</span>
</div>
</div>
<!-- BODY -->
<div class="mrs-card-body">
${remarks ? `
<div class="mrs-field-row">
<span class="mrs-field-lbl">
<i class="fas fa-sticky-note"></i> Remarks
</span>
<span class="mrs-field-val"
style="color:var(--text-muted,#6b8890)">
${H.escHtml(remarks)}
</span>
</div>` : ""}
${isApproved && approvedBy ? `
<div class="mrs-field-row">
<span class="mrs-field-lbl">
<i class="fas fa-user-check"></i> Approved by
</span>
<span class="mrs-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="mrs-card-ft">
${isDraft ? `
<button class="mrs-btn mrs-btn-approve btn-mrs-approve"
data-mrsid="${mrsId}"
data-mrsno="${H.escAttr(mrsNo)}"
data-itemname="${H.escAttr(itemName)}"
data-returnedby="${H.escAttr(returnedBy)}"
data-qtyreturned="${qtyReturned}"
data-condition="${H.escAttr(condition)}">
<i class="fas fa-check-circle"></i> Approve
</button>
<button class="mrs-btn mrs-btn-cancel btn-mrs-cancel"
data-mrsid="${mrsId}"
data-mrsno="${H.escAttr(mrsNo)}"
data-itemname="${H.escAttr(itemName)}"
data-returnedby="${H.escAttr(returnedBy)}"
data-qtyreturned="${qtyReturned}">
<i class="fas fa-ban"></i> Cancel
</button>` : `
<button class="mrs-btn mrs-btn-cancel btn-mrs-cancel"
${isCancelled ? "disabled" : ""}
data-mrsid="${mrsId}"
data-mrsno="${H.escAttr(mrsNo)}"
data-itemname="${H.escAttr(itemName)}"
data-returnedby="${H.escAttr(returnedBy)}"
data-qtyreturned="${qtyReturned}"
style="flex:1">
<i class="fas fa-ban"></i>
${isCancelled ? "Cancelled" : "Cancel"}
</button>`}
</div>
</div>`;
}
// ══════════════════════════════════════════════════════════════════════
// CREATE MRS MODAL
// ══════════════════════════════════════════════════════════════════════
function openCreateModal() {
_selectedRIS = null;
const overlay = document.getElementById("mrs-create-overlay");
// Reset form
document.getElementById("mrs-ris-search").value = "";
document.getElementById("mrs-create-qty").value = "";
document.getElementById("mrs-create-qty").disabled = true;
document.getElementById("mrs-create-returnedby").value = "";
document.getElementById("mrs-create-remarks").value = "";
document.getElementById("mrs-create-condition").value = "Good";
document.getElementById("mrs-selected-ris-badge").style.display = "none";
document.getElementById("mrs-ris-results").style.display = "none";
document.getElementById("mrs-create-qty-warn").style.display = "none";
document.getElementById("mrs-create-qty-hint").style.display = "none";
document.getElementById("mrs-create-submit-btn").disabled = true;
overlay.style.display = "flex";
document.getElementById("mrs-ris-search").focus();
// Wire close
const closeModal = () => { overlay.style.display = "none"; };
document.getElementById("mrs-create-close").onclick = closeModal;
document.getElementById("mrs-create-cancel-btn").onclick = closeModal;
overlay.onclick = e => { if (e.target === overlay) closeModal(); };
// RIS search input with debounce
let risSearchTimer;
document.getElementById("mrs-ris-search").oninput = () => {
clearTimeout(risSearchTimer);
risSearchTimer = setTimeout(_searchRIS, 300);
};
// Qty validation
document.getElementById("mrs-create-qty").oninput = _validateCreateQty;
// Submit
const submitBtn = document.getElementById("mrs-create-submit-btn");
const newSubmit = submitBtn.cloneNode(true);
submitBtn.replaceWith(newSubmit);
newSubmit.addEventListener("click", _handleCreateSubmit);
}
// RIS live search
async function _searchRIS() {
const q = document.getElementById("mrs-ris-search").value.trim();
const results = document.getElementById("mrs-ris-results");
if (!q) { results.style.display = "none"; return; }
results.style.display = "block";
results.innerHTML = `<div style="padding:10px 14px;font-size:12px;
color:var(--text-muted,#6b8890)">
<i class="fas fa-spinner fa-spin"></i> Searching…</div>`;
try {
const res = await fetch(
`/RISMgmt/GetRIS?searchRISNo=${encodeURIComponent(q)}&status=1&pageSize=10`
);
// const res = await fetch(`/RISMgmt/GetRIS?q=${encodeURIComponent(q)}`);
const json = await res.json();
const list = (json.data ?? json).data ?? json.data ?? [];
if (!list.length) {
results.innerHTML = `<div style="padding:10px 14px;font-size:12px;
color:var(--text-muted,#6b8890)">No approved RIS found.</div>`;
return;
}
results.innerHTML = list.map(r => `
<div class="mrs-ris-result-item"
data-risid="${r.risId ?? r.RISId}"
data-risno="${H.escAttr(r.risNo ?? r.RISNo ?? '')}"
data-itemname="${H.escAttr(r.itemName ?? r.ItemName ?? '')}"
data-discipline="${H.escAttr(r.disciplineName ?? r.DisciplineName ?? '')}"
data-qtyissued="${r.qtyIssued ?? r.QtyIssued ?? 0}"
data-maxreturn="${r.qtyAvailableToReturn ?? (r.qtyIssued - r.totalReturned) ?? 0}">
<span class="mrs-ris-result-no">
${H.escHtml(r.risNo ?? r.RISNo ?? '—')}
</span>
<span class="mrs-ris-result-meta">
${H.escHtml(r.itemName ?? r.ItemName ?? '—')} ·
${H.escHtml(r.disciplineName ?? r.DisciplineName ?? '—')} ·
<strong>${r.qtyAvailableToReturn ?? 0} pcs available</strong>
</span>
</div>`).join("");
results.querySelectorAll(".mrs-ris-result-item").forEach(el =>
el.addEventListener("click", () => _selectRIS(el.dataset))
);
} catch (err) {
results.innerHTML = `<div style="padding:10px 14px;font-size:12px;color:#dc2626">
Search failed.</div>`;
}
}
function _selectRIS(dataset) {
_selectedRIS = {
risId: parseInt(dataset.risid, 10),
risNo: dataset.risno,
itemName: dataset.itemname,
discipline: dataset.discipline,
qtyIssued: parseInt(dataset.qtyissued, 10),
maxReturn: parseInt(dataset.maxreturn, 10)
};
document.getElementById("mrs-ris-search").value = _selectedRIS.risNo;
document.getElementById("mrs-ris-results").style.display = "none";
// Populate badge
document.getElementById("mrs-sel-risno").textContent = _selectedRIS.risNo;
document.getElementById("mrs-sel-qtyissued").textContent = _selectedRIS.qtyIssued + " pcs";
document.getElementById("mrs-sel-maxreturn").textContent = _selectedRIS.maxReturn + " pcs";
document.getElementById("mrs-sel-itemname").textContent = _selectedRIS.itemName;
document.getElementById("mrs-sel-discipline").textContent = _selectedRIS.discipline;
const badge = document.getElementById("mrs-selected-ris-badge");
badge.style.display = "flex";
const qtyEl = document.getElementById("mrs-create-qty");
qtyEl.disabled = false;
qtyEl.max = _selectedRIS.maxReturn;
qtyEl.value = "";
qtyEl.focus();
const hint = document.getElementById("mrs-create-qty-hint");
hint.style.display = "block";
hint.textContent = `Max returnable: ${_selectedRIS.maxReturn} pcs`;
document.getElementById("mrs-create-submit-btn").disabled = false;
document.getElementById("mrs-create-subtitle").textContent =
`${_selectedRIS.risNo} — ${_selectedRIS.itemName}`;
}
function _validateCreateQty() {
const val = parseInt(document.getElementById("mrs-create-qty").value, 10);
const max = _selectedRIS?.maxReturn ?? 0;
const warn = document.getElementById("mrs-create-qty-warn");
const txt = document.getElementById("mrs-create-qty-warn-text");
const el = document.getElementById("mrs-create-qty");
const over = !isNaN(val) && val > max;
const zero = !isNaN(val) && val < 1;
txt.textContent = over ? `Cannot exceed ${max} available.`
: zero ? "Must be at least 1." : "";
warn.style.display = (over || zero) ? "flex" : "none";
el.classList.toggle("error", over || zero);
}
async function _handleCreateSubmit() {
if (!_selectedRIS) {
showToast("warning", "Please select a RIS reference.", "Required", 3000);
return;
}
const qty = parseInt(document.getElementById("mrs-create-qty").value, 10);
const returnedBy = document.getElementById("mrs-create-returnedby").value.trim();
const condition = document.getElementById("mrs-create-condition").value;
const remarks = document.getElementById("mrs-create-remarks").value.trim();
let valid = true;
if (isNaN(qty) || qty < 1 || qty > _selectedRIS.maxReturn) {
document.getElementById("mrs-create-qty").classList.add("error");
valid = false;
}
if (!returnedBy) {
document.getElementById("mrs-create-returnedby").classList.add("error");
valid = false;
}
if (!valid) {
showToast("warning", "Please fix the highlighted fields.", "Validation", 3000);
return;
}
const btn = document.getElementById("mrs-create-submit-btn");
btn.disabled = true;
btn.querySelector("i").className = "fas fa-spinner fa-spin";
try {
const res = await fetch("/MRSMgmt/CreateMRS", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
RISId: _selectedRIS.risId,
QtyReturned: qty,
ReturnedBy: returnedBy,
Condition: condition,
Remarks: remarks || null
})
});
const json = await res.json();
const d = json.data ?? json;
if (!res.ok || !d.success) {
showToast("error", d.message ?? "Could not create MRS.", "Failed", 4000);
return;
}
showToast("success", d.message ?? "MRS created successfully.", "Done!", 3500);
document.getElementById("mrs-create-overlay").style.display = "none";
fetchData();
} catch (err) {
console.error("CreateMRS error:", err);
showToast("error", "Request failed. Please try again.", "Error", 4000);
} finally {
if (document.getElementById("mrs-create-overlay").style.display !== "none") {
btn.disabled = false;
btn.querySelector("i").className = "fas fa-file-import";
}
}
}
// ══════════════════════════════════════════════════════════════════════
// APPROVE MRS MODAL
// ══════════════════════════════════════════════════════════════════════
function openApproveModal(mrsId, dataset) {
const overlay = document.getElementById("mrs-approve-overlay");
document.getElementById("mrs-approve-subtitle").textContent =
`${dataset.mrsno} — ${dataset.itemname ?? ""}`;
document.getElementById("mrs-approve-detail").innerHTML = `
<div class="am-field">
<span class="am-lbl"><i class="fas fa-hashtag"></i> MRS No</span>
<span class="am-val">${H.escHtml(dataset.mrsno)}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-undo"></i> Qty Returned</span>
<span class="am-val">${dataset.qtyreturned ?? 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> Returned By</span>
<span class="am-val">${H.escHtml(dataset.returnedby ?? "—")}</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-tag"></i> Condition</span>
<span class="am-val">${H.escHtml(dataset.condition ?? "—")}</span>
</div>`;
overlay.style.display = "flex";
const closeModal = () => { overlay.style.display = "none"; };
const confirmBtn = document.getElementById("mrs-approve-confirm-btn");
const cancelBtn = document.getElementById("mrs-approve-cancel-btn");
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("/MRSMgmt/ApproveMRS", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mrsId })
});
const json = await res.json();
const d = json.data ?? json;
if (!res.ok || !d.success) {
showToast("error", d.message ?? "Could not approve MRS.", "Failed", 4000);
return;
}
showToast("success", d.message ?? "MRS approved.", "Approved!", 3500);
closeModal();
fetchData();
} catch (err) {
console.error("ApproveMRS error:", err);
showToast("error", "Request failed.", "Error", 4000);
} finally {
newConfirm.disabled = false;
newConfirm.innerHTML = `<i class="fas fa-check"></i> Approve MRS`;
}
});
}
// ══════════════════════════════════════════════════════════════════════
// CANCEL MRS MODAL
// ══════════════════════════════════════════════════════════════════════
function openCancelModal(mrsId, dataset) {
const overlay = document.getElementById("mrs-cancel-overlay");
const reasonEl = document.getElementById("mrs-cancel-reason");
reasonEl.value = "";
reasonEl.classList.remove("error");
document.getElementById("mrs-cancel-subtitle").textContent =
`${dataset.mrsno} — ${dataset.itemname ?? ""}`;
document.getElementById("mrs-cancel-detail").innerHTML = `
<div class="am-field">
<span class="am-lbl"><i class="fas fa-hashtag"></i> MRS No</span>
<span class="am-val" style="color:#991b1b">
${H.escHtml(dataset.mrsno)}
</span>
</div>
<div class="am-field">
<span class="am-lbl"><i class="fas fa-undo"></i> Qty Returned</span>
<span class="am-val" style="color:#991b1b">
${dataset.qtyreturned ?? 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> Returned By</span>
<span class="am-val">${H.escHtml(dataset.returnedby ?? "—")}</span>
</div>`;
overlay.style.display = "flex";
const closeModal = () => { overlay.style.display = "none"; };
const confirmBtn = document.getElementById("mrs-cancel-confirm-btn");
const dismissBtn = document.getElementById("mrs-cancel-dismiss-btn");
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("/MRSMgmt/CancelMRS", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mrsId, reason })
});
const json = await res.json();
const d = json.data ?? json;
if (!res.ok || !d.success) {
showToast("error", d.message ?? "Could not cancel MRS.", "Failed", 4000);
return;
}
showToast("success", d.message ?? "MRS cancelled.", "Cancelled", 3500);
closeModal();
fetchData();
} catch (err) {
console.error("CancelMRS error:", err);
showToast("error", "Request failed.", "Error", 4000);
} finally {
newConfirm.disabled = false;
newConfirm.innerHTML = `<i class="fas fa-ban"></i> Cancel MRS`;
}
});
}
// ══════════════════════════════════════════════════════════════════════
// CREATE FAB — inject above the filter bar
// ══════════════════════════════════════════════════════════════════════
(function injectCreateButton() {
const filterBar = document.querySelector(".inv-filters");
if (!filterBar) return;
const fab = document.createElement("button");
fab.className = "mrs-create-fab";
fab.innerHTML = `<i class="fas fa-plus"></i> New Material Return Slip`;
fab.addEventListener("click", openCreateModal);
filterBar.parentNode.insertBefore(fab, filterBar);
})();
// ══════════════════════════════════════════════════════════════════════
// 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>