1273 lines
59 KiB
Plaintext
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>
|