633 lines
26 KiB
Plaintext
633 lines
26 KiB
Plaintext
@{
|
|
ViewData["Title"] = "IT Request Review";
|
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
}
|
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
|
|
|
<style>
|
|
:root{
|
|
--card-radius:16px;
|
|
--soft-shadow:0 8px 24px rgba(0,0,0,.08);
|
|
--soft-border:#eef2f6;
|
|
--chip-pending:#ffe599; /* soft amber */
|
|
--chip-approved:#b7e1cd; /* soft green */
|
|
--chip-rejected:#f8b4b4; /* soft red */
|
|
--text-muted:#6b7280;
|
|
}
|
|
|
|
/* Shell */
|
|
#reviewApp{
|
|
max-width: 1200px;
|
|
margin: auto;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* Page header */
|
|
.page-head{
|
|
display:flex; align-items:center; justify-content:space-between;
|
|
gap:1rem; margin-bottom:1rem;
|
|
}
|
|
.page-title{
|
|
display:flex; align-items:center; gap:.75rem; margin:0;
|
|
font-weight:700; letter-spacing:.2px;
|
|
}
|
|
.subtle{
|
|
color:var(--text-muted);
|
|
font-weight:500;
|
|
}
|
|
|
|
/* Card */
|
|
.ui-card{
|
|
background:#fff; border-radius:var(--card-radius);
|
|
box-shadow:var(--soft-shadow); border:1px solid var(--soft-border);
|
|
overflow:hidden; margin-bottom:18px;
|
|
}
|
|
.ui-card-head{
|
|
display:flex; align-items:center; justify-content:space-between;
|
|
padding:14px 18px; border-bottom:1px solid var(--soft-border);
|
|
background:linear-gradient(180deg,#fbfdff, #f7fafc);
|
|
}
|
|
.ui-card-head h6{ margin:0; font-weight:700; color:#0b5ed7; }
|
|
.ui-card-body{ padding:16px 18px; }
|
|
|
|
/* Requester grid */
|
|
.req-grid{
|
|
display:grid; grid-template-columns:repeat(2,1fr);
|
|
gap:10px 18px;
|
|
}
|
|
@@media (max-width:768px){ .req-grid{ grid-template-columns:1fr; } }
|
|
.req-line b{ color:#111827; }
|
|
.req-line span{ color:var(--text-muted); }
|
|
|
|
/* Chips */
|
|
.chip{
|
|
display:inline-flex; align-items:center; gap:.4rem;
|
|
padding:.3rem .6rem; border-radius:999px; font-weight:600; font-size:12px;
|
|
border:1px solid rgba(0,0,0,.05);
|
|
}
|
|
.chip i{ font-size:14px; }
|
|
.chip-pending{ background:var(--chip-pending); }
|
|
.chip-approved{ background:var(--chip-approved); }
|
|
.chip-rejected{ background:var(--chip-rejected); }
|
|
.chip-muted{ background:#e5e7eb; }
|
|
|
|
/* Tables */
|
|
.nice-table{
|
|
width:100%; border-collapse:separate; border-spacing:0;
|
|
overflow:hidden; border-radius:12px; border:1px solid var(--soft-border);
|
|
}
|
|
.nice-table thead th{
|
|
background:#f3f6fb; color:#334155; font-weight:700; font-size:12px;
|
|
text-transform:uppercase; letter-spacing:.4px; border-bottom:1px solid var(--soft-border);
|
|
}
|
|
.nice-table th, .nice-table td{ padding:10px 12px; vertical-align:middle; }
|
|
.nice-table tbody tr + tr td{ border-top:1px solid #f1f5f9; }
|
|
.nice-table tbody tr:hover{ background:#fafcff; }
|
|
|
|
/* Boolean badges in table */
|
|
.yes-badge, .no-badge{
|
|
display:inline-block; padding:.25rem .5rem; font-size:12px; font-weight:700;
|
|
border-radius:999px;
|
|
}
|
|
.yes-badge{ background:#e7f7ed; color:#166534; border:1px solid #c7ecd3;}
|
|
.no-badge{ background:#eef2f7; color:#334155; border:1px solid #e1e7ef;}
|
|
|
|
/* Empty state */
|
|
.empty{
|
|
text-align:center; padding:18px; color:var(--text-muted);
|
|
}
|
|
.empty i{ display:block; font-size:22px; margin-bottom:6px; opacity:.7; }
|
|
|
|
/* Skeletons */
|
|
.skeleton{ position:relative; background:#f1f5f9; overflow:hidden; border-radius:6px; }
|
|
.skeleton::after{
|
|
content:""; position:absolute; inset:0;
|
|
background:linear-gradient(90deg, transparent, rgba(255,255,255,.6), transparent);
|
|
animation: shimmer 1.2s infinite;
|
|
transform: translateX(-100%);
|
|
}
|
|
@@keyframes shimmer{
|
|
0%{ transform:translateX(-100%); }
|
|
100%{ transform:translateX(100%); }
|
|
}
|
|
|
|
/* Sticky action bar */
|
|
.action-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,.8);
|
|
backdrop-filter: blur(6px);
|
|
border:1px solid var(--soft-border); box-shadow:var(--soft-shadow);
|
|
margin-top:8px;
|
|
}
|
|
|
|
/* Soft buttons */
|
|
.btn-soft{
|
|
border-radius:10px; padding:.55rem .9rem; font-weight:700; letter-spacing:.2px;
|
|
border:1px solid transparent; box-shadow:0 2px 8px rgba(0,0,0,.06);
|
|
}
|
|
.btn-approve{ background:#22c55e; color:#fff; }
|
|
.btn-approve:hover{ background:#16a34a; }
|
|
.btn-reject{ background:#ef4444; color:#fff; }
|
|
.btn-reject:hover{ background:#dc2626; }
|
|
.btn-disabled{ background:#e5e7eb; color:#6b7280; cursor:not-allowed; }
|
|
</style>
|
|
|
|
<div id="reviewApp">
|
|
<!-- Header -->
|
|
<div class="page-head">
|
|
<h3 class="page-title">
|
|
</h3>
|
|
<div>
|
|
<span :class="overallChip.class">
|
|
<i :class="overallChip.icon"></i>
|
|
{{ overallChip.text }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Requester Info -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-person-badge"></i> Requester Info</h6>
|
|
<small class="subtle" v-if="!isLoading">Submitted: {{ formatDate(userInfo.submitDate) }}</small>
|
|
<div v-else class="skeleton" style="height:14px; width:160px;"></div>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div class="req-grid">
|
|
<div class="req-line">
|
|
<b>Name</b><br>
|
|
<span v-if="!isLoading">{{ userInfo.staffName || '—' }}</span>
|
|
<div v-else class="skeleton" style="height:14px;"></div>
|
|
</div>
|
|
<div class="req-line">
|
|
<b>Department</b><br>
|
|
<span v-if="!isLoading">{{ userInfo.departmentName || '—' }}</span>
|
|
<div v-else class="skeleton" style="height:14px;"></div>
|
|
</div>
|
|
<div class="req-line">
|
|
<b>Company</b><br>
|
|
<span v-if="!isLoading">{{ userInfo.companyName || '—' }}</span>
|
|
<div v-else class="skeleton" style="height:14px;"></div>
|
|
</div>
|
|
<div class="req-line">
|
|
<b>Designation</b><br>
|
|
<span v-if="!isLoading">{{ userInfo.designation || '—' }}</span>
|
|
<div v-else class="skeleton" style="height:14px;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hardware -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-cpu"></i> Hardware Requested</h6>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div v-if="isLoading">
|
|
<div class="skeleton" style="height:36px; margin-bottom:10px;"></div>
|
|
<div class="skeleton" style="height:36px; margin-bottom:10px;"></div>
|
|
<div class="skeleton" style="height:36px;"></div>
|
|
</div>
|
|
<div v-else>
|
|
<table class="nice-table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Category</th>
|
|
<th>Purpose</th>
|
|
<th>Justification</th>
|
|
<th>Other</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="item in hardware" :key="item.id">
|
|
<td>{{ item.category }}</td>
|
|
<td>{{ item.purpose }}</td>
|
|
<td>{{ item.justification }}</td>
|
|
<td>{{ item.otherDescription }}</td>
|
|
</tr>
|
|
<tr v-if="hardware.length === 0">
|
|
<td colspan="4" class="empty">
|
|
<i class="bi bi-inboxes"></i>
|
|
No hardware requested
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Email -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-envelope-paper"></i> Email Requests</h6>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div v-if="isLoading">
|
|
<div class="skeleton" style="height:36px; margin-bottom:10px;"></div>
|
|
<div class="skeleton" style="height:36px;"></div>
|
|
</div>
|
|
<div v-else>
|
|
<table class="nice-table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Purpose</th>
|
|
<th>Proposed Address</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="item in emails" :key="item.id">
|
|
<td>{{ item.purpose }}</td>
|
|
<td>{{ item.proposedAddress }}</td>
|
|
</tr>
|
|
<tr v-if="emails.length === 0">
|
|
<td colspan="2" class="empty">
|
|
<i class="bi bi-inboxes"></i>
|
|
No email requests
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OS Requirements -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-windows"></i> Operating System Requirements</h6>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div v-if="isLoading">
|
|
<div class="skeleton" style="height:36px;"></div>
|
|
</div>
|
|
<div v-else>
|
|
<table class="nice-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Requirement</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="item in osreqs" :key="item.id">
|
|
<td>{{ item.requirementText }}</td>
|
|
</tr>
|
|
<tr v-if="osreqs.length === 0">
|
|
<td class="empty">
|
|
<i class="bi bi-inboxes"></i>
|
|
No OS requirements
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Software -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-boxes"></i> Software Requested</h6>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div v-if="isLoading">
|
|
<div class="skeleton" style="height:36px; margin-bottom:10px;"></div>
|
|
<div class="skeleton" style="height:36px;"></div>
|
|
</div>
|
|
<div v-else>
|
|
<table class="nice-table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Bucket</th>
|
|
<th>Name</th>
|
|
<th>Other</th>
|
|
<th>Notes</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="item in software" :key="item.id">
|
|
<td>{{ item.bucket }}</td>
|
|
<td>{{ item.name }}</td>
|
|
<td>{{ item.otherName }}</td>
|
|
<td>{{ item.notes }}</td>
|
|
</tr>
|
|
<tr v-if="software.length === 0">
|
|
<td colspan="4" class="empty">
|
|
<i class="bi bi-inboxes"></i>
|
|
No software requested
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shared Permissions -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-folder-symlink"></i> Shared Folder / Permission Requests</h6>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div v-if="isLoading">
|
|
<div class="skeleton" style="height:36px;"></div>
|
|
</div>
|
|
<div v-else>
|
|
<table class="nice-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Share Name</th>
|
|
<th>Read</th>
|
|
<th>Write</th>
|
|
<th>Delete</th>
|
|
<th>Remove</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="item in sharedPerms" :key="item.id">
|
|
<td>{{ item.shareName }}</td>
|
|
<td><span :class="item.canRead ? 'yes-badge' : 'no-badge'">{{ item.canRead ? 'Yes' : 'No' }}</span></td>
|
|
<td><span :class="item.canWrite ? 'yes-badge' : 'no-badge'">{{ item.canWrite ? 'Yes' : 'No' }}</span></td>
|
|
<td><span :class="item.canDelete ? 'yes-badge' : 'no-badge'">{{ item.canDelete ? 'Yes' : 'No' }}</span></td>
|
|
<td><span :class="item.canRemove ? 'yes-badge' : 'no-badge'">{{ item.canRemove ? 'Yes' : 'No' }}</span></td>
|
|
</tr>
|
|
<tr v-if="sharedPerms.length === 0">
|
|
<td colspan="5" class="empty">
|
|
<i class="bi bi-inboxes"></i>
|
|
No shared permissions requested
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Approval Trail -->
|
|
<div class="ui-card">
|
|
<div class="ui-card-head">
|
|
<h6><i class="bi bi-flag"></i> Approval Trail</h6>
|
|
</div>
|
|
<div class="ui-card-body">
|
|
<div v-if="isLoading">
|
|
<div class="skeleton" style="height:36px; margin-bottom:10px;"></div>
|
|
<div class="skeleton" style="height:36px; margin-bottom:10px;"></div>
|
|
<div class="skeleton" style="height:36px;"></div>
|
|
</div>
|
|
<div v-else>
|
|
<table class="nice-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Stage</th>
|
|
<th>Status</th>
|
|
|
|
<th>Date</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>HOD</td>
|
|
<td><span :class="badgeChip(status.hodStatus).class"><i :class="badgeChip(status.hodStatus).icon"></i>{{ status.hodStatus || '—' }}</span></td>
|
|
|
|
<td>{{ formatDate(status.hodSubmitDate) || '—' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Group IT HOD</td>
|
|
<td><span :class="badgeChip(status.gitHodStatus).class"><i :class="badgeChip(status.gitHodStatus).icon"></i>{{ status.gitHodStatus || '—' }}</span></td>
|
|
|
|
<td>{{ formatDate(status.gitHodSubmitDate) || '—' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Finance HOD</td>
|
|
<td><span :class="badgeChip(status.finHodStatus).class"><i :class="badgeChip(status.finHodStatus).icon"></i>{{ status.finHodStatus || '—' }}</span></td>
|
|
|
|
<td>{{ formatDate(status.finHodSubmitDate) || '—' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Management</td>
|
|
<td><span :class="badgeChip(status.mgmtStatus).class"><i :class="badgeChip(status.mgmtStatus).icon"></i>{{ status.mgmtStatus || '—' }}</span></td>
|
|
|
|
<td>{{ formatDate(status.mgmtSubmitDate) || '—' }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Sticky Action Bar -->
|
|
<div class="action-bar">
|
|
<template v-if="status.canApprove">
|
|
<button class="btn-soft btn-approve" @@click="updateStatus('Approved')">
|
|
<i class="bi bi-check2-circle"></i> Approve
|
|
</button>
|
|
<button class="btn-soft btn-reject" @@click="updateStatus('Rejected')">
|
|
<i class="bi bi-x-circle"></i> Reject
|
|
</button>
|
|
</template>
|
|
<template v-else>
|
|
<button class="btn-soft btn-disabled" disabled>
|
|
<i class="bi bi-shield-lock"></i> You cannot act on this request right now
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const reviewApp = Vue.createApp({
|
|
data() {
|
|
return {
|
|
isLoading: true,
|
|
userInfo: {
|
|
staffName: "", departmentName: "", companyName: "",
|
|
designation: "", submitDate: ""
|
|
},
|
|
hardware: [],
|
|
emails: [],
|
|
osreqs: [],
|
|
software: [],
|
|
sharedPerms: [],
|
|
status: {
|
|
hodStatus: "Pending", gitHodStatus: "Pending",
|
|
finHodStatus: "Pending", mgmtStatus: "Pending",
|
|
|
|
hodSubmitDate: "", gitHodSubmitDate: "", finHodSubmitDate: "", mgmtSubmitDate: "",
|
|
overallStatus: "Pending", canApprove: false
|
|
}
|
|
};
|
|
},
|
|
computed:{
|
|
overallChip(){
|
|
const s = (this.status.overallStatus || 'Pending').toLowerCase();
|
|
if(s==='approved') return { class:'chip chip-approved', icon:'bi bi-check2-circle', text:'Overall: Approved' };
|
|
if(s==='rejected') return { class:'chip chip-rejected', icon:'bi bi-x-circle', text:'Overall: Rejected' };
|
|
return { class:'chip chip-pending', icon:'bi bi-hourglass-split', text:`Overall: ${this.status.overallStatus || 'Pending'}` };
|
|
}
|
|
},
|
|
methods: {
|
|
badgeChip(v){
|
|
const s = (v || 'Pending').toLowerCase();
|
|
if(s==='approved') return { class:'chip chip-approved', icon:'bi bi-check2' };
|
|
if(s==='rejected') return { class:'chip chip-rejected', icon:'bi bi-x-lg' };
|
|
if(s==='pending') return { class:'chip chip-pending', icon:'bi bi-hourglass' };
|
|
return { class:'chip chip-muted', icon:'bi bi-dot' };
|
|
},
|
|
|
|
// ===== Load existing (full function) =====
|
|
async loadRequest() {
|
|
this.isLoading = true;
|
|
|
|
// 1) Read statusId from URL
|
|
const params = new URLSearchParams(window.location.search);
|
|
const statusId = params.get("statusId");
|
|
if (!statusId) {
|
|
alert("Missing statusId in URL");
|
|
this.isLoading = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 2) Fetch request payload
|
|
const res = await fetch(`/ItRequestAPI/request/${statusId}`);
|
|
const ct = res.headers.get('content-type') || '';
|
|
let data, text;
|
|
|
|
if (ct.includes('application/json')) {
|
|
data = await res.json();
|
|
} else {
|
|
text = await res.text();
|
|
throw new Error(text || `HTTP ${res.status}`);
|
|
}
|
|
if (!res.ok) throw new Error(data?.message || `HTTP ${res.status}`);
|
|
|
|
console.log("RequestReview raw payload:", data);
|
|
|
|
// 3) Requester / submit metadata
|
|
// Prefer top-level data.userInfo; if absent, fall back to data.request / data.Request
|
|
const reqSrc = (data.userInfo ?? data.request ?? data.Request ?? {});
|
|
|
|
// Many APIs place submit date under status or request; pick the first available
|
|
const submittedAt =
|
|
data.status?.submitDate ?? data.status?.SubmitDate ??
|
|
reqSrc.submitDate ?? reqSrc.SubmitDate ??
|
|
data.status?.firstSubmittedAt ?? data.status?.FirstSubmittedAt ??
|
|
data.request?.firstSubmittedAt ?? data.Request?.FirstSubmittedAt ?? "";
|
|
|
|
this.userInfo = {
|
|
staffName: reqSrc.staffName ?? reqSrc.StaffName ?? "",
|
|
departmentName: reqSrc.departmentName ?? reqSrc.DepartmentName ?? "",
|
|
companyName: reqSrc.companyName ?? reqSrc.CompanyName ?? "",
|
|
designation: reqSrc.designation ?? reqSrc.Designation ?? "",
|
|
submitDate: submittedAt
|
|
};
|
|
|
|
// 4) Hardware
|
|
this.hardware = (data.hardware ?? []).map(x => ({
|
|
id: x.id ?? x.Id,
|
|
category: x.category ?? x.Category ?? "",
|
|
purpose: x.purpose ?? x.Purpose ?? "",
|
|
justification: x.justification ?? x.Justification ?? "",
|
|
otherDescription: x.otherDescription ?? x.OtherDescription ?? ""
|
|
}));
|
|
|
|
// 5) Emails
|
|
this.emails = (data.emails ?? []).map(x => ({
|
|
id: x.id ?? x.Id,
|
|
purpose: x.purpose ?? x.Purpose ?? "",
|
|
proposedAddress: x.proposedAddress ?? x.ProposedAddress ?? ""
|
|
}));
|
|
|
|
// 6) OS requirements
|
|
this.osreqs = (data.osreqs ?? data.OSReqs ?? []).map(x => ({
|
|
id: x.id ?? x.Id,
|
|
requirementText: x.requirementText ?? x.RequirementText ?? ""
|
|
}));
|
|
|
|
// 7) Software
|
|
this.software = (data.software ?? []).map(x => ({
|
|
id: x.id ?? x.Id,
|
|
bucket: x.bucket ?? x.Bucket ?? "",
|
|
name: x.name ?? x.Name ?? "",
|
|
otherName: x.otherName ?? x.OtherName ?? "",
|
|
notes: x.notes ?? x.Notes ?? ""
|
|
}));
|
|
|
|
// 8) Shared permissions
|
|
this.sharedPerms = (data.sharedPerms ?? data.sharedPermissions ?? []).map(x => ({
|
|
id: x.id ?? x.Id,
|
|
shareName: x.shareName ?? x.ShareName ?? "",
|
|
canRead: (x.canRead ?? x.CanRead) || false,
|
|
canWrite: (x.canWrite ?? x.CanWrite) || false,
|
|
canDelete: (x.canDelete ?? x.CanDelete) || false,
|
|
canRemove: (x.canRemove ?? x.CanRemove) || false
|
|
}));
|
|
|
|
// 9) Status block
|
|
this.status = {
|
|
hodStatus: data.status?.hodStatus ?? data.status?.HodStatus ?? "Pending",
|
|
gitHodStatus: data.status?.gitHodStatus ?? data.status?.GitHodStatus ?? "Pending",
|
|
finHodStatus: data.status?.finHodStatus ?? data.status?.FinHodStatus ?? "Pending",
|
|
mgmtStatus: data.status?.mgmtStatus ?? data.status?.MgmtStatus ?? "Pending",
|
|
|
|
hodSubmitDate: data.status?.hodSubmitDate ?? data.status?.HodSubmitDate ?? "",
|
|
gitHodSubmitDate: data.status?.gitHodSubmitDate ?? data.status?.GitHodSubmitDate ?? "",
|
|
finHodSubmitDate: data.status?.finHodSubmitDate ?? data.status?.FinHodSubmitDate ?? "",
|
|
mgmtSubmitDate: data.status?.mgmtSubmitDate ?? data.status?.MgmtSubmitDate ?? "",
|
|
|
|
overallStatus: data.status?.overallStatus ?? data.status?.OverallStatus ?? "Pending",
|
|
canApprove: (data.status?.canApprove ?? data.status?.CanApprove) || false
|
|
};
|
|
|
|
const os = (this.status.overallStatus || '').toLowerCase();
|
|
if (os === 'cancelled' || os === 'draft') {
|
|
this.status.canApprove = false;
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error("RequestReview fetch failed:", err);
|
|
alert(`Failed to load request: ${err.message}`);
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
},
|
|
|
|
|
|
async updateStatus(decision) {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const statusId = params.get("statusId");
|
|
try {
|
|
const res = await fetch(`/ItRequestAPI/approveReject`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ statusId: parseInt(statusId), decision: decision })
|
|
});
|
|
const ct = res.headers.get('content-type') || '';
|
|
const payload = ct.includes('application/json') ? await res.json() : { message: await res.text() };
|
|
if (!res.ok) throw new Error(payload?.message || `HTTP ${res.status}`);
|
|
|
|
// Small UX pop
|
|
const verb = decision === 'Approved' ? 'approved' : 'rejected';
|
|
alert(`Request ${verb} successfully.`);
|
|
this.loadRequest();
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert(`Failed to update status: ${e.message}`);
|
|
}
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '';
|
|
try{
|
|
// Show using local timezone, readable
|
|
return new Date(dateStr).toLocaleString();
|
|
}catch{ return dateStr; }
|
|
}
|
|
},
|
|
mounted() { this.loadRequest(); }
|
|
});
|
|
reviewApp.mount("#reviewApp");
|
|
</script>
|