322 lines
15 KiB
Plaintext
322 lines
15 KiB
Plaintext
@{
|
||
ViewData["Title"] = "Edit Section B – Asset Information";
|
||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||
// ?statusId=123
|
||
}
|
||
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
||
|
||
<style>
|
||
:root{ --card-r:16px; --soft-b:#eef2f6; --soft-s:0 8px 24px rgba(0,0,0,.08); --muted:#6b7280; }
|
||
#sbEditApp{ max-width:1100px; margin:auto; font-size:14px; }
|
||
.ui-card{ background:#fff; border:1px solid var(--soft-b); border-radius:var(--card-r); box-shadow:var(--soft-s); margin-bottom:16px; overflow:hidden; }
|
||
.ui-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 18px; border-bottom:1px solid var(--soft-b); background:linear-gradient(180deg,#fbfdff,#f7fafc); }
|
||
.ui-head h6{ margin:0; font-weight:800; color:#0b5ed7; }
|
||
.ui-body{ padding:16px 18px; }
|
||
.muted{ color:var(--muted); }
|
||
.grid2{ display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
||
@@media (max-width:768px){ .grid2{ grid-template-columns:1fr; } }
|
||
.readonly-pill{ display:inline-flex; align-items:center; gap:.4rem; padding:.28rem .6rem; border-radius:999px; background:#eef2f7; color:#334155; font-weight:700; font-size:12px; }
|
||
.sig-box{ border:1px dashed #d1d5db; border-radius:8px; padding:12px; background:#fbfbfc; }
|
||
.submit-bar{ position:sticky; bottom:12px; z-index:5; display:flex; justify-content:flex-end; gap:10px; padding:12px; border-radius:12px; background:rgba(255,255,255,.85); backdrop-filter:blur(6px); border:1px solid var(--soft-b); box-shadow:var(--soft-s); margin:6px 0 30px; }
|
||
</style>
|
||
|
||
<div id="sbEditApp">
|
||
<!-- Page head -->
|
||
<div class="d-flex align-items-center justify-content-between my-3">
|
||
<h5 class="m-0 fw-bold">Edit Section B – Asset Information</h5>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<span class="readonly-pill">Overall: {{ meta.overallStatus || '—' }}</span>
|
||
<span class="readonly-pill">Requestor: {{ meta.requestorName || '—' }}</span>
|
||
<span class="readonly-pill" v-if="meta.isItMember">You are IT</span>
|
||
<span class="readonly-pill" :class="meta.sectionBSent ? '' : 'bg-warning-subtle'">
|
||
{{ meta.sectionBSent ? 'Sent' : 'Draft' }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="error" class="alert alert-danger py-2">{{ error }}</div>
|
||
<div v-if="busy" class="alert alert-secondary py-2">Loading…</div>
|
||
|
||
<!-- Gate -->
|
||
<div v-if="!busy && meta.overallStatus !== 'Approved'" class="alert alert-warning">
|
||
Section B is available only after the request is <strong>Approved</strong>. Current status: <strong>{{ meta.overallStatus || '—' }}</strong>.
|
||
</div>
|
||
|
||
<template v-if="!busy && meta.overallStatus === 'Approved'">
|
||
<!-- Asset info -->
|
||
<div class="ui-card">
|
||
<div class="ui-head">
|
||
<h6><i class="bi bi-hdd-stack"></i> Asset Information</h6>
|
||
<span class="muted" v-if="meta.lastEditedBy">
|
||
Last edited by {{ meta.lastEditedBy }} at {{ fmtDT(meta.lastEditedAt) }}
|
||
</span>
|
||
</div>
|
||
<div class="ui-body">
|
||
<div v-if="!meta.isItMember" class="alert alert-info py-2">
|
||
You’re not in the IT Team. You can view once saved, but cannot edit.
|
||
</div>
|
||
<div v-if="meta.locked" class="alert alert-light border">
|
||
Locked for editing (sent). Acceptances can proceed in Section B page.
|
||
</div>
|
||
|
||
<div class="grid2">
|
||
<div><label class="form-label">Asset No</label>
|
||
<input class="form-control" v-model.trim="form.assetNo" :disabled="!meta.isItMember || meta.locked">
|
||
</div>
|
||
<div><label class="form-label">Machine ID</label>
|
||
<input class="form-control" v-model.trim="form.machineId" :disabled="!meta.isItMember || meta.locked">
|
||
</div>
|
||
<div><label class="form-label">IP Address</label>
|
||
<input class="form-control" v-model.trim="form.ipAddress" :disabled="!meta.isItMember || meta.locked">
|
||
</div>
|
||
<div><label class="form-label">Wired MAC Address</label>
|
||
<input class="form-control" v-model.trim="form.wiredMac" :disabled="!meta.isItMember || meta.locked">
|
||
</div>
|
||
<div><label class="form-label">Wi-Fi MAC Address</label>
|
||
<input class="form-control" v-model.trim="form.wifiMac" :disabled="!meta.isItMember || meta.locked">
|
||
</div>
|
||
<div><label class="form-label">Dial-up Account</label>
|
||
<input class="form-control" v-model.trim="form.dialupAcc" :disabled="!meta.isItMember || meta.locked">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-2">
|
||
<label class="form-label">Remarks</label>
|
||
<textarea rows="4" class="form-control" v-model.trim="form.remarks" :disabled="!meta.isItMember || meta.locked"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Acceptances (summary + IT accept button, gated by sent) -->
|
||
<div class="ui-card">
|
||
<div class="ui-head">
|
||
<h6><i class="bi bi-patch-check"></i> Acceptances</h6>
|
||
</div>
|
||
<div class="ui-body">
|
||
<div v-if="!meta.sectionBSent" class="alert alert-light border">
|
||
Send Section B first to enable acceptances.
|
||
</div>
|
||
|
||
<div class="row g-3" v-else>
|
||
<div class="col-md-6">
|
||
<div class="sig-box">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<strong>Requestor Acknowledgement</strong>
|
||
<span class="badge" :class="meta.requestorAccepted ? 'bg-success' : 'bg-secondary'">
|
||
{{ meta.requestorAccepted ? 'Accepted' : 'Pending' }}
|
||
</span>
|
||
</div>
|
||
<div class="mt-2">
|
||
<div>Name: <strong>{{ meta.requestorName || '—' }}</strong></div>
|
||
<div>Date: <strong>{{ fmtDT(meta.requestorAcceptedAt) || '—' }}</strong></div>
|
||
</div>
|
||
<div class="mt-2">
|
||
<a class="btn btn-outline-primary btn-sm"
|
||
:href="`/IT/ApprovalDashboard/SectionB?statusId=${statusId}`">
|
||
Open Section B (requestor view)
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<div class="sig-box">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<strong>Completed by (IT)</strong>
|
||
<span class="badge" :class="meta.itAccepted ? 'bg-success' : 'bg-secondary'">
|
||
{{ meta.itAccepted ? 'Accepted' : 'Pending' }}
|
||
</span>
|
||
</div>
|
||
<div class="mt-2">
|
||
<div>Name: <strong>{{ meta.itAcceptedBy || (meta.isItMember ? '(you?)' : '—') }}</strong></div>
|
||
<div>Date: <strong>{{ fmtDT(meta.itAcceptedAt) || '—' }}</strong></div>
|
||
</div>
|
||
<div class="mt-2">
|
||
<button class="btn btn-outline-primary btn-sm"
|
||
:disabled="busy || !meta.isItMember || meta.itAccepted"
|
||
@@click="accept('IT')">
|
||
IT Accept
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn btn-outline-secondary btn-sm mt-2"
|
||
:disabled="busy || !(meta.requestorAccepted && meta.itAccepted)"
|
||
@@click="downloadPdf">
|
||
Download PDF
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sticky action bar (same layout as Section B) -->
|
||
<div class="submit-bar">
|
||
<button class="btn btn-light me-auto" @@click="goBack">
|
||
<i class="bi bi-arrow-left"></i> Back
|
||
</button>
|
||
|
||
<button class="btn btn-secondary" :disabled="busy || !meta.isItMember || meta.locked" @@click="resetDraft">Reset</button>
|
||
<button class="btn btn-primary" :disabled="busy || !meta.isItMember || meta.locked" @@click="saveDraft">
|
||
<span v-if="saving" class="spinner-border spinner-border-sm me-2"></span>
|
||
Save Draft
|
||
</button>
|
||
<button class="btn btn-success" :disabled="busy || !meta.isItMember || meta.locked" @@click="sendNow">Send Now</button>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
@section Scripts{
|
||
<script>
|
||
const sbEditApp = Vue.createApp({
|
||
data(){
|
||
const url = new URL(window.location.href);
|
||
return {
|
||
statusId: Number(url.searchParams.get('statusId')) || 0,
|
||
returnUrl: url.searchParams.get('returnUrl'),
|
||
busy:false, saving:false, error:null,
|
||
meta:{
|
||
overallStatus:null, requestorName:null,
|
||
isRequestor:false, isItMember:false,
|
||
sectionBSent:false, sectionBSentAt:null,
|
||
locked:false,
|
||
lastEditedBy:null, lastEditedAt:null,
|
||
requestorAccepted:false, requestorAcceptedAt:null,
|
||
itAccepted:false, itAcceptedAt:null, itAcceptedBy:null
|
||
},
|
||
form:{ assetNo:"", machineId:"", ipAddress:"", wiredMac:"", wifiMac:"", dialupAcc:"", remarks:"" }
|
||
};
|
||
},
|
||
methods:{
|
||
|
||
goBack() {
|
||
const go = (u) => window.location.href = u;
|
||
|
||
// 1) Prefer explicit returnUrl (only if local)
|
||
if (this.returnUrl) {
|
||
try {
|
||
const u = new URL(this.returnUrl, window.location.origin);
|
||
if (u.origin === window.location.origin) { go(u.href); return; }
|
||
} catch { }
|
||
}
|
||
|
||
// 2) Else use Referer (only if local)
|
||
if (document.referrer) {
|
||
try {
|
||
const u = new URL(document.referrer);
|
||
if (u.origin === window.location.origin) { go(document.referrer); return; }
|
||
} catch { }
|
||
}
|
||
|
||
// 3) Else browser history
|
||
if (history.length > 1) { history.back(); return; }
|
||
|
||
// 4) Final fallback based on role
|
||
go(this.meta.isItMember ? '/IT/ApprovalDashboard' : '/IT/MyRequests');
|
||
},
|
||
|
||
fmtDT(d){ if(!d) return ""; const dt=new Date(d); return isNaN(dt)?"":dt.toLocaleString(); },
|
||
async load(){
|
||
try{
|
||
this.busy=true; this.error=null;
|
||
const r = await fetch(`/ItRequestAPI/sectionB/meta?statusId=${this.statusId}`);
|
||
if(!r.ok) throw new Error(`Load failed (${r.status})`);
|
||
const j = await r.json();
|
||
|
||
this.meta = {
|
||
overallStatus: j.overallStatus,
|
||
requestorName: j.requestorName,
|
||
isRequestor: j.isRequestor,
|
||
isItMember: j.isItMember,
|
||
sectionBSent: !!j.sectionBSent,
|
||
sectionBSentAt: j.sectionBSentAt,
|
||
locked: !!j.locked,
|
||
lastEditedBy: j.sectionB?.lastEditedBy || null,
|
||
lastEditedAt: j.sectionB?.lastEditedAt || null,
|
||
requestorAccepted: !!j.requestorAccepted,
|
||
requestorAcceptedAt: j.requestorAcceptedAt || null,
|
||
itAccepted: !!j.itAccepted,
|
||
itAcceptedAt: j.itAcceptedAt || null,
|
||
itAcceptedBy: j.itAcceptedBy || null
|
||
};
|
||
|
||
const s = j.sectionB || {};
|
||
this.form = {
|
||
assetNo: s.assetNo || "",
|
||
machineId: s.machineId || "",
|
||
ipAddress: s.ipAddress || "",
|
||
wiredMac: s.wiredMac || "",
|
||
wifiMac: s.wifiMac || "",
|
||
dialupAcc: s.dialupAcc || "",
|
||
remarks: s.remarks || ""
|
||
};
|
||
}catch(e){ this.error = e.message || 'Failed to load.'; }
|
||
finally{ this.busy=false; }
|
||
},
|
||
async saveDraft(){
|
||
try{
|
||
this.saving=true; this.error=null;
|
||
const res = await fetch('/ItRequestAPI/sectionB/save', {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({ statusId:this.statusId, ...this.form })
|
||
});
|
||
const j = await res.json().catch(()=> ({}));
|
||
if(!res.ok) throw new Error(j.message || `Save failed (${res.status})`);
|
||
await this.load();
|
||
}catch(e){ this.error = e.message || 'Unable to save.'; }
|
||
finally{ this.saving=false; }
|
||
},
|
||
async resetDraft(){
|
||
if(!confirm('Reset Section B to an empty draft?')) return;
|
||
try{
|
||
this.busy=true; this.error=null;
|
||
const res = await fetch('/ItRequestAPI/sectionB/reset', {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({ statusId:this.statusId })
|
||
});
|
||
const j = await res.json().catch(()=> ({}));
|
||
if(!res.ok) throw new Error(j.message || `Reset failed (${res.status})`);
|
||
await this.load();
|
||
}catch(e){ this.error = e.message || 'Unable to reset.'; }
|
||
finally{ this.busy=false; }
|
||
},
|
||
async sendNow(){
|
||
if(!confirm('Send Section B now? You will not be able to edit afterwards.')) return;
|
||
try{
|
||
this.busy=true; this.error=null;
|
||
const res = await fetch('/ItRequestAPI/sectionB/send', {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({ statusId:this.statusId })
|
||
});
|
||
const j = await res.json().catch(()=> ({}));
|
||
if(!res.ok) throw new Error(j.message || `Send failed (${res.status})`);
|
||
alert(j.message || 'Section B sent and locked for editing.');
|
||
await this.load();
|
||
}catch(e){ this.error = e.message || 'Unable to send.'; }
|
||
finally{ this.busy=false; }
|
||
},
|
||
async accept(kind){ // IT accept from here
|
||
try{
|
||
this.busy=true; this.error=null;
|
||
const res = await fetch('/ItRequestAPI/sectionB/accept', {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({ statusId:this.statusId, by: kind })
|
||
});
|
||
const j = await res.json().catch(()=> ({}));
|
||
if(!res.ok) throw new Error(j.message || `Accept failed (${res.status})`);
|
||
await this.load();
|
||
}catch(e){ this.error = e.message || 'Action failed.'; }
|
||
finally{ this.busy=false; }
|
||
},
|
||
downloadPdf(){ window.open(`/ItRequestAPI/sectionB/pdf?statusId=${this.statusId}`,'_blank'); }
|
||
},
|
||
mounted(){
|
||
if(!this.statusId){ this.error='Missing statusId in the URL.'; return; }
|
||
this.load();
|
||
}
|
||
});
|
||
sbEditApp.mount('#sbEditApp');
|
||
</script>
|
||
}
|