|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8" /> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|
|
<title>Comprehensive Permit to Work (PTW) Application</title> |
|
|
<style> |
|
|
|
|
|
* { |
|
|
box-sizing: border-box; |
|
|
} |
|
|
body { |
|
|
margin: 0; |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
background: #f4f7fa; |
|
|
color: #333; |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
padding: 20px; |
|
|
} |
|
|
#app { |
|
|
max-width: 1100px; |
|
|
width: 100%; |
|
|
background: #fff; |
|
|
padding: 30px 40px; |
|
|
box-shadow: 0 0 20px rgb(0 0 0 / 0.1); |
|
|
border-radius: 12px; |
|
|
} |
|
|
h1 { |
|
|
margin-bottom: 10px; |
|
|
color: #00467f; |
|
|
} |
|
|
h2 { |
|
|
color: #006bb3; |
|
|
margin-top: 40px; |
|
|
margin-bottom: 15px; |
|
|
border-bottom: 3px solid #0077cc; |
|
|
padding-bottom: 5px; |
|
|
} |
|
|
|
|
|
|
|
|
.main-tabs { |
|
|
display: flex; |
|
|
margin-top: 15px; |
|
|
border-bottom: 2px solid #d3d9e6; |
|
|
gap: 20px; |
|
|
user-select: none; |
|
|
} |
|
|
.main-tab { |
|
|
padding: 12px 26px; |
|
|
font-weight: 700; |
|
|
cursor: pointer; |
|
|
border-radius: 10px 10px 0 0; |
|
|
background: #e9f0f6; |
|
|
color: #0059b3; |
|
|
box-shadow: inset 0 3px 7px rgb(0 71 127 / 0.1); |
|
|
transition: background-color 0.3s ease, color 0.3s ease; |
|
|
} |
|
|
.main-tab:hover:not(.active) { |
|
|
background-color: #c6d9f2; |
|
|
} |
|
|
.main-tab.active { |
|
|
background-color: #0077cc; |
|
|
color: white; |
|
|
box-shadow: 0 6px 10px rgb(0 119 204 / 0.3); |
|
|
border-bottom: 2px solid white; |
|
|
} |
|
|
|
|
|
|
|
|
.tabs { |
|
|
display: flex; |
|
|
margin-top: 15px; |
|
|
border-bottom: 2px solid #d3d9e6; |
|
|
gap: 20px; |
|
|
user-select: none; |
|
|
} |
|
|
.tab { |
|
|
padding: 10px 22px; |
|
|
font-weight: 700; |
|
|
cursor: pointer; |
|
|
border-radius: 10px 10px 0 0; |
|
|
background: #e9f0f6; |
|
|
color: #0059b3; |
|
|
box-shadow: inset 0 3px 7px rgb(0 71 127 / 0.1); |
|
|
transition: background-color 0.3s ease, color 0.3s ease; |
|
|
} |
|
|
.tab:hover:not(.active) { |
|
|
background-color: #c6d9f2; |
|
|
} |
|
|
.tab.active { |
|
|
background-color: #0077cc; |
|
|
color: white; |
|
|
box-shadow: 0 6px 10px rgb(0 119 204 / 0.3); |
|
|
border-bottom: 2px solid white; |
|
|
} |
|
|
|
|
|
|
|
|
form { |
|
|
background: #e9f0f6; |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
box-shadow: inset 1px 1px 5px rgb(0 71 127 / 0.1); |
|
|
} |
|
|
form .row { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
gap: 15px 20px; |
|
|
} |
|
|
form label { |
|
|
flex: 1 1 160px; |
|
|
font-weight: 600; |
|
|
margin-bottom: 5px; |
|
|
color: #004d99; |
|
|
} |
|
|
form input, form textarea, form select { |
|
|
flex: 2 1 280px; |
|
|
padding: 8px 10px; |
|
|
border: 1.5px solid #a9c4db; |
|
|
border-radius: 6px; |
|
|
font-size: 16px; |
|
|
transition: border-color 0.3s ease; |
|
|
} |
|
|
form input:focus, form textarea:focus, form select:focus { |
|
|
outline: none; |
|
|
border-color: #0077cc; |
|
|
} |
|
|
form textarea { |
|
|
resize: vertical; |
|
|
min-height: 60px; |
|
|
} |
|
|
.form-group { |
|
|
margin-bottom: 18px; |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
|
|
|
.filter-container { |
|
|
margin-top: 10px; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
.filter-container label { |
|
|
font-weight: 600; |
|
|
color: #004d99; |
|
|
margin-right: 10px; |
|
|
} |
|
|
.filter-container input { |
|
|
padding: 6px 10px; |
|
|
border-radius: 6px; |
|
|
border: 1.5px solid #a9c4db; |
|
|
font-size: 16px; |
|
|
width: 220px; |
|
|
max-width: 100%; |
|
|
} |
|
|
|
|
|
|
|
|
button { |
|
|
background-color: #0077cc; |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 8px; |
|
|
padding: 12px 24px; |
|
|
font-size: 16px; |
|
|
font-weight: 700; |
|
|
cursor: pointer; |
|
|
margin-top: 10px; |
|
|
transition: background-color 0.3s ease; |
|
|
} |
|
|
button:disabled { |
|
|
background-color: #7aaed9; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
button:hover:not(:disabled) { |
|
|
background-color: #005fa3; |
|
|
} |
|
|
.btn-approve { |
|
|
background-color: #308446; |
|
|
} |
|
|
.btn-approve:hover { |
|
|
background-color: #257234; |
|
|
} |
|
|
.btn-reject { |
|
|
background-color: #cc3b3b; |
|
|
} |
|
|
.btn-reject:hover { |
|
|
background-color: #a92b2b; |
|
|
} |
|
|
.btn-print { |
|
|
background-color: #0077cc; |
|
|
} |
|
|
.btn-print:hover { |
|
|
background-color: #005fa3; |
|
|
} |
|
|
|
|
|
|
|
|
table { |
|
|
width: 100%; |
|
|
border-collapse: collapse; |
|
|
margin-top: 15px; |
|
|
} |
|
|
thead { |
|
|
background: #0077cc; |
|
|
color: #fff; |
|
|
} |
|
|
thead th { |
|
|
padding: 12px 15px; |
|
|
text-align: left; |
|
|
} |
|
|
tbody tr { |
|
|
border-bottom: 1.5px solid #d6e4f3; |
|
|
transition: background-color 0.2s ease; |
|
|
} |
|
|
tbody tr:hover { |
|
|
background-color: #f0f7ff; |
|
|
cursor: pointer; |
|
|
} |
|
|
tbody td { |
|
|
padding: 12px 15px; |
|
|
vertical-align: middle; |
|
|
} |
|
|
.status-created { |
|
|
color: #3066BE; |
|
|
font-weight: 600; |
|
|
} |
|
|
.status-inprogress { |
|
|
color: #F5AB35; |
|
|
font-weight: 600; |
|
|
} |
|
|
.status-closed { |
|
|
color: #308446; |
|
|
font-weight: 600; |
|
|
} |
|
|
.status-pending { |
|
|
color: #f5a623; |
|
|
font-weight: 600; |
|
|
} |
|
|
.status-approved { |
|
|
color: #2e8b57; |
|
|
font-weight: 600; |
|
|
} |
|
|
.status-rejected { |
|
|
color: #cc3b3b; |
|
|
font-weight: 600; |
|
|
} |
|
|
.status-nonactive { |
|
|
color: #888888; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
|
|
|
.modal { |
|
|
display: none; |
|
|
position: fixed; |
|
|
z-index: 100; |
|
|
left: 0; top: 0; right: 0; bottom: 0; |
|
|
background-color: rgba(0,0,0,0.6); |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
} |
|
|
.modal.active { |
|
|
display: flex; |
|
|
} |
|
|
.modal-content { |
|
|
background: white; |
|
|
max-width: 600px; |
|
|
width: 90%; |
|
|
border-radius: 12px; |
|
|
padding: 25px 30px; |
|
|
box-shadow: 0 8px 30px rgb(0 0 0 / 0.15); |
|
|
max-height: 90vh; |
|
|
overflow-y: auto; |
|
|
position: relative; |
|
|
} |
|
|
.modal-header { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
.modal-header h3 { |
|
|
margin: 0; |
|
|
color: #00467f; |
|
|
} |
|
|
.modal-close { |
|
|
font-size: 24px; |
|
|
font-weight: 700; |
|
|
cursor: pointer; |
|
|
color: #777; |
|
|
border: none; |
|
|
background: none; |
|
|
} |
|
|
.modal-close:hover { |
|
|
color: #00467f; |
|
|
} |
|
|
|
|
|
|
|
|
.printable-permit { |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
padding: 20px; |
|
|
border: 1px solid #ccc; |
|
|
} |
|
|
.printable-permit h2 { |
|
|
text-align: center; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
.printable-permit dl { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 2fr; |
|
|
gap: 8px 20px; |
|
|
} |
|
|
.printable-permit dt { |
|
|
font-weight: 700; |
|
|
color: #00467f; |
|
|
} |
|
|
.printable-permit dd { |
|
|
margin: 0 0 8px 0; |
|
|
white-space: pre-line; |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 720px) { |
|
|
form .row { |
|
|
flex-direction: column; |
|
|
} |
|
|
form label, form input, form textarea, form select { |
|
|
flex: 1 1 100%; |
|
|
} |
|
|
.form-group { |
|
|
flex-direction: column; |
|
|
align-items: flex-start; |
|
|
} |
|
|
tbody td { |
|
|
padding: 10px 8px; |
|
|
} |
|
|
.printable-permit dl { |
|
|
grid-template-columns: 1fr 1fr; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.sr-only { |
|
|
position: absolute; |
|
|
width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div id="app"> |
|
|
<h1>Permit to Work (PTW) Management System</h1> |
|
|
<p>Manage work permits with creation, approval, monitoring, and closing for HSE compliance.</p> |
|
|
|
|
|
<nav class="main-tabs" role="tablist" aria-label="Main application tabs"> |
|
|
<div class="main-tab active" data-tab="create" role="tab" aria-selected="true" tabindex="0" aria-controls="createTabPanel" id="mainTabCreate">Create Permit</div> |
|
|
<div class="main-tab" data-tab="monitor" role="tab" aria-selected="false" tabindex="-1" aria-controls="monitorTabPanel" id="mainTabMonitor">Monitor Permits</div> |
|
|
<div class="main-tab" data-tab="approval" role="tab" aria-selected="false" tabindex="-1" aria-controls="approvalTabPanel" id="mainTabApproval">Approval</div> |
|
|
</nav> |
|
|
|
|
|
<section id="createTabPanel" role="tabpanel" tabindex="0" aria-labelledby="mainTabCreate"> |
|
|
<h2>Create New Permit</h2> |
|
|
<form id="createPermitForm" autocomplete="off"> |
|
|
<div class="form-group row"> |
|
|
<label for="workTitle">Work Title<span style="color:#cc0000">*</span></label> |
|
|
<input type="text" id="workTitle" name="workTitle" required placeholder="Brief work title" /> |
|
|
</div> |
|
|
|
|
|
<div class="form-group row"> |
|
|
<label for="workDescription">Work Description<span style="color:#cc0000">*</span></label> |
|
|
<textarea id="workDescription" name="workDescription" required placeholder="Describe the job details"></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="form-group row"> |
|
|
<label for="location">Location<span style="color:#cc0000">*</span></label> |
|
|
<input type="text" id="location" name="location" required placeholder="Work location" /> |
|
|
</div> |
|
|
|
|
|
<div class="form-group row"> |
|
|
<label for="responsiblePerson">Responsible Person<span style="color:#cc0000">*</span></label> |
|
|
<input type="text" id="responsiblePerson" name="responsiblePerson" required placeholder="Name of person responsible" /> |
|
|
</div> |
|
|
|
|
|
<div class="form-group row"> |
|
|
<label for="startDate">Start Date & Time<span style="color:#cc0000">*</span></label> |
|
|
<input type="datetime-local" id="startDate" name="startDate" required /> |
|
|
</div> |
|
|
|
|
|
<div class="form-group row"> |
|
|
<label for="endDate">Expected End Date & Time<span style="color:#cc0000">*</span></label> |
|
|
<input type="datetime-local" id="endDate" name="endDate" required /> |
|
|
</div> |
|
|
|
|
|
<div class="form-group row"> |
|
|
<label for="safetyChecks">Safety Checks (comma separated)</label> |
|
|
<input type="text" id="safetyChecks" name="safetyChecks" placeholder="e.g. PPE check, Lockout tagout" /> |
|
|
</div> |
|
|
|
|
|
<button type="submit">Create Permit</button> |
|
|
</form> |
|
|
</section> |
|
|
|
|
|
<section id="monitorTabPanel" role="tabpanel" tabindex="0" aria-labelledby="mainTabMonitor" hidden> |
|
|
<h2>Monitor Permits</h2> |
|
|
|
|
|
<div class="filter-container"> |
|
|
<label for="monitorFilter">Filter (search in ID, Title, Responsible Person, Location): </label> |
|
|
<input type="text" id="monitorFilter" placeholder="Type to filter..." aria-label="Filter permits for monitoring" /> |
|
|
</div> |
|
|
|
|
|
<nav class="tabs" role="tablist" aria-label="Permit Status Filter Tabs"> |
|
|
<div class="tab active" data-view="active" role="tab" aria-selected="true" tabindex="0" aria-controls="permitsTable" id="tabActive">Active</div> |
|
|
<div class="tab" data-view="nonactive" role="tab" aria-selected="false" tabindex="-1" aria-controls="permitsTable" id="tabNonActive">Non-Active</div> |
|
|
<div class="tab" data-view="complete" role="tab" aria-selected="false" tabindex="-1" aria-controls="permitsTable" id="tabComplete">Complete</div> |
|
|
</nav> |
|
|
|
|
|
<table id="permitsTable" aria-live="polite" aria-describedby="permitsDesc" aria-label="List of permits"> |
|
|
<thead> |
|
|
<tr> |
|
|
<th>Permit ID</th> |
|
|
<th>Work Title</th> |
|
|
<th>Responsible Person</th> |
|
|
<th>Location</th> |
|
|
<th>Start Date</th> |
|
|
<th>End Date</th> |
|
|
<th>Status</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
|
|
|
<div id="permitsDesc" class="sr-only">This table shows permits filtered by the selected status tab and filter above.</div> |
|
|
</section> |
|
|
|
|
|
<section id="approvalTabPanel" role="tabpanel" tabindex="0" aria-labelledby="mainTabApproval" hidden> |
|
|
<h2>Approval Management</h2> |
|
|
|
|
|
<div class="filter-container"> |
|
|
<label for="approvalFilter">Filter (search in ID, Title, Responsible Person, Location): </label> |
|
|
<input type="text" id="approvalFilter" placeholder="Type to filter..." aria-label="Filter permits for approval" /> |
|
|
</div> |
|
|
|
|
|
<nav class="tabs" role="tablist" aria-label="Approval Status Filter Tabs"> |
|
|
<div class="tab active" data-view="pending" role="tab" aria-selected="true" tabindex="0" aria-controls="approvalTable" id="tabPending">Pending Approval</div> |
|
|
<div class="tab" data-view="approved" role="tab" aria-selected="false" tabindex="-1" aria-controls="approvalTable" id="tabApproved">Approved</div> |
|
|
<div class="tab" data-view="rejected" role="tab" aria-selected="false" tabindex="-1" aria-controls="approvalTable" id="tabRejected">Rejected</div> |
|
|
</nav> |
|
|
|
|
|
<table id="approvalTable" aria-live="polite" aria-describedby="approvalDesc" aria-label="List of permits for approval"> |
|
|
<thead> |
|
|
<tr> |
|
|
<th>Permit ID</th> |
|
|
<th>Work Title</th> |
|
|
<th>Responsible Person</th> |
|
|
<th>Location</th> |
|
|
<th>Start Date</th> |
|
|
<th>End Date</th> |
|
|
<th>Status</th> |
|
|
<th>Actions</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
|
|
|
<div id="approvalDesc" class="sr-only">This table shows permits filtered by the selected approval status tab and filter above.</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<div id="permitModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-describedby="modalDesc"> |
|
|
<div class="modal-content" tabindex="0"> |
|
|
<div class="modal-header"> |
|
|
<h3 id="modalTitle">Permit Details</h3> |
|
|
<button class="modal-close" id="modalCloseBtn" aria-label="Close modal">×</button> |
|
|
</div> |
|
|
<div id="modalDesc" tabindex="0"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="printModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="printModalTitle" aria-describedby="printModalDesc"> |
|
|
<div class="modal-content" tabindex="0"> |
|
|
<div class="modal-header"> |
|
|
<h3 id="printModalTitle">Printable Permit</h3> |
|
|
<button class="modal-close" id="printModalCloseBtn" aria-label="Close print modal">×</button> |
|
|
</div> |
|
|
<div id="printModalDesc" tabindex="0" class="printable-permit"> |
|
|
|
|
|
</div> |
|
|
<div style="margin-top: 20px; text-align: center;"> |
|
|
<button type="button" id="printBtn" class="btn-print">Print Permit</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
(() => { |
|
|
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; |
|
|
|
|
|
|
|
|
function formatDateTimeLocal(date) { |
|
|
if (!date) return ''; |
|
|
const d = new Date(date); |
|
|
return d.toLocaleString([], { year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit' }); |
|
|
} |
|
|
function generatePermitID() { |
|
|
return 'PTW-' + Math.random().toString(36).substr(2, 6).toUpperCase(); |
|
|
} |
|
|
function escapeHtml(unsafe) { |
|
|
return unsafe.replace(/[&<"'>]/g, function(m) { |
|
|
switch(m) { |
|
|
case '&': return '&'; |
|
|
case '<': return '<'; |
|
|
case '>': return '>'; |
|
|
case '"': return '"'; |
|
|
case "'": return '''; |
|
|
default: return m; |
|
|
} |
|
|
}); |
|
|
} |
|
|
function daysBetween(date1, date2) { |
|
|
return Math.floor((date2 - date1) / (1000 * 60 * 60 * 24)); |
|
|
} |
|
|
|
|
|
|
|
|
let permits = JSON.parse(localStorage.getItem('permits') || '[]'); |
|
|
let currentEditingPermitId = null; |
|
|
let currentView = 'active'; |
|
|
let currentApprovalView = 'pending'; |
|
|
|
|
|
|
|
|
const createForm = document.getElementById('createPermitForm'); |
|
|
const permitsTableBody = document.querySelector('#permitsTable tbody'); |
|
|
const approvalTableBody = document.querySelector('#approvalTable tbody'); |
|
|
|
|
|
|
|
|
const mainTabs = Array.from(document.querySelectorAll('.main-tab')); |
|
|
const createTabPanel = document.getElementById('createTabPanel'); |
|
|
const monitorTabPanel = document.getElementById('monitorTabPanel'); |
|
|
const approvalTabPanel = document.getElementById('approvalTabPanel'); |
|
|
|
|
|
|
|
|
const statusTabs = Array.from(monitorTabPanel.querySelectorAll('.tab')); |
|
|
const monitorFilterInput = document.getElementById('monitorFilter'); |
|
|
|
|
|
|
|
|
const approvalStatusTabs = Array.from(approvalTabPanel.querySelectorAll('.tab')); |
|
|
const approvalFilterInput = document.getElementById('approvalFilter'); |
|
|
|
|
|
const permitModal = document.getElementById('permitModal'); |
|
|
const modalCloseBtn = document.getElementById('modalCloseBtn'); |
|
|
const modalDesc = document.getElementById('modalDesc'); |
|
|
const modalTitle = document.getElementById('modalTitle'); |
|
|
|
|
|
const printModal = document.getElementById('printModal'); |
|
|
const printModalCloseBtn = document.getElementById('printModalCloseBtn'); |
|
|
const printModalDesc = document.getElementById('printModalDesc'); |
|
|
const printBtn = document.getElementById('printBtn'); |
|
|
|
|
|
|
|
|
function savePermits() { |
|
|
localStorage.setItem('permits', JSON.stringify(permits)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function filterPermitsByView() { |
|
|
let filtered = []; |
|
|
const now = new Date(); |
|
|
|
|
|
switch(currentView) { |
|
|
case 'active': |
|
|
|
|
|
filtered = permits.filter(p => p.status === 'Approved' || p.status === 'In Progress'); |
|
|
break; |
|
|
case 'nonactive': |
|
|
|
|
|
filtered = permits.filter(p => { |
|
|
if(p.status !== 'Pending Approval') return false; |
|
|
const createdDate = new Date(p.createdDate || p.submittedDate || p.startDate || now); |
|
|
return (now - createdDate) > SEVEN_DAYS_MS; |
|
|
}); |
|
|
break; |
|
|
case 'complete': |
|
|
|
|
|
filtered = permits.filter(p => p.status === 'Closed'); |
|
|
break; |
|
|
default: |
|
|
filtered = permits; |
|
|
} |
|
|
const filterText = monitorFilterInput.value.trim().toLowerCase(); |
|
|
if(filterText.length === 0) return filtered; |
|
|
|
|
|
return filtered.filter(p => |
|
|
p.id.toLowerCase().includes(filterText) || |
|
|
p.workTitle.toLowerCase().includes(filterText) || |
|
|
p.responsiblePerson.toLowerCase().includes(filterText) || |
|
|
p.location.toLowerCase().includes(filterText) |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
function filterPermitsByApprovalView() { |
|
|
let filtered = []; |
|
|
switch(currentApprovalView) { |
|
|
case 'pending': |
|
|
filtered = permits.filter(p => p.status === 'Pending Approval'); |
|
|
break; |
|
|
case 'approved': |
|
|
filtered = permits.filter(p => p.status === 'Approved' || p.status === 'In Progress'); |
|
|
break; |
|
|
case 'rejected': |
|
|
filtered = permits.filter(p => p.status === 'Rejected'); |
|
|
break; |
|
|
default: |
|
|
filtered = []; |
|
|
} |
|
|
const filterText = approvalFilterInput.value.trim().toLowerCase(); |
|
|
if(filterText.length === 0) return filtered; |
|
|
|
|
|
return filtered.filter(p => |
|
|
p.id.toLowerCase().includes(filterText) || |
|
|
p.workTitle.toLowerCase().includes(filterText) || |
|
|
p.responsiblePerson.toLowerCase().includes(filterText) || |
|
|
p.location.toLowerCase().includes(filterText) |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
function renderPermits() { |
|
|
permitsTableBody.innerHTML = ''; |
|
|
let filteredPermits = filterPermitsByView(); |
|
|
|
|
|
if (filteredPermits.length === 0) { |
|
|
const tr = document.createElement('tr'); |
|
|
const td = document.createElement('td'); |
|
|
td.colSpan = 7; |
|
|
td.style.textAlign = 'center'; |
|
|
td.style.color = '#777'; |
|
|
td.textContent = { |
|
|
active: 'No active permits found.', |
|
|
nonactive: 'No non-active permits found.', |
|
|
complete: 'No completed permits found.' |
|
|
}[currentView] || 'No permits found.'; |
|
|
tr.appendChild(td); |
|
|
permitsTableBody.appendChild(tr); |
|
|
return; |
|
|
} |
|
|
filteredPermits.forEach(p => { |
|
|
const tr = document.createElement('tr'); |
|
|
tr.tabIndex = 0; |
|
|
tr.setAttribute('data-id', p.id); |
|
|
|
|
|
let td = document.createElement('td'); |
|
|
td.textContent = p.id; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.workTitle; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.responsiblePerson; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.location; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = formatDateTimeLocal(p.startDate); |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = formatDateTimeLocal(p.endDate); |
|
|
tr.appendChild(td); |
|
|
|
|
|
|
|
|
let statusClass = 'status-' + p.status.toLowerCase().replace(/\s/g, ''); |
|
|
|
|
|
if(currentView === 'nonactive') statusClass = 'status-nonactive'; |
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.status; |
|
|
td.classList.add(statusClass); |
|
|
tr.appendChild(td); |
|
|
|
|
|
tr.addEventListener('click', () => openPermitModal(p.id)); |
|
|
tr.addEventListener('keypress', e => { |
|
|
if(e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
openPermitModal(p.id); |
|
|
} |
|
|
}); |
|
|
|
|
|
permitsTableBody.appendChild(tr); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function renderApprovalPermits() { |
|
|
approvalTableBody.innerHTML = ''; |
|
|
let filteredPermits = filterPermitsByApprovalView(); |
|
|
|
|
|
if (filteredPermits.length === 0) { |
|
|
const tr = document.createElement('tr'); |
|
|
const td = document.createElement('td'); |
|
|
td.colSpan = 8; |
|
|
td.style.textAlign = 'center'; |
|
|
td.style.color = '#777'; |
|
|
td.textContent = { |
|
|
pending: 'No permits pending approval.', |
|
|
approved: 'No approved permits found.', |
|
|
rejected: 'No rejected permits found.' |
|
|
}[currentApprovalView] || 'No permits found.'; |
|
|
tr.appendChild(td); |
|
|
approvalTableBody.appendChild(tr); |
|
|
return; |
|
|
} |
|
|
|
|
|
filteredPermits.forEach(p => { |
|
|
const tr = document.createElement('tr'); |
|
|
tr.tabIndex = 0; |
|
|
tr.setAttribute('data-id', p.id); |
|
|
|
|
|
let td = document.createElement('td'); |
|
|
td.textContent = p.id; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.workTitle; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.responsiblePerson; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.location; |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = formatDateTimeLocal(p.startDate); |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = formatDateTimeLocal(p.endDate); |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
td.textContent = p.status; |
|
|
td.classList.add('status-'+p.status.toLowerCase().replace(/\s/g, '')); |
|
|
tr.appendChild(td); |
|
|
|
|
|
td = document.createElement('td'); |
|
|
if(p.status === 'Pending Approval') { |
|
|
const btnApprove = document.createElement('button'); |
|
|
btnApprove.textContent = 'Approve'; |
|
|
btnApprove.className = 'btn-approve'; |
|
|
btnApprove.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
approvePermit(p.id); |
|
|
}); |
|
|
td.appendChild(btnApprove); |
|
|
|
|
|
const btnReject = document.createElement('button'); |
|
|
btnReject.textContent = 'Reject'; |
|
|
btnReject.className = 'btn-reject'; |
|
|
btnReject.style.marginLeft = '10px'; |
|
|
btnReject.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
rejectPermit(p.id); |
|
|
}); |
|
|
td.appendChild(btnReject); |
|
|
} else if(p.status === 'Approved' || p.status === 'In Progress') { |
|
|
const btnPrint = document.createElement('button'); |
|
|
btnPrint.textContent = 'Print'; |
|
|
btnPrint.className = 'btn-print'; |
|
|
btnPrint.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
openPrintModal(p.id); |
|
|
}); |
|
|
td.appendChild(btnPrint); |
|
|
} else { |
|
|
td.textContent = '-'; |
|
|
} |
|
|
tr.appendChild(td); |
|
|
|
|
|
tr.addEventListener('click', () => openPermitModal(p.id)); |
|
|
tr.addEventListener('keypress', e => { |
|
|
if(e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
openPermitModal(p.id); |
|
|
} |
|
|
}); |
|
|
|
|
|
approvalTableBody.appendChild(tr); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function openPermitModal(permitId) { |
|
|
currentEditingPermitId = permitId; |
|
|
const permit = permits.find(p => p.id === permitId); |
|
|
if (!permit) return; |
|
|
|
|
|
modalTitle.textContent = `Permit Details - ${permit.id}`; |
|
|
modalDesc.innerHTML = ''; |
|
|
|
|
|
|
|
|
const infoList = document.createElement('dl'); |
|
|
infoList.style.marginBottom = '20px'; |
|
|
|
|
|
function addInfoItem(label, value) { |
|
|
const dt = document.createElement('dt'); |
|
|
dt.textContent = label; |
|
|
dt.style.fontWeight = '600'; |
|
|
dt.style.marginTop = '8px'; |
|
|
const dd = document.createElement('dd'); |
|
|
dd.textContent = value; |
|
|
dd.style.marginLeft = '0'; |
|
|
dd.style.marginBottom = '8px'; |
|
|
infoList.appendChild(dt); |
|
|
infoList.appendChild(dd); |
|
|
} |
|
|
|
|
|
addInfoItem('Work Title:', permit.workTitle); |
|
|
addInfoItem('Work Description:', permit.workDescription); |
|
|
addInfoItem('Location:', permit.location); |
|
|
addInfoItem('Responsible Person:', permit.responsiblePerson); |
|
|
addInfoItem('Start Date & Time:', formatDateTimeLocal(permit.startDate)); |
|
|
addInfoItem('Expected End Date & Time:', formatDateTimeLocal(permit.endDate)); |
|
|
addInfoItem('Status:', permit.status); |
|
|
addInfoItem('Approval Status:', ['Pending Approval', 'Approved', 'Rejected'].includes(permit.status) ? permit.status : 'Not submitted for approval'); |
|
|
addInfoItem('Safety Checks:', permit.safetyChecks.length > 0 ? permit.safetyChecks.join(', ') : 'None'); |
|
|
addInfoItem('Comments:', permit.comments.length > 0 ? permit.comments.join('; ') : 'None'); |
|
|
|
|
|
modalDesc.appendChild(infoList); |
|
|
|
|
|
|
|
|
if (permit.status !== 'Closed' && permit.status !== 'Rejected') { |
|
|
const formUpdate = document.createElement('form'); |
|
|
formUpdate.id = 'monitorForm'; |
|
|
|
|
|
|
|
|
if(permit.safetyChecks.length>0){ |
|
|
const checksLabel = document.createElement('label'); |
|
|
checksLabel.textContent = 'Mark Safety Checks Completed:'; |
|
|
checksLabel.style.fontWeight = '600'; |
|
|
formUpdate.appendChild(checksLabel); |
|
|
|
|
|
permit.safetyChecks.forEach((check,idx)=>{ |
|
|
const div = document.createElement('div'); |
|
|
div.style.marginBottom = '6px'; |
|
|
|
|
|
const input = document.createElement('input'); |
|
|
input.type = 'checkbox'; |
|
|
input.id = 'check_'+idx; |
|
|
input.name = 'safetyCheck'; |
|
|
input.value = check; |
|
|
input.checked = permit.completedSafetyChecks.includes(check); |
|
|
|
|
|
const label = document.createElement('label'); |
|
|
label.htmlFor = 'check_'+idx; |
|
|
label.textContent = ' ' + check; |
|
|
|
|
|
div.appendChild(input); |
|
|
div.appendChild(label); |
|
|
formUpdate.appendChild(div); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const commentLabel = document.createElement('label'); |
|
|
commentLabel.htmlFor = 'newComment'; |
|
|
commentLabel.textContent = 'Add Monitoring Comment:'; |
|
|
commentLabel.style.fontWeight = '600'; |
|
|
commentLabel.style.marginTop = '12px'; |
|
|
formUpdate.appendChild(commentLabel); |
|
|
|
|
|
const commentInput = document.createElement('textarea'); |
|
|
commentInput.id = 'newComment'; |
|
|
commentInput.placeholder = 'Add notes or observations here...'; |
|
|
commentInput.rows = 3; |
|
|
formUpdate.appendChild(commentInput); |
|
|
|
|
|
|
|
|
const btnContainer = document.createElement('div'); |
|
|
btnContainer.style.marginTop = '18px'; |
|
|
btnContainer.style.display = 'flex'; |
|
|
btnContainer.style.gap = '12px'; |
|
|
|
|
|
|
|
|
if (permit.status === 'Created') { |
|
|
const submitApprovalBtn = document.createElement('button'); |
|
|
submitApprovalBtn.type = 'button'; |
|
|
submitApprovalBtn.textContent = 'Submit for Approval'; |
|
|
submitApprovalBtn.style.backgroundColor = '#0066cc'; |
|
|
submitApprovalBtn.style.color = 'white'; |
|
|
submitApprovalBtn.addEventListener('click', () => submitForApproval(permit.id)); |
|
|
btnContainer.appendChild(submitApprovalBtn); |
|
|
} |
|
|
|
|
|
if (permit.status === 'Approved' || permit.status === 'In Progress') { |
|
|
const closeBtn = document.createElement('button'); |
|
|
closeBtn.type = 'button'; |
|
|
closeBtn.textContent = 'Close Permit'; |
|
|
closeBtn.style.backgroundColor = '#308446'; |
|
|
closeBtn.style.color = 'white'; |
|
|
closeBtn.addEventListener('click', () => updatePermitStatus('Closed')); |
|
|
btnContainer.appendChild(closeBtn); |
|
|
} |
|
|
|
|
|
const saveBtn = document.createElement('button'); |
|
|
saveBtn.type = 'submit'; |
|
|
saveBtn.textContent = 'Save Monitoring Updates'; |
|
|
btnContainer.appendChild(saveBtn); |
|
|
|
|
|
formUpdate.appendChild(btnContainer); |
|
|
|
|
|
formUpdate.addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
saveMonitoringData(); |
|
|
}); |
|
|
|
|
|
modalDesc.appendChild(formUpdate); |
|
|
} else if (permit.status === 'Rejected') { |
|
|
const rejectedNote = document.createElement('p'); |
|
|
rejectedNote.textContent = 'This permit has been rejected and cannot be updated.'; |
|
|
rejectedNote.style.fontStyle = 'italic'; |
|
|
rejectedNote.style.color = '#cc3b3b'; |
|
|
modalDesc.appendChild(rejectedNote); |
|
|
} else { |
|
|
const closedNote = document.createElement('p'); |
|
|
closedNote.textContent = 'This permit is closed and can no longer be updated.'; |
|
|
closedNote.style.fontStyle = 'italic'; |
|
|
closedNote.style.color = '#308446'; |
|
|
modalDesc.appendChild(closedNote); |
|
|
} |
|
|
|
|
|
permitModal.classList.add('active'); |
|
|
modalDesc.focus(); |
|
|
} |
|
|
|
|
|
|
|
|
function saveMonitoringData() { |
|
|
const permit = permits.find(p => p.id === currentEditingPermitId); |
|
|
if (!permit) return; |
|
|
|
|
|
|
|
|
const checkboxes = permitModal.querySelectorAll('input[name="safetyCheck"]'); |
|
|
permit.completedSafetyChecks = []; |
|
|
checkboxes.forEach(cb => { |
|
|
if(cb.checked){ |
|
|
permit.completedSafetyChecks.push(cb.value); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const newComment = permitModal.querySelector('#newComment').value.trim(); |
|
|
if(newComment.length > 0){ |
|
|
permit.comments.push(newComment); |
|
|
permitModal.querySelector('#newComment').value = ''; |
|
|
} |
|
|
|
|
|
savePermits(); |
|
|
renderPermits(); |
|
|
renderApprovalPermits(); |
|
|
|
|
|
|
|
|
openPermitModal(permit.id); |
|
|
} |
|
|
|
|
|
|
|
|
function updatePermitStatus(newStatus) { |
|
|
const permit = permits.find(p => p.id === currentEditingPermitId); |
|
|
if (!permit) return; |
|
|
|
|
|
permit.status = newStatus; |
|
|
if(newStatus === 'In Progress'){ |
|
|
permit.monitoringStart = new Date().toISOString(); |
|
|
} |
|
|
if(newStatus === 'Approved'){ |
|
|
permit.approvedDate = new Date().toISOString(); |
|
|
|
|
|
permit.status = 'Approved'; |
|
|
} |
|
|
if(newStatus === 'Closed'){ |
|
|
permit.closedAt = new Date().toISOString(); |
|
|
} |
|
|
|
|
|
savePermits(); |
|
|
renderPermits(); |
|
|
renderApprovalPermits(); |
|
|
openPermitModal(permit.id); |
|
|
} |
|
|
|
|
|
|
|
|
function submitForApproval(id) { |
|
|
const permit = permits.find(p => p.id === id); |
|
|
if (!permit) return; |
|
|
if (permit.status !== 'Created') { |
|
|
alert('Only permits in "Created" status can be submitted for approval.'); |
|
|
return; |
|
|
} |
|
|
permit.status = 'Pending Approval'; |
|
|
permit.submittedDate = new Date().toISOString(); |
|
|
savePermits(); |
|
|
alert('Permit submitted for leader approval.'); |
|
|
renderPermits(); |
|
|
renderApprovalPermits(); |
|
|
activateMainTab('approval'); |
|
|
activateApprovalStatusTab('pending'); |
|
|
} |
|
|
|
|
|
|
|
|
function approvePermit(id) { |
|
|
const permit = permits.find(p => p.id === id); |
|
|
if (!permit) return; |
|
|
if (permit.status !== 'Pending Approval') { |
|
|
alert('Only permits pending approval can be approved.'); |
|
|
return; |
|
|
} |
|
|
permit.status = 'Approved'; |
|
|
permit.approvedDate = new Date().toISOString(); |
|
|
savePermits(); |
|
|
alert('Permit approved.'); |
|
|
renderApprovalPermits(); |
|
|
renderPermits(); |
|
|
} |
|
|
|
|
|
|
|
|
function rejectPermit(id) { |
|
|
const permit = permits.find(p => p.id === id); |
|
|
if (!permit) return; |
|
|
if (permit.status !== 'Pending Approval') { |
|
|
alert('Only permits pending approval can be rejected.'); |
|
|
return; |
|
|
} |
|
|
permit.status = 'Rejected'; |
|
|
savePermits(); |
|
|
alert('Permit rejected.'); |
|
|
renderApprovalPermits(); |
|
|
renderPermits(); |
|
|
} |
|
|
|
|
|
|
|
|
function openPrintModal(id) { |
|
|
const permit = permits.find(p => p.id === id); |
|
|
if (!permit) return; |
|
|
|
|
|
let content = ` |
|
|
<h2>Permit to Work (PTW)</h2> |
|
|
<dl> |
|
|
<dt>Permit ID:</dt><dd>${escapeHtml(permit.id)}</dd> |
|
|
<dt>Work Title:</dt><dd>${escapeHtml(permit.workTitle)}</dd> |
|
|
<dt>Work Description:</dt><dd>${escapeHtml(permit.workDescription)}</dd> |
|
|
<dt>Location:</dt><dd>${escapeHtml(permit.location)}</dd> |
|
|
<dt>Responsible Person:</dt><dd>${escapeHtml(permit.responsiblePerson)}</dd> |
|
|
<dt>Start Date & Time:</dt><dd>${formatDateTimeLocal(permit.startDate)}</dd> |
|
|
<dt>Expected End Date & Time:</dt><dd>${formatDateTimeLocal(permit.endDate)}</dd> |
|
|
<dt>Status:</dt><dd>${escapeHtml(permit.status)}</dd> |
|
|
<dt>Safety Checks:</dt><dd>${permit.safetyChecks.length > 0 ? escapeHtml(permit.safetyChecks.join(', ')) : 'None'}</dd> |
|
|
<dt>Comments:</dt><dd>${permit.comments.length > 0 ? escapeHtml(permit.comments.join('\n')) : 'None'}</dd> |
|
|
</dl> |
|
|
`; |
|
|
printModalDesc.innerHTML = content; |
|
|
printModal.classList.add('active'); |
|
|
printModalDesc.focus(); |
|
|
} |
|
|
|
|
|
|
|
|
function closeModal() { |
|
|
permitModal.classList.remove('active'); |
|
|
currentEditingPermitId = null; |
|
|
} |
|
|
function closePrintModal() { |
|
|
printModal.classList.remove('active'); |
|
|
} |
|
|
|
|
|
|
|
|
function printPermit() { |
|
|
const printWindow = window.open('', '', 'width=800,height=600'); |
|
|
if (!printWindow) { |
|
|
alert('Pop-up blocked. Please allow pop-ups for this website to print.'); |
|
|
return; |
|
|
} |
|
|
printWindow.document.write(` |
|
|
<html> |
|
|
<head> |
|
|
<title>Print Permit</title> |
|
|
<style> |
|
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 20px; } |
|
|
h2 { text-align: center; margin-bottom: 20px; } |
|
|
dl { display: grid; grid-template-columns: 1fr 2fr; gap: 8px 20px; } |
|
|
dt { font-weight: 700; color: #00467f; } |
|
|
dd { margin: 0 0 8px 0; white-space: pre-line; } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
${printModalDesc.innerHTML} |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=alterzick/ptw" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |
|
|
`); |
|
|
printWindow.document.close(); |
|
|
printWindow.focus(); |
|
|
printWindow.print(); |
|
|
printWindow.close(); |
|
|
} |
|
|
|
|
|
|
|
|
createForm.addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
const start = createForm.startDate.value; |
|
|
const end = createForm.endDate.value; |
|
|
if(start >= end){ |
|
|
alert('Start date/time must be before end date/time.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const newPermit = { |
|
|
id: generatePermitID(), |
|
|
workTitle: createForm.workTitle.value.trim(), |
|
|
workDescription: createForm.workDescription.value.trim(), |
|
|
location: createForm.location.value.trim(), |
|
|
responsiblePerson: createForm.responsiblePerson.value.trim(), |
|
|
startDate: start, |
|
|
endDate: end, |
|
|
safetyChecks: createForm.safetyChecks.value.trim() ? createForm.safetyChecks.value.split(',').map(s => s.trim()).filter(s => s.length > 0) : [], |
|
|
completedSafetyChecks: [], |
|
|
comments: [], |
|
|
status: 'Pending Approval', |
|
|
monitoringStart: null, |
|
|
closedAt: null, |
|
|
submittedDate: new Date().toISOString() |
|
|
}; |
|
|
|
|
|
permits.unshift(newPermit); |
|
|
savePermits(); |
|
|
renderPermits(); |
|
|
renderApprovalPermits(); |
|
|
|
|
|
activateMainTab('approval'); |
|
|
activateApprovalStatusTab('pending'); |
|
|
|
|
|
createForm.reset(); |
|
|
}); |
|
|
|
|
|
|
|
|
monitorFilterInput.addEventListener('input', () => { |
|
|
renderPermits(); |
|
|
}); |
|
|
|
|
|
approvalFilterInput.addEventListener('input', () => { |
|
|
renderApprovalPermits(); |
|
|
}); |
|
|
|
|
|
modalCloseBtn.addEventListener('click', closeModal); |
|
|
permitModal.addEventListener('click', (e) => { |
|
|
if(e.target === permitModal){ |
|
|
closeModal(); |
|
|
} |
|
|
}); |
|
|
|
|
|
printModalCloseBtn.addEventListener('click', closePrintModal); |
|
|
printModal.addEventListener('click', (e) => { |
|
|
if(e.target === printModal){ |
|
|
closePrintModal(); |
|
|
} |
|
|
}); |
|
|
printBtn.addEventListener('click', printPermit); |
|
|
|
|
|
|
|
|
mainTabs.forEach(tab => { |
|
|
tab.addEventListener('click', () => { |
|
|
if(tab.classList.contains('active')) return; |
|
|
deactivateAllMainTabs(); |
|
|
activateMainTab(tab.getAttribute('data-tab')); |
|
|
}); |
|
|
tab.addEventListener('keypress', e => { |
|
|
if(e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
tab.click(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
function deactivateAllMainTabs() { |
|
|
mainTabs.forEach(tab => { |
|
|
tab.classList.remove('active'); |
|
|
tab.setAttribute('aria-selected', 'false'); |
|
|
tab.tabIndex = -1; |
|
|
}); |
|
|
createTabPanel.hidden = true; |
|
|
monitorTabPanel.hidden = true; |
|
|
approvalTabPanel.hidden = true; |
|
|
} |
|
|
function activateMainTab(tabName) { |
|
|
deactivateAllMainTabs(); |
|
|
const tab = mainTabs.find(t => t.getAttribute('data-tab') === tabName); |
|
|
if(!tab) return; |
|
|
tab.classList.add('active'); |
|
|
tab.setAttribute('aria-selected', 'true'); |
|
|
tab.tabIndex = 0; |
|
|
if(tabName === 'create') { |
|
|
createTabPanel.hidden = false; |
|
|
createTabPanel.focus(); |
|
|
} else if(tabName === 'monitor') { |
|
|
monitorTabPanel.hidden = false; |
|
|
monitorTabPanel.focus(); |
|
|
} else if(tabName === 'approval') { |
|
|
approvalTabPanel.hidden = false; |
|
|
approvalTabPanel.focus(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
statusTabs.forEach(tab => { |
|
|
tab.addEventListener('click', () => { |
|
|
if(tab.classList.contains('active')) return; |
|
|
deactivateAllStatusTabs(); |
|
|
activateStatusTab(tab.getAttribute('data-view')); |
|
|
}); |
|
|
tab.addEventListener('keypress', e => { |
|
|
if(e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
tab.click(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
function deactivateAllStatusTabs() { |
|
|
statusTabs.forEach(tab => { |
|
|
tab.classList.remove('active'); |
|
|
tab.setAttribute('aria-selected', 'false'); |
|
|
tab.tabIndex = -1; |
|
|
}); |
|
|
} |
|
|
function activateStatusTab(viewName) { |
|
|
deactivateAllStatusTabs(); |
|
|
const tab = statusTabs.find(t => t.getAttribute('data-view') === viewName); |
|
|
if(!tab) return; |
|
|
tab.classList.add('active'); |
|
|
tab.setAttribute('aria-selected', 'true'); |
|
|
tab.tabIndex = 0; |
|
|
currentView = viewName; |
|
|
renderPermits(); |
|
|
} |
|
|
|
|
|
|
|
|
approvalStatusTabs.forEach(tab => { |
|
|
tab.addEventListener('click', () => { |
|
|
if(tab.classList.contains('active')) return; |
|
|
deactivateAllApprovalStatusTabs(); |
|
|
activateApprovalStatusTab(tab.getAttribute('data-view')); |
|
|
}); |
|
|
tab.addEventListener('keypress', e => { |
|
|
if(e.key === 'Enter' || e.key === ' ') { |
|
|
e.preventDefault(); |
|
|
tab.click(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
function deactivateAllApprovalStatusTabs() { |
|
|
approvalStatusTabs.forEach(tab => { |
|
|
tab.classList.remove('active'); |
|
|
tab.setAttribute('aria-selected', 'false'); |
|
|
tab.tabIndex = -1; |
|
|
}); |
|
|
} |
|
|
function activateApprovalStatusTab(viewName) { |
|
|
deactivateAllApprovalStatusTabs(); |
|
|
const tab = approvalStatusTabs.find(t => t.getAttribute('data-view') === viewName); |
|
|
if(!tab) return; |
|
|
tab.classList.add('active'); |
|
|
tab.setAttribute('aria-selected', 'true'); |
|
|
tab.tabIndex = 0; |
|
|
currentApprovalView = viewName; |
|
|
renderApprovalPermits(); |
|
|
} |
|
|
|
|
|
|
|
|
activateMainTab('create'); |
|
|
activateStatusTab('active'); |
|
|
activateApprovalStatusTab('pending'); |
|
|
|
|
|
renderPermits(); |
|
|
renderApprovalPermits(); |
|
|
})(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|
|
|
|