558 lines
25 KiB
Plaintext
558 lines
25 KiB
Plaintext
@{
|
||
ViewData["Title"] = "My IT Requests";
|
||
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>
|
||
.container-outer {
|
||
max-width: 1300px;
|
||
margin: auto;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.table-container {
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
box-shadow: 0 6px 18px rgba(0,0,0,.06);
|
||
padding: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.filters .form-label {
|
||
font-size: 12px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.nav-tabs .badge {
|
||
margin-left: 6px;
|
||
}
|
||
|
||
.empty {
|
||
text-align: center;
|
||
padding: 18px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.empty i {
|
||
display: block;
|
||
font-size: 22px;
|
||
margin-bottom: 6px;
|
||
opacity: .7;
|
||
}
|
||
|
||
.skeleton {
|
||
position: relative;
|
||
background: #f1f5f9;
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
height: 28px;
|
||
}
|
||
|
||
.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%);
|
||
}
|
||
}
|
||
|
||
.pillbar button {
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.section-title {
|
||
font-weight: 700;
|
||
font-size: 16px;
|
||
margin: 0 0 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.section-title .hint {
|
||
font-weight: 500;
|
||
color: #6b7280;
|
||
font-size: 12px;
|
||
}
|
||
</style>
|
||
|
||
<div id="myReqApp" class="container-outer">
|
||
<h3 class="mb-4 fw-bold"></h3>
|
||
|
||
<!-- Filters -->
|
||
<div class="row mb-3 align-items-end filters">
|
||
<div class="col-md-auto me-3">
|
||
<label class="form-label">Month</label>
|
||
<select class="form-control form-control-sm" v-model.number="selectedMonth" @@change="fetchData">
|
||
<option v-for="(m, i) in months" :key="i" :value="i + 1">{{ m }}</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-auto">
|
||
<label class="form-label">Year</label>
|
||
<select class="form-control form-control-sm" v-model.number="selectedYear" @@change="fetchData">
|
||
<option v-for="y in years" :key="y" :value="y">{{ y }}</option>
|
||
</select>
|
||
</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>
|
||
|
||
<!-- ===================== TABLE 1: MAIN REQUESTS (Draft/Pending/Approved/Rejected/Cancelled) ===================== -->
|
||
<div class="table-container table-responsive">
|
||
<ul class="nav nav-tabs mb-3">
|
||
<li class="nav-item"><a class="nav-link" :class="{ active: activeTab === 'Draft' }" href="#" @@click.prevent="switchTab('Draft')">Draft <span class="badge bg-info">{{ counts.draft }}</span></a></li>
|
||
<li class="nav-item"><a class="nav-link" :class="{ active: activeTab === 'Pending' }" href="#" @@click.prevent="switchTab('Pending')">Pending <span class="badge bg-warning text-dark">{{ counts.pending }}</span></a></li>
|
||
<li class="nav-item"><a class="nav-link" :class="{ active: activeTab === 'Approved' }" href="#" @@click.prevent="switchTab('Approved')">Approved <span class="badge bg-success">{{ counts.approved }}</span></a></li>
|
||
<li class="nav-item"><a class="nav-link" :class="{ active: activeTab === 'Rejected' }" href="#" @@click.prevent="switchTab('Rejected')">Rejected <span class="badge bg-danger">{{ counts.rejected }}</span></a></li>
|
||
<li class="nav-item"><a class="nav-link" :class="{ active: activeTab === 'Cancelled' }" href="#" @@click.prevent="switchTab('Cancelled')">Cancelled <span class="badge bg-secondary">{{ counts.cancelled }}</span></a></li>
|
||
</ul>
|
||
|
||
<table class="table table-bordered table-sm table-striped align-middle text-center">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>Department</th>
|
||
<th>Company</th>
|
||
<th>Required Date</th>
|
||
<th>Date Submitted</th>
|
||
<th style="width:260px;">Action</th>
|
||
</tr>
|
||
</thead>
|
||
|
||
<tbody v-if="busy">
|
||
<tr v-for="n in 5" :key="'sk-main-'+n">
|
||
<td colspan="5"><div class="skeleton"></div></td>
|
||
</tr>
|
||
</tbody>
|
||
|
||
<tbody v-else>
|
||
<tr v-for="r in paginatedData" :key="'row-'+activeTab+'-'+r.statusId">
|
||
<td>{{ r.departmentName }}</td>
|
||
<td>{{ r.companyName }}</td>
|
||
<td>{{ fmtDate(r.requiredDate) }}</td>
|
||
<td>{{ fmtDateTime(r.submitDate) }}</td>
|
||
<td>
|
||
<div class="d-flex justify-content-center align-items-center">
|
||
<!-- View: Draft -> Edit page, others -> RequestReview -->
|
||
<button class="btn btn-primary btn-sm me-1" @@click="view(r)">View</button>
|
||
|
||
<!-- Cancel: Draft only (server enforces final business rules) -->
|
||
<button v-if="activeTab==='Draft'"
|
||
class="btn btn-outline-danger btn-sm"
|
||
:disabled="cancellingId === r.itRequestId"
|
||
@@click="cancel(r.itRequestId)">
|
||
<span v-if="cancellingId === r.itRequestId" class="spinner-border spinner-border-sm me-1"></span>
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr v-if="!paginatedData.length" key="empty-main">
|
||
<td colspan="5" class="empty"><i class="bi bi-inboxes"></i> No {{ activeTab.toLowerCase() }} requests</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination (Main) -->
|
||
<div class="d-flex justify-content-between align-items-center mt-2" v-if="filteredData.length">
|
||
<small class="text-muted">
|
||
Showing {{ (currentPage - 1) * itemsPerPage + 1 }} – {{ Math.min(currentPage * itemsPerPage, filteredData.length) }}
|
||
of {{ filteredData.length }}
|
||
</small>
|
||
|
||
<div class="d-flex align-items-center gap-2">
|
||
<div class="text-muted">Rows</div>
|
||
<select class="form-select form-select-sm" style="width:90px" v-model.number="itemsPerPage" @@change="currentPage=1">
|
||
<option :value="5">5</option>
|
||
<option :value="10">10</option>
|
||
<option :value="20">20</option>
|
||
<option :value="50">50</option>
|
||
</select>
|
||
<div class="btn-group">
|
||
<button class="btn btn-outline-secondary btn-sm" :disabled="currentPage===1" @@click="currentPage--">Prev</button>
|
||
<button class="btn btn-outline-secondary btn-sm" :disabled="currentPage * itemsPerPage >= filteredData.length" @@click="currentPage++">Next</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== TABLE 2: SECTION B (SECOND TABLE) ===================== -->
|
||
<div class="table-container table-responsive">
|
||
<div class="section-title">
|
||
<span><i class="bi bi-clipboard-check"></i> Section B</span>
|
||
<span class="hint">Requests with overall status Approved/Completed</span>
|
||
</div>
|
||
|
||
<!-- Section B sub-filters + callout -->
|
||
<div class="d-flex align-items-center justify-content-between flex-wrap mb-2">
|
||
<div class="pillbar">
|
||
<span class="text-muted me-2">Show:</span>
|
||
<button type="button" class="btn btn-sm"
|
||
:class="sbSubTab==='need' ? 'btn-primary' : 'btn-outline-secondary'"
|
||
@@click="setSbSubTab('need')">
|
||
Your Acceptance <span class="badge bg-light text-dark ms-1">{{ sbCount.need }}</span>
|
||
</button>
|
||
<button type="button" class="btn btn-sm"
|
||
:class="sbSubTab==='waiting' ? 'btn-primary' : 'btn-outline-secondary'"
|
||
@@click="setSbSubTab('waiting')">
|
||
Waiting IT <span class="badge bg-light text-dark ms-1">{{ sbCount.waiting }}</span>
|
||
</button>
|
||
<button type="button" class="btn btn-sm"
|
||
:class="sbSubTab==='notstarted' ? 'btn-primary' : 'btn-outline-secondary'"
|
||
@@click="setSbSubTab('notstarted')">
|
||
Not Started <span class="badge bg-light text-dark ms-1">{{ sbCount.notStarted }}</span>
|
||
</button>
|
||
<button type="button" class="btn btn-sm"
|
||
:class="sbSubTab==='complete' ? 'btn-primary' : 'btn-outline-secondary'"
|
||
@@click="setSbSubTab('complete')">
|
||
Complete <span class="badge bg-light text-dark ms-1">{{ sbCount.complete }}</span>
|
||
</button>
|
||
</div>
|
||
<div v-if="sbCount.need > 0" class="alert alert-warning py-1 px-2 m-0">
|
||
You have {{ sbCount.need }} Section B {{ sbCount.need===1 ? 'item' : 'items' }} that need your acceptance.
|
||
</div>
|
||
</div>
|
||
|
||
<table class="table table-bordered table-sm table-striped align-middle text-center">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>Department</th>
|
||
<th>Company</th>
|
||
<th>Section B Status</th>
|
||
<th style="width:360px;">Action</th>
|
||
</tr>
|
||
</thead>
|
||
|
||
<!-- Skeleton while meta loads -->
|
||
<tbody v-if="busy && !sbLoaded">
|
||
<tr v-for="n in 4" :key="'sk-sb-'+n">
|
||
<td colspan="4"><div class="skeleton"></div></td>
|
||
</tr>
|
||
</tbody>
|
||
|
||
<!-- Section B rows -->
|
||
<tbody v-else>
|
||
<tr v-for="r in sectionBPage" :key="'sb-'+r.statusId">
|
||
<td>{{ r.departmentName }}</td>
|
||
<td>{{ r.companyName }}</td>
|
||
<td>
|
||
<span class="badge"
|
||
:class="r.sb.itAccepted && r.sb.requestorAccepted ? 'bg-success'
|
||
: (r.sb.saved ? 'bg-warning text-dark' : 'bg-secondary')">
|
||
<template v-if="r.sb.itAccepted && r.sb.requestorAccepted">Approved</template>
|
||
<template v-else-if="r.sb.saved && !r.sb.requestorAccepted">Your Acceptance</template>
|
||
<template v-else-if="r.sb.saved && r.sb.requestorAccepted && !r.sb.itAccepted">Waiting IT</template>
|
||
<template v-else>Not Started</template>
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<!-- ACTION RULES BY SUBTAB -->
|
||
<div class="d-flex justify-content-center align-items-center flex-wrap" style="gap:6px;">
|
||
<!-- Always show View -->
|
||
<button class="btn btn-primary btn-sm" @@click="openSectionB(r.statusId)">View</button>
|
||
|
||
<!-- Your Acceptance tab ONLY: show Accept (Requestor) -->
|
||
<button v-if="sbSubTab==='need'"
|
||
class="btn btn-outline-dark btn-sm"
|
||
:disabled="busy || !r.sb.saved || r.sb.requestorAccepted"
|
||
@@click="acceptRequestor(r.statusId)">
|
||
Accept (Requestor)
|
||
</button>
|
||
|
||
<!-- Complete tab ONLY: show PDF -->
|
||
<button v-if="sbSubTab==='complete' && r.sb.itAccepted && r.sb.requestorAccepted"
|
||
class="btn btn-outline-secondary btn-sm"
|
||
@@click="downloadPdf(r.statusId)">
|
||
PDF
|
||
</button>
|
||
<!-- Not Started / Waiting IT: no other actions (View only) -->
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- Empty state -->
|
||
<tr v-if="sbLoaded && sectionBFiltered.length === 0">
|
||
<td colspan="4" class="empty"><i class="bi bi-inboxes"></i> No Section B items in this view</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination (Section B) -->
|
||
<div class="d-flex justify-content-between align-items-center mt-2" v-if="sectionBFiltered.length">
|
||
<small class="text-muted">
|
||
Showing {{ (sectionBPageIndex - 1) * itemsPerPage + 1 }} – {{ Math.min(sectionBPageIndex * itemsPerPage, sectionBFiltered.length) }}
|
||
of {{ sectionBFiltered.length }}
|
||
</small>
|
||
|
||
<div class="btn-group">
|
||
<button class="btn btn-outline-secondary btn-sm" :disabled="sectionBPageIndex===1" @@click="sectionBPageIndex--">Prev</button>
|
||
<button class="btn btn-outline-secondary btn-sm" :disabled="sectionBPageIndex * itemsPerPage >= sectionBFiltered.length" @@click="sectionBPageIndex++">Next</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@section Scripts {
|
||
<script>
|
||
const app = Vue.createApp({
|
||
data() {
|
||
const now = new Date();
|
||
return {
|
||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||
years: Array.from({ length: 10 }, (_, i) => now.getFullYear() - 5 + i),
|
||
selectedMonth: now.getMonth() + 1,
|
||
selectedYear: now.getFullYear(),
|
||
|
||
busy: false, error: null, allRows: [],
|
||
// MAIN table
|
||
activeTab: 'Pending',
|
||
currentPage: 1, itemsPerPage: 10,
|
||
cancellingId: 0,
|
||
|
||
// SECTION B (second table)
|
||
sbMetaMap: {}, // statusId -> { saved, requestorAccepted, itAccepted, lastEditedBy }
|
||
sbLoaded: false,
|
||
sectionBPageIndex: 1,
|
||
sbSubTab: 'need', // 'need' | 'waiting' | 'notstarted' | 'complete'
|
||
|
||
// Which overall statuses are eligible for Section B
|
||
SECTIONB_ALLOW_STATUSES: ['Approved', 'Completed']
|
||
};
|
||
},
|
||
computed: {
|
||
// Badge counts for main tabs
|
||
counts() {
|
||
const c = { draft: 0, pending: 0, approved: 0, rejected: 0, cancelled: 0 };
|
||
this.allRows.forEach(r => {
|
||
const s = (r.overallStatus || 'Pending');
|
||
if (s === 'Draft') c.draft++;
|
||
else if (s === 'Pending') c.pending++;
|
||
else if (s === 'Approved') c.approved++;
|
||
else if (s === 'Rejected') c.rejected++;
|
||
else if (s === 'Cancelled') c.cancelled++;
|
||
});
|
||
return c;
|
||
},
|
||
|
||
// MAIN table filtering & pagination
|
||
filteredData() {
|
||
const tab = this.activeTab;
|
||
return this.allRows.filter(r => (r.overallStatus || 'Pending') === tab);
|
||
},
|
||
paginatedData() {
|
||
const start = (this.currentPage - 1) * this.itemsPerPage;
|
||
return this.filteredData.slice(start, start + this.itemsPerPage);
|
||
},
|
||
|
||
// Build Section B candidate rows and sort by priority
|
||
sectionBRows() {
|
||
const rows = this.allRows
|
||
.filter(r => this.SECTIONB_ALLOW_STATUSES.includes(r.overallStatus || ''))
|
||
.map(r => ({
|
||
...r,
|
||
sb: this.sbMetaMap[r.statusId] || { saved: false, requestorAccepted: false, itAccepted: false, lastEditedBy: null }
|
||
}));
|
||
const rank = (x) => {
|
||
if (x.sb.itAccepted && x.sb.requestorAccepted) return 3; // complete (lowest priority)
|
||
if (!x.sb.saved) return 1; // not started
|
||
if (x.sb.saved && x.sb.requestorAccepted && !x.sb.itAccepted) return 2; // waiting IT
|
||
if (x.sb.saved && !x.sb.requestorAccepted) return 4; // needs requestor (highest)
|
||
return 0;
|
||
};
|
||
return rows.slice().sort((a, b) => {
|
||
const rb = rank(b) - rank(a);
|
||
if (rb !== 0) return rb;
|
||
return new Date(b.submitDate) - new Date(a.submitDate);
|
||
});
|
||
},
|
||
|
||
// Section B counts for pills
|
||
sbCount() {
|
||
const c = { need: 0, waiting: 0, notStarted: 0, complete: 0 };
|
||
this.sectionBRows.forEach(r => {
|
||
const st = this.sbStageOf(r.sb);
|
||
if (st === 'need') c.need++;
|
||
else if (st === 'waiting') c.waiting++;
|
||
else if (st === 'notstarted') c.notStarted++;
|
||
else if (st === 'complete') c.complete++;
|
||
});
|
||
return c;
|
||
},
|
||
|
||
// Apply Section B sub-filter & pagination
|
||
sectionBFiltered() {
|
||
const want = this.sbSubTab; // 'need'|'waiting'|'notstarted'|'complete'
|
||
return this.sectionBRows.filter(r => this.sbStageOf(r.sb) === want);
|
||
},
|
||
sectionBPage() {
|
||
const start = (this.sectionBPageIndex - 1) * this.itemsPerPage;
|
||
return this.sectionBFiltered.slice(start, start + this.itemsPerPage);
|
||
}
|
||
},
|
||
methods: {
|
||
// MAIN table
|
||
switchTab(tab) {
|
||
this.activeTab = tab;
|
||
this.currentPage = 1;
|
||
},
|
||
|
||
async fetchData() {
|
||
try {
|
||
this.busy = true; this.error = null;
|
||
const y = this.selectedYear, m = this.selectedMonth;
|
||
|
||
// Inclusive month range (UTC)
|
||
const from = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0));
|
||
const to = new Date(Date.UTC(y, m, 0, 23, 59, 59, 999));
|
||
|
||
const params = new URLSearchParams();
|
||
params.set('from', from.toISOString());
|
||
params.set('to', to.toISOString());
|
||
params.set('page', '1');
|
||
params.set('pageSize', '500');
|
||
|
||
const res = await fetch(`/ItRequestAPI/myRequests?${params.toString()}`);
|
||
if (!res.ok) throw new Error(`Load failed (${res.status})`);
|
||
const data = await res.json();
|
||
|
||
this.allRows = (data.data || []).map(x => ({
|
||
itRequestId: x.itRequestId,
|
||
statusId: x.statusId,
|
||
departmentName: x.departmentName,
|
||
companyName: x.companyName,
|
||
requiredDate: x.requiredDate,
|
||
submitDate: x.submitDate,
|
||
overallStatus: x.overallStatus || 'Pending'
|
||
}));
|
||
|
||
// Reset Section B cache and load fresh meta
|
||
this.sbMetaMap = {};
|
||
this.sbLoaded = false;
|
||
await this.loadSectionBMeta();
|
||
|
||
} catch (e) {
|
||
this.error = e.message || 'Failed to load.';
|
||
} finally {
|
||
this.busy = false;
|
||
}
|
||
},
|
||
|
||
fmtDate(d) { if (!d) return ''; const dt = new Date(d); return isNaN(dt) ? d : dt.toLocaleDateString(); },
|
||
fmtDateTime(d) { if (!d) return ''; const dt = new Date(d); return isNaN(dt) ? d : dt.toLocaleString(); },
|
||
|
||
view(row) {
|
||
if (this.activeTab === 'Draft') {
|
||
window.location.href = `/IT/ApprovalDashboard/Edit?statusId=${row.statusId}`;
|
||
} else {
|
||
window.location.href = `/IT/ApprovalDashboard/RequestReview?statusId=${row.statusId}`;
|
||
}
|
||
},
|
||
|
||
async cancel(requestId) {
|
||
if (!confirm('Cancel this request? This cannot be undone.')) return;
|
||
this.cancellingId = requestId;
|
||
try {
|
||
const res = await fetch('/ItRequestAPI/cancel', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ requestId, reason: 'User requested cancellation' })
|
||
});
|
||
const data = await res.json().catch(() => ({}));
|
||
if (!res.ok) throw new Error(data?.message || 'Cancel failed');
|
||
await this.fetchData();
|
||
} catch (e) {
|
||
alert('Error: ' + (e.message || e));
|
||
} finally {
|
||
this.cancellingId = 0;
|
||
}
|
||
},
|
||
|
||
// ========= Section B (second table) =========
|
||
openSectionB(statusId) {
|
||
const here = window.location.pathname + window.location.search + window.location.hash;
|
||
const returnUrl = encodeURIComponent(here);
|
||
window.location.href = `/IT/ApprovalDashboard/SectionB?statusId=${statusId}&returnUrl=${returnUrl}`;
|
||
},
|
||
|
||
async acceptRequestor(statusId) {
|
||
try {
|
||
this.busy = true;
|
||
const res = await fetch('/ItRequestAPI/sectionB/accept', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ statusId, by: 'REQUESTOR' })
|
||
});
|
||
const j = await res.json().catch(() => ({}));
|
||
if (!res.ok) throw new Error(j.message || 'Accept failed');
|
||
|
||
// refresh only this row's meta
|
||
await this.loadSectionBMeta([statusId]);
|
||
} catch (e) {
|
||
alert(e.message || 'Action failed');
|
||
} finally {
|
||
this.busy = false;
|
||
}
|
||
},
|
||
|
||
downloadPdf(statusId) { window.open(`/ItRequestAPI/sectionB/pdf?statusId=${statusId}`, '_blank'); },
|
||
|
||
// Derive requestor-centric stage
|
||
sbStageOf(sb) {
|
||
if (sb.itAccepted && sb.requestorAccepted) return 'complete';
|
||
if (sb.saved && !sb.requestorAccepted) return 'need';
|
||
if (sb.saved && sb.requestorAccepted && !sb.itAccepted) return 'waiting';
|
||
return 'notstarted';
|
||
},
|
||
|
||
// Load meta for all eligible Section B rows (or subset)
|
||
async loadSectionBMeta(whichStatusIds = null) {
|
||
try {
|
||
const targets = (whichStatusIds && whichStatusIds.length)
|
||
? whichStatusIds
|
||
: this.allRows
|
||
.filter(r => this.SECTIONB_ALLOW_STATUSES.includes(r.overallStatus || ''))
|
||
.map(r => r.statusId);
|
||
|
||
if (!targets.length) { this.sbLoaded = true; return; }
|
||
|
||
for (const sid of targets) {
|
||
const res = await fetch(`/ItRequestAPI/sectionB/meta?statusId=${sid}`);
|
||
if (!res.ok) continue;
|
||
const j = await res.json().catch(() => ({}));
|
||
this.sbMetaMap[sid] = {
|
||
saved: !!j.sectionB?.saved,
|
||
requestorAccepted: !!j.requestorAccepted,
|
||
itAccepted: !!j.itAccepted,
|
||
lastEditedBy: j.sectionB?.lastEditedBy || null
|
||
};
|
||
}
|
||
this.sbLoaded = true;
|
||
} catch {
|
||
this.sbLoaded = true;
|
||
}
|
||
},
|
||
|
||
setSbSubTab(tab) {
|
||
this.sbSubTab = tab;
|
||
this.sectionBPageIndex = 1;
|
||
}
|
||
},
|
||
mounted() { this.fetchData(); }
|
||
});
|
||
app.mount('#myReqApp');
|
||
</script>
|
||
}
|