PSTW_CentralizeSystem/Areas/IT/Views/ApprovalDashboard/Create.cshtml
2025-11-10 10:26:57 +08:00

826 lines
34 KiB
Plaintext

@{
ViewData["Title"] = "New IT Request";
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-r: 16px;
--soft-b: #eef2f6;
--soft-s: 0 8px 24px rgba(0,0,0,.08);
--muted: #6b7280;
--ok: #16a34a;
--warn: #f59e0b;
--err: #dc2626;
}
#itFormApp {
max-width: 1100px;
margin: auto;
font-size: 14px;
}
.page-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin: 16px 0 10px;
}
.page-title {
display: flex;
align-items: center;
gap: .6rem;
margin: 0;
font-weight: 800;
letter-spacing: .2px;
}
.subtle {
color: var(--muted);
font-weight: 500;
}
.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;
}
.note {
color: var(--muted);
font-size: 12px;
}
.chip {
display: inline-flex;
align-items: center;
gap: .45rem;
padding: .25rem .6rem;
border-radius: 999px;
font-weight: 700;
font-size: 12px;
border: 1px solid rgba(0,0,0,.06);
background: #eef2f7;
color: #334155;
}
.chip i {
font-size: 14px;
}
.chip-ok {
background: #e7f7ed;
color: #166534;
border-color: #c7ecd3;
}
.chip-warn {
background: #fff7e6;
color: #92400e;
border-color: #fdebd1;
}
.form-label .req {
color: var(--err);
margin-left: .2rem;
}
.invalid-hint {
color: var(--err);
font-size: 12px;
margin-top: 4px;
}
.req-grid {
display: grid;
grid-template-columns: repeat(2,1fr);
gap: 12px 18px;
}
@@media (max-width:768px) {
.req-grid {
grid-template-columns: 1fr;
}
}
.mini-table {
border: 1px solid var(--soft-b);
border-radius: 12px;
overflow: hidden;
}
.mini-head {
background: #f3f6fb;
padding: 10px 12px;
font-weight: 700;
color: #334155;
font-size: 12px;
text-transform: uppercase;
letter-spacing: .4px;
}
.mini-row {
display: flex;
gap: 10px;
align-items: center;
padding: 10px 12px;
border-top: 1px solid #f1f5f9;
}
.mini-row:hover {
background: #fafcff;
}
.btn-soft {
border-radius: 10px;
padding: .5rem .8rem;
font-weight: 700;
letter-spacing: .2px;
border: 1px solid transparent;
box-shadow: 0 2px 8px rgba(0,0,0,.06);
}
.btn-add {
background: #0b5ed7;
color: #fff;
}
.btn-add:hover {
background: #0a53be;
}
.btn-del {
background: #fff;
color: #dc2626;
border: 1px solid #f1d2d2;
}
.btn-del:hover {
background: #fff5f5;
}
.stacked-checks .form-check {
margin-bottom: .4rem;
}
.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;
}
.btn-go {
background: #22c55e;
color: #fff;
border-radius: 10px;
padding: .6rem .95rem;
font-weight: 800;
}
.btn-go:hover {
background: #16a34a;
}
.btn-send {
background: #0b5ed7;
color: #fff;
border-radius: 10px;
padding: .6rem .95rem;
font-weight: 800;
}
.btn-send:hover {
background: #0a53be;
}
.btn-reset {
background: #fff;
color: #334155;
border: 1px solid var(--soft-b);
border-radius: 10px;
padding: .6rem .95rem;
font-weight: 700;
}
.btn-reset:hover {
background: #f8fafc;
}
.muted {
color: var(--muted);
}
.perm-flags {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.divider {
height: 1px;
background: #eef2f6;
margin: 8px 0;
}
</style>
<div id="itFormApp">
<div class="page-head">
<h3 class="page-title"></h3>
<div class="subtle">Stages: HOD → Group IT HOD → Finance HOD → Management</div>
</div>
<!-- Requester -->
<div class="ui-card">
<div class="ui-head">
<h6><i class="bi bi-person-badge"></i> Requester Details</h6>
<span class="note">These fields are snapshotted at submission</span>
</div>
<div class="ui-body">
<div class="req-grid">
<div>
<label class="form-label">Staff Name</label>
<input type="text" class="form-control" v-model.trim="model.staffName" readonly>
</div>
<div>
<label class="form-label">Designation</label>
<input type="text" class="form-control" v-model.trim="model.designation" readonly>
</div>
<div>
<label class="form-label">Company</label>
<input type="text" class="form-control" v-model.trim="model.companyName" readonly>
</div>
<div>
<label class="form-label">Div/Dept</label>
<input type="text" class="form-control" v-model.trim="model.departmentName" readonly>
</div>
<div>
<label class="form-label">Location</label>
<input type="text" class="form-control" v-model.trim="model.location" readonly>
</div>
<div>
<label class="form-label">Phone Ext</label>
<input type="text" class="form-control" v-model.trim="model.phoneExt" readonly>
</div>
<div>
<label class="form-label">Employment Status</label>
<select class="form-select" v-model="model.employmentStatus" disabled>
<option value="">--</option>
<option>Permanent</option>
<option>Contract</option>
<option>Temp</option>
<option>New Staff</option>
</select>
</div>
<div v-if="model.employmentStatus==='Contract' || model.employmentStatus==='Temp'">
<label class="form-label">Contract End Date</label>
<input type="date" class="form-control" v-model="model.contractEndDate" readonly>
</div>
<div>
<label class="form-label">Required Date <span class="req">*</span></label>
<input type="date" class="form-control" v-model="model.requiredDate" :min="minReqISO">
<div class="invalid-hint" v-if="validation.requiredDate">{{ validation.requiredDate }}</div>
</div>
</div>
</div>
</div>
<!-- Hardware -->
<div class="ui-card">
<div class="ui-head">
<h6><i class="bi bi-cpu"></i> Hardware Requirements</h6>
<div class="d-flex align-items-center gap-2">
<span class="chip" v-if="hardwareCount===0"><i class="bi bi-inboxes"></i>None selected</span>
<span class="chip chip-ok" v-else><i class="bi bi-check2"></i>{{ hardwareCount }} selected</span>
</div>
</div>
<div class="ui-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Purpose <span class="req" v-if="hardwareCount>0">*</span></label>
<select class="form-select" v-model="hardwarePurpose">
<option value="">-- Select --</option>
<option value="NewRecruitment">New Staff Recruitment</option>
<option value="Replacement">Replacement</option>
<option value="Additional">Additional</option>
</select>
<div class="invalid-hint" v-if="validation.hardwarePurpose">{{ validation.hardwarePurpose }}</div>
<label class="form-label mt-3">Justification (for hardware change)</label>
<textarea class="form-control" rows="3" v-model="hardwareJustification" placeholder="-"></textarea>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center justify-content-between">
<label class="form-label">Select below</label>
<div class="small text-muted">All-inclusive toggles will auto-select sensible accessories</div>
</div>
<div class="stacked-checks">
<div class="form-check" v-for="opt in hardwareCategories" :key="opt.key">
<input class="form-check-input" type="checkbox" :id="'cat_'+opt.key"
v-model="opt.include" @@change="onHardwareToggle(opt.key)">
<label class="form-check-label" :for="'cat_'+opt.key">{{ opt.label }}</label>
</div>
</div>
<div class="mt-2">
<label class="form-label">Other (Specify)</label>
<input class="form-control form-control-sm" v-model.trim="hardwareOther" placeholder="-">
</div>
</div>
</div>
</div>
</div>
<!-- Email -->
<div class="ui-card">
<div class="ui-head">
<h6><i class="bi bi-envelope-paper"></i> Email</h6>
<div class="d-flex align-items-center gap-2">
<span class="note">Enter proposed address(es) without <code>@@domain</code></span>
<button class="btn-soft btn-add" @@click="addEmail"><i class="bi bi-plus"></i> Add</button>
</div>
</div>
<div class="ui-body">
<div class="mini-table">
<div class="mini-head">Proposed Address (without @@domain)</div>
<div v-for="(row, i) in emailRows" :key="'em-'+i" class="mini-row">
<div class="flex-grow-1">
<input class="form-control form-control-sm" v-model.trim="row.proposedAddress" placeholder="e.g. j.doe">
</div>
<button class="btn btn-del btn-sm" @@click="removeEmail(i)"><i class="bi bi-x"></i></button>
</div>
<div v-if="emailRows.length===0" class="mini-row">
<div class="text-muted">No email rows</div>
</div>
</div>
</div>
</div>
<!-- OS -->
<div class="ui-card">
<div class="ui-head">
<h6><i class="bi bi-windows"></i> Operating System Requirements</h6>
<button class="btn-soft btn-add" @@click="addOs"><i class="bi bi-plus"></i> Add</button>
</div>
<div class="ui-body">
<div class="mini-table">
<div class="mini-head">Requirement</div>
<div v-for="(row, i) in osReqs" :key="'os-'+i" class="mini-row">
<textarea class="form-control" rows="2" v-model="row.requirementText" placeholder="e.g. Windows 11 Pro required due to ..."></textarea>
<button class="btn btn-del btn-sm" @@click="removeOs(i)"><i class="bi bi-x"></i></button>
</div>
<div v-if="osReqs.length===0" class="mini-row">
<div class="text-muted">No OS requirements</div>
</div>
</div>
</div>
</div>
<!-- Software -->
<div class="ui-card">
<div class="ui-head">
<h6><i class="bi bi-boxes"></i> Software</h6>
<span class="note">Tick to include; use Others to specify anything not listed</span>
</div>
<div class="ui-body">
<div class="row g-3">
<div class="col-md-4">
<h6 class="mb-2">General Software</h6>
<div class="form-check" v-for="opt in softwareGeneralOpts" :key="'gen-'+opt">
<input class="form-check-input" type="checkbox" :id="'gen_'+opt" v-model="softwareGeneral[opt]">
<label class="form-check-label" :for="'gen_'+opt">{{ opt }}</label>
</div>
<div class="mt-2">
<label class="form-label">Others (Specify)</label>
<input class="form-control form-control-sm" v-model.trim="softwareGeneralOther" placeholder="-">
</div>
</div>
<div class="col-md-4">
<h6 class="mb-2">Utility Software</h6>
<div class="form-check" v-for="opt in softwareUtilityOpts" :key="'utl-'+opt">
<input class="form-check-input" type="checkbox" :id="'utl_'+opt" v-model="softwareUtility[opt]">
<label class="form-check-label" :for="'utl_'+opt">{{ opt }}</label>
</div>
<div class="mt-2">
<label class="form-label">Others (Specify)</label>
<input class="form-control form-control-sm" v-model.trim="softwareUtilityOther" placeholder="-">
</div>
</div>
<div class="col-md-4">
<h6 class="mb-2">Custom Software</h6>
<label class="form-label">Others (Specify)</label>
<input class="form-control form-control-sm" v-model.trim="softwareCustomOther" placeholder="-">
</div>
</div>
</div>
</div>
<!-- Shared Permissions (CAP 6) -->
<div class="ui-card">
<div class="ui-head">
<h6><i class="bi bi-share"></i> Shared Permissions</h6>
<div class="d-flex align-items-center gap-2">
<span class="note">Max 6 entries</span>
<button class="btn-soft btn-add" @@click="addPerm" :disabled="sharedPerms.length>=6"><i class="bi bi-plus"></i> Add</button>
</div>
</div>
<div class="ui-body">
<div class="mini-table">
<div class="mini-head">Share Name &amp; Permissions</div>
<div v-for="(p, i) in sharedPerms" :key="'sp-'+i" class="mini-row">
<input class="form-control form-control-sm" style="max-width:280px"
v-model.trim="p.shareName" placeholder="e.g. Finance Shared Folder">
<div class="perm-flags">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" v-model="p.canRead" :id="'r'+i">
<label class="form-check-label" :for="'r'+i">Read</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" v-model="p.canWrite" :id="'w'+i">
<label class="form-check-label" :for="'w'+i">Write</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" v-model="p.canDelete" :id="'d'+i">
<label class="form-check-label" :for="'d'+i">Delete</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" v-model="p.canRemove" :id="'u'+i">
<label class="form-check-label" :for="'u'+i">Remove</label>
</div>
</div>
<button class="btn btn-del btn-sm" @@click="removePerm(i)"><i class="bi bi-x"></i></button>
</div>
<div v-if="sharedPerms.length===0" class="mini-row">
<div class="text-muted">No shared permissions</div>
</div>
</div>
<div class="invalid-hint" v-if="validation.sharedPerms">{{ validation.sharedPerms }}</div>
</div>
</div>
<!-- Submit bar -->
<div class="submit-bar">
<div class="me-auto d-flex align-items-center gap-2">
<span class="chip" :class="model.requiredDate ? 'chip-ok' : 'chip-warn'">
<i class="bi" :class="model.requiredDate ? 'bi-check2' : 'bi-exclamation-triangle'"></i>
{{ model.requiredDate ? 'Required date set' : 'Required date missing' }}
</span>
<span class="chip" v-if="hardwareCount>0 && !hardwarePurpose"><i class="bi bi-exclamation-triangle"></i> Hardware purpose required</span>
<span class="chip chip-ok" v-else-if="hardwareCount>0"><i class="bi bi-check2"></i> Hardware purpose ok</span>
<span class="chip" v-if="sharedPerms.length>6"><i class="bi bi-exclamation-triangle"></i> Max 6 permissions</span>
</div>
<button class="btn btn-reset" @@click="resetForm" :disabled="saving">Reset (sections)</button>
<button class="btn btn-go" @@click="saveDraft" :disabled="saving">
<span v-if="saving && intent==='draft'" class="spinner-border spinner-border-sm me-2"></span>
Save Draft
</button>
<button class="btn btn-send" @@click="openConfirm" :disabled="saving">
<span v-if="saving && intent==='send'" class="spinner-border spinner-border-sm me-2"></span>
Send Now
</button>
</div>
</div>
<div class="modal fade" id="sendConfirm" tabindex="-1" aria-labelledby="sendConfirmLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius:14px;">
<div class="modal-header">
<h6 class="modal-title fw-bold" id="sendConfirmLabel">Submit & Lock</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Please double-check your entries. Once sent, this request becomes <strong>Pending</strong> and is <strong>locked</strong> from editing.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmSendBtn">Yes, Send Now</button>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
async function ensureBootstrapModal() {
if (window.bootstrap && window.bootstrap.Modal) return;
await new Promise((resolve) => {
const s = document.createElement('script');
s.src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js";
s.async = true;
s.onload = resolve;
s.onerror = resolve;
document.head.appendChild(s);
});
}
</script>
<script>
const EDIT_WINDOW_HOURS = 24;
const app = Vue.createApp({
data() {
const plus7 = new Date(); plus7.setDate(plus7.getDate() + 7);
return {
saving: false,
intent: '',
validation: { requiredDate: "", hardwarePurpose: "", sharedPerms: "" },
minReqISO: plus7.toISOString().slice(0, 10),
model: {
userId: 0, staffName: "", companyName: "", departmentName: "",
designation: "", location: "", employmentStatus: "", contractEndDate: null,
requiredDate: "", phoneExt: ""
},
hardwarePurpose: "",
hardwareJustification: "",
hardwareCategories: [
{ key: "DesktopAllIn", label: "Desktop (all inclusive)", include: false },
{ key: "NotebookAllIn", label: "Notebook (all inclusive)", include: false },
{ key: "DesktopOnly", label: "Desktop only", include: false },
{ key: "NotebookOnly", label: "Notebook only", include: false },
{ key: "NotebookBattery", label: "Notebook battery", include: false },
{ key: "PowerAdapter", label: "Power Adapter", include: false },
{ key: "Mouse", label: "Computer Mouse", include: false },
{ key: "ExternalHDD", label: "External Hard Drive", include: false }
],
hardwareOther: "",
emailRows: [],
osReqs: [],
softwareGeneralOpts: ["MS Word", "MS Excel", "MS Outlook", "MS PowerPoint", "MS Access", "MS Project", "Acrobat Standard", "AutoCAD", "Worktop/ERP Login"],
softwareUtilityOpts: ["PDF Viewer", "7Zip", "AutoCAD Viewer", "Smart Draw"],
softwareGeneral: {},
softwareUtility: {},
softwareGeneralOther: "",
softwareUtilityOther: "",
softwareCustomOther: "",
// shared permissions
sharedPerms: []
};
},
computed: {
hardwareCount() {
let c = this.hardwareCategories.filter(x => x.include).length;
if (this.hardwareOther.trim()) c += 1;
return c;
}
},
methods: {
// ----- Hardware helpers -----
onHardwareToggle(key) {
const set = (k, v) => {
const t = this.hardwareCategories.find(x => x.key === k);
if (t) t.include = v;
};
if (key === "DesktopAllIn") {
const allIn = this.hardwareCategories.find(x => x.key === "DesktopAllIn")?.include;
if (allIn) {
// mutually exclusive with NotebookOnly/NotebookAllIn
set("NotebookAllIn", false);
set("NotebookOnly", false);
// sensible accessories
set("Mouse", true);
// desktop doesn't need PowerAdapter
}
}
if (key === "NotebookAllIn") {
const allIn = this.hardwareCategories.find(x => x.key === "NotebookAllIn")?.include;
if (allIn) {
set("DesktopAllIn", false);
set("DesktopOnly", false);
// sensible accessories
set("PowerAdapter", true);
set("Mouse", true);
set("NotebookBattery", true);
}
}
if (key === "DesktopOnly") {
const only = this.hardwareCategories.find(x => x.key === "DesktopOnly")?.include;
if (only) {
set("DesktopAllIn", false);
}
}
if (key === "NotebookOnly") {
const only = this.hardwareCategories.find(x => x.key === "NotebookOnly")?.include;
if (only) {
set("NotebookAllIn", false);
}
}
},
// ----- Email/OS -----
addEmail() { this.emailRows.push({ proposedAddress: "" }); },
removeEmail(i) { this.emailRows.splice(i, 1); },
addOs() { this.osReqs.push({ requirementText: "" }); },
removeOs(i) { this.osReqs.splice(i, 1); },
// ----- Shared perms -----
addPerm() {
if (this.sharedPerms.length >= 6) return;
this.sharedPerms.push({ shareName: "", canRead: true, canWrite: false, canDelete: false, canRemove: false });
},
removePerm(i) { this.sharedPerms.splice(i, 1); },
// ----- Validation -----
validate() {
this.validation = { requiredDate: "", hardwarePurpose: "", sharedPerms: "" };
if (!this.model.requiredDate) {
this.validation.requiredDate = "Required Date is mandatory.";
} else if (this.model.requiredDate < this.minReqISO) {
this.validation.requiredDate = "Required Date must be at least 7 days from today.";
}
const anyHardware = this.hardwareCount > 0;
if (anyHardware && !this.hardwarePurpose) this.validation.hardwarePurpose = "Please select a Hardware Purpose.";
if (this.sharedPerms.length > 6) this.validation.sharedPerms = "Maximum 6 shared permissions.";
return !this.validation.requiredDate && !this.validation.hardwarePurpose && !this.validation.sharedPerms;
},
// ----- DTO -----
buildDto() {
const hardware = [];
const justification = this.hardwareJustification || "";
const purpose = this.hardwarePurpose || "";
this.hardwareCategories.forEach(c => {
if (c.include) hardware.push({ category: c.key, purpose, justification, otherDescription: "" });
});
if (this.hardwareOther.trim()) {
hardware.push({ category: "Other", purpose, justification, otherDescription: this.hardwareOther.trim() });
}
const emails = this.emailRows.map(x => ({ proposedAddress: x.proposedAddress || "" }));
const OSReqs = this.osReqs.map(x => ({ requirementText: x.requirementText }));
const software = [];
Object.keys(this.softwareGeneral).forEach(name => { if (this.softwareGeneral[name]) software.push({ bucket: "General", name, otherName: "", notes: "" }); });
Object.keys(this.softwareUtility).forEach(name => { if (this.softwareUtility[name]) software.push({ bucket: "Utility", name, otherName: "", notes: "" }); });
if (this.softwareGeneralOther?.trim()) software.push({ bucket: "General", name: "Others", otherName: this.softwareGeneralOther.trim(), notes: "" });
if (this.softwareUtilityOther?.trim()) software.push({ bucket: "Utility", name: "Others", otherName: this.softwareUtilityOther.trim(), notes: "" });
if (this.softwareCustomOther?.trim()) software.push({ bucket: "Custom", name: "Others", otherName: this.softwareCustomOther.trim(), notes: "" });
// shared perms (cap at 6 client-side)
const sharedPerms = this.sharedPerms.slice(0, 6).map(x => ({
shareName: x.shareName || "",
canRead: !!x.canRead,
canWrite: !!x.canWrite,
canDelete: !!x.canDelete,
canRemove: !!x.canRemove
}));
return {
staffName: this.model.staffName,
companyName: this.model.companyName,
departmentName: this.model.departmentName,
designation: this.model.designation,
location: this.model.location,
employmentStatus: this.model.employmentStatus,
contractEndDate: this.model.contractEndDate || null,
requiredDate: this.model.requiredDate,
phoneExt: this.model.phoneExt,
editWindowHours: EDIT_WINDOW_HOURS,
hardware, emails, OSReqs, software, sharedPerms
};
},
async createRequest(sendNow = false) {
const dto = this.buildDto();
dto.sendNow = !!sendNow;
const r = await fetch('/ItRequestAPI/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dto)
});
const ct = r.headers.get('content-type') || '';
const payload = ct.includes('application/json') ? await r.json() : { message: await r.text() };
if (!r.ok) throw new Error(payload?.message || `Create failed (${r.status})`);
const statusId = payload?.statusId;
if (!statusId) throw new Error('Create succeeded but no statusId returned.');
return statusId;
},
async saveDraft() {
if (!this.validate()) return;
this.saving = true; this.intent = 'draft';
try {
await this.createRequest(false);
window.location.href = `/IT/ApprovalDashboard/MyRequests`;
} catch (e) {
alert('Error: ' + (e?.message || e));
} finally { this.saving = false; this.intent = ''; }
},
async openConfirm() {
if (!this.validate()) return;
await ensureBootstrapModal();
const modalEl = document.getElementById('sendConfirm');
if (!window.bootstrap || !bootstrap.Modal) {
if (confirm('Submit & Lock?\nOnce sent, this request becomes Pending and is locked from editing.')) {
this.sendNow();
}
return;
}
const Modal = bootstrap.Modal;
const inst = (typeof Modal.getOrCreateInstance === 'function')
? Modal.getOrCreateInstance(modalEl)
: (Modal.getInstance(modalEl) || new Modal(modalEl));
const btn = document.getElementById('confirmSendBtn');
btn.onclick = () => { inst.hide(); this.sendNow(); };
inst.show();
},
async sendNow() {
if (!this.validate()) return;
this.saving = true; this.intent = 'send';
try {
await this.createRequest(true);
window.location.href = `/IT/ApprovalDashboard/MyRequests`;
} catch (e) {
alert('Error: ' + (e?.message || e));
} finally {
this.saving = false; this.intent = '';
}
},
resetForm() {
this.hardwarePurpose = "";
this.hardwareJustification = "";
this.hardwareCategories.forEach(x => x.include = false);
this.hardwareOther = "";
this.emailRows = [];
this.osReqs = [];
this.softwareGeneral = {};
this.softwareUtility = {};
this.softwareGeneralOther = "";
this.softwareUtilityOther = "";
this.softwareCustomOther = "";
this.sharedPerms = [];
this.model.requiredDate = "";
this.validation = { requiredDate: "", hardwarePurpose: "", sharedPerms: "" };
},
async prefillFromServer() {
try {
const res = await fetch('/ItRequestAPI/me');
if (!res.ok) return;
const me = await res.json();
this.model.userId = me.userId || 0;
this.model.staffName = me.staffName || "";
this.model.companyName = me.companyName || "";
this.model.departmentName = me.departmentName || "";
this.model.designation = me.designation || "";
this.model.location = me.location || "";
this.model.employmentStatus = me.employmentStatus || "";
this.model.contractEndDate = me.contractEndDate || null;
this.model.phoneExt = me.phoneExt || "";
} catch { /* ignore */ }
}
},
mounted() { this.prefillFromServer(); }
});
app.mount('#itFormApp');
</script>
}