alterzick commited on
Commit
f5351ad
·
verified ·
1 Parent(s): 1191a7d

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1301 -18
  3. prompts.txt +0 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Ptw
3
- emoji: 🏢
4
- colorFrom: pink
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: ptw
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1302 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Comprehensive Permit to Work (PTW) Application</title>
7
+ <style>
8
+ /* Reset and base */
9
+ * {
10
+ box-sizing: border-box;
11
+ }
12
+ body {
13
+ margin: 0;
14
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15
+ background: #f4f7fa;
16
+ color: #333;
17
+ display: flex;
18
+ justify-content: center;
19
+ padding: 20px;
20
+ }
21
+ #app {
22
+ max-width: 1100px;
23
+ width: 100%;
24
+ background: #fff;
25
+ padding: 30px 40px;
26
+ box-shadow: 0 0 20px rgb(0 0 0 / 0.1);
27
+ border-radius: 12px;
28
+ }
29
+ h1 {
30
+ margin-bottom: 10px;
31
+ color: #00467f;
32
+ }
33
+ h2 {
34
+ color: #006bb3;
35
+ margin-top: 40px;
36
+ margin-bottom: 15px;
37
+ border-bottom: 3px solid #0077cc;
38
+ padding-bottom: 5px;
39
+ }
40
+
41
+ /* Top-Level Tabs */
42
+ .main-tabs {
43
+ display: flex;
44
+ margin-top: 15px;
45
+ border-bottom: 2px solid #d3d9e6;
46
+ gap: 20px;
47
+ user-select: none;
48
+ }
49
+ .main-tab {
50
+ padding: 12px 26px;
51
+ font-weight: 700;
52
+ cursor: pointer;
53
+ border-radius: 10px 10px 0 0;
54
+ background: #e9f0f6;
55
+ color: #0059b3;
56
+ box-shadow: inset 0 3px 7px rgb(0 71 127 / 0.1);
57
+ transition: background-color 0.3s ease, color 0.3s ease;
58
+ }
59
+ .main-tab:hover:not(.active) {
60
+ background-color: #c6d9f2;
61
+ }
62
+ .main-tab.active {
63
+ background-color: #0077cc;
64
+ color: white;
65
+ box-shadow: 0 6px 10px rgb(0 119 204 / 0.3);
66
+ border-bottom: 2px solid white;
67
+ }
68
+
69
+ /* Tabs inside Monitor and Approval tab */
70
+ .tabs {
71
+ display: flex;
72
+ margin-top: 15px;
73
+ border-bottom: 2px solid #d3d9e6;
74
+ gap: 20px;
75
+ user-select: none;
76
+ }
77
+ .tab {
78
+ padding: 10px 22px;
79
+ font-weight: 700;
80
+ cursor: pointer;
81
+ border-radius: 10px 10px 0 0;
82
+ background: #e9f0f6;
83
+ color: #0059b3;
84
+ box-shadow: inset 0 3px 7px rgb(0 71 127 / 0.1);
85
+ transition: background-color 0.3s ease, color 0.3s ease;
86
+ }
87
+ .tab:hover:not(.active) {
88
+ background-color: #c6d9f2;
89
+ }
90
+ .tab.active {
91
+ background-color: #0077cc;
92
+ color: white;
93
+ box-shadow: 0 6px 10px rgb(0 119 204 / 0.3);
94
+ border-bottom: 2px solid white;
95
+ }
96
+
97
+ /* Form styles */
98
+ form {
99
+ background: #e9f0f6;
100
+ padding: 20px;
101
+ border-radius: 8px;
102
+ box-shadow: inset 1px 1px 5px rgb(0 71 127 / 0.1);
103
+ }
104
+ form .row {
105
+ display: flex;
106
+ flex-wrap: wrap;
107
+ gap: 15px 20px;
108
+ }
109
+ form label {
110
+ flex: 1 1 160px;
111
+ font-weight: 600;
112
+ margin-bottom: 5px;
113
+ color: #004d99;
114
+ }
115
+ form input, form textarea, form select {
116
+ flex: 2 1 280px;
117
+ padding: 8px 10px;
118
+ border: 1.5px solid #a9c4db;
119
+ border-radius: 6px;
120
+ font-size: 16px;
121
+ transition: border-color 0.3s ease;
122
+ }
123
+ form input:focus, form textarea:focus, form select:focus {
124
+ outline: none;
125
+ border-color: #0077cc;
126
+ }
127
+ form textarea {
128
+ resize: vertical;
129
+ min-height: 60px;
130
+ }
131
+ .form-group {
132
+ margin-bottom: 18px;
133
+ display: flex;
134
+ flex-wrap: wrap;
135
+ align-items: center;
136
+ }
137
+
138
+ /* Filter input */
139
+ .filter-container {
140
+ margin-top: 10px;
141
+ margin-bottom: 10px;
142
+ }
143
+ .filter-container label {
144
+ font-weight: 600;
145
+ color: #004d99;
146
+ margin-right: 10px;
147
+ }
148
+ .filter-container input {
149
+ padding: 6px 10px;
150
+ border-radius: 6px;
151
+ border: 1.5px solid #a9c4db;
152
+ font-size: 16px;
153
+ width: 220px;
154
+ max-width: 100%;
155
+ }
156
+
157
+ /* Buttons */
158
+ button {
159
+ background-color: #0077cc;
160
+ color: white;
161
+ border: none;
162
+ border-radius: 8px;
163
+ padding: 12px 24px;
164
+ font-size: 16px;
165
+ font-weight: 700;
166
+ cursor: pointer;
167
+ margin-top: 10px;
168
+ transition: background-color 0.3s ease;
169
+ }
170
+ button:disabled {
171
+ background-color: #7aaed9;
172
+ cursor: not-allowed;
173
+ }
174
+ button:hover:not(:disabled) {
175
+ background-color: #005fa3;
176
+ }
177
+ .btn-approve {
178
+ background-color: #308446;
179
+ }
180
+ .btn-approve:hover {
181
+ background-color: #257234;
182
+ }
183
+ .btn-reject {
184
+ background-color: #cc3b3b;
185
+ }
186
+ .btn-reject:hover {
187
+ background-color: #a92b2b;
188
+ }
189
+ .btn-print {
190
+ background-color: #0077cc;
191
+ }
192
+ .btn-print:hover {
193
+ background-color: #005fa3;
194
+ }
195
+
196
+ /* Table */
197
+ table {
198
+ width: 100%;
199
+ border-collapse: collapse;
200
+ margin-top: 15px;
201
+ }
202
+ thead {
203
+ background: #0077cc;
204
+ color: #fff;
205
+ }
206
+ thead th {
207
+ padding: 12px 15px;
208
+ text-align: left;
209
+ }
210
+ tbody tr {
211
+ border-bottom: 1.5px solid #d6e4f3;
212
+ transition: background-color 0.2s ease;
213
+ }
214
+ tbody tr:hover {
215
+ background-color: #f0f7ff;
216
+ cursor: pointer;
217
+ }
218
+ tbody td {
219
+ padding: 12px 15px;
220
+ vertical-align: middle;
221
+ }
222
+ .status-created {
223
+ color: #3066BE;
224
+ font-weight: 600;
225
+ }
226
+ .status-inprogress {
227
+ color: #F5AB35;
228
+ font-weight: 600;
229
+ }
230
+ .status-closed {
231
+ color: #308446;
232
+ font-weight: 600;
233
+ }
234
+ .status-pending {
235
+ color: #f5a623;
236
+ font-weight: 600;
237
+ }
238
+ .status-approved {
239
+ color: #2e8b57;
240
+ font-weight: 600;
241
+ }
242
+ .status-rejected {
243
+ color: #cc3b3b;
244
+ font-weight: 600;
245
+ }
246
+ .status-nonactive {
247
+ color: #888888;
248
+ font-weight: 600;
249
+ }
250
+
251
+ /* Modal */
252
+ .modal {
253
+ display: none;
254
+ position: fixed;
255
+ z-index: 100;
256
+ left: 0; top: 0; right: 0; bottom: 0;
257
+ background-color: rgba(0,0,0,0.6);
258
+ justify-content: center;
259
+ align-items: center;
260
+ }
261
+ .modal.active {
262
+ display: flex;
263
+ }
264
+ .modal-content {
265
+ background: white;
266
+ max-width: 600px;
267
+ width: 90%;
268
+ border-radius: 12px;
269
+ padding: 25px 30px;
270
+ box-shadow: 0 8px 30px rgb(0 0 0 / 0.15);
271
+ max-height: 90vh;
272
+ overflow-y: auto;
273
+ position: relative;
274
+ }
275
+ .modal-header {
276
+ display: flex;
277
+ justify-content: space-between;
278
+ align-items: center;
279
+ margin-bottom: 15px;
280
+ }
281
+ .modal-header h3 {
282
+ margin: 0;
283
+ color: #00467f;
284
+ }
285
+ .modal-close {
286
+ font-size: 24px;
287
+ font-weight: 700;
288
+ cursor: pointer;
289
+ color: #777;
290
+ border: none;
291
+ background: none;
292
+ }
293
+ .modal-close:hover {
294
+ color: #00467f;
295
+ }
296
+
297
+ /* Printable permit */
298
+ .printable-permit {
299
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
300
+ padding: 20px;
301
+ border: 1px solid #ccc;
302
+ }
303
+ .printable-permit h2 {
304
+ text-align: center;
305
+ margin-bottom: 20px;
306
+ }
307
+ .printable-permit dl {
308
+ display: grid;
309
+ grid-template-columns: 1fr 2fr;
310
+ gap: 8px 20px;
311
+ }
312
+ .printable-permit dt {
313
+ font-weight: 700;
314
+ color: #00467f;
315
+ }
316
+ .printable-permit dd {
317
+ margin: 0 0 8px 0;
318
+ white-space: pre-line;
319
+ }
320
+
321
+ /* Responsive */
322
+ @media (max-width: 720px) {
323
+ form .row {
324
+ flex-direction: column;
325
+ }
326
+ form label, form input, form textarea, form select {
327
+ flex: 1 1 100%;
328
+ }
329
+ .form-group {
330
+ flex-direction: column;
331
+ align-items: flex-start;
332
+ }
333
+ tbody td {
334
+ padding: 10px 8px;
335
+ }
336
+ .printable-permit dl {
337
+ grid-template-columns: 1fr 1fr;
338
+ }
339
+ }
340
+
341
+ /* Screen reader only helper */
342
+ .sr-only {
343
+ position: absolute;
344
+ width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0;
345
+ }
346
+ </style>
347
+ </head>
348
+ <body>
349
+ <div id="app">
350
+ <h1>Permit to Work (PTW) Management System</h1>
351
+ <p>Manage work permits with creation, approval, monitoring, and closing for HSE compliance.</p>
352
+
353
+ <nav class="main-tabs" role="tablist" aria-label="Main application tabs">
354
+ <div class="main-tab active" data-tab="create" role="tab" aria-selected="true" tabindex="0" aria-controls="createTabPanel" id="mainTabCreate">Create Permit</div>
355
+ <div class="main-tab" data-tab="monitor" role="tab" aria-selected="false" tabindex="-1" aria-controls="monitorTabPanel" id="mainTabMonitor">Monitor Permits</div>
356
+ <div class="main-tab" data-tab="approval" role="tab" aria-selected="false" tabindex="-1" aria-controls="approvalTabPanel" id="mainTabApproval">Approval</div>
357
+ </nav>
358
+
359
+ <section id="createTabPanel" role="tabpanel" tabindex="0" aria-labelledby="mainTabCreate">
360
+ <h2>Create New Permit</h2>
361
+ <form id="createPermitForm" autocomplete="off">
362
+ <div class="form-group row">
363
+ <label for="workTitle">Work Title<span style="color:#cc0000">*</span></label>
364
+ <input type="text" id="workTitle" name="workTitle" required placeholder="Brief work title" />
365
+ </div>
366
+
367
+ <div class="form-group row">
368
+ <label for="workDescription">Work Description<span style="color:#cc0000">*</span></label>
369
+ <textarea id="workDescription" name="workDescription" required placeholder="Describe the job details"></textarea>
370
+ </div>
371
+
372
+ <div class="form-group row">
373
+ <label for="location">Location<span style="color:#cc0000">*</span></label>
374
+ <input type="text" id="location" name="location" required placeholder="Work location" />
375
+ </div>
376
+
377
+ <div class="form-group row">
378
+ <label for="responsiblePerson">Responsible Person<span style="color:#cc0000">*</span></label>
379
+ <input type="text" id="responsiblePerson" name="responsiblePerson" required placeholder="Name of person responsible" />
380
+ </div>
381
+
382
+ <div class="form-group row">
383
+ <label for="startDate">Start Date & Time<span style="color:#cc0000">*</span></label>
384
+ <input type="datetime-local" id="startDate" name="startDate" required />
385
+ </div>
386
+
387
+ <div class="form-group row">
388
+ <label for="endDate">Expected End Date & Time<span style="color:#cc0000">*</span></label>
389
+ <input type="datetime-local" id="endDate" name="endDate" required />
390
+ </div>
391
+
392
+ <div class="form-group row">
393
+ <label for="safetyChecks">Safety Checks (comma separated)</label>
394
+ <input type="text" id="safetyChecks" name="safetyChecks" placeholder="e.g. PPE check, Lockout tagout" />
395
+ </div>
396
+
397
+ <button type="submit">Create Permit</button>
398
+ </form>
399
+ </section>
400
+
401
+ <section id="monitorTabPanel" role="tabpanel" tabindex="0" aria-labelledby="mainTabMonitor" hidden>
402
+ <h2>Monitor Permits</h2>
403
+
404
+ <div class="filter-container">
405
+ <label for="monitorFilter">Filter (search in ID, Title, Responsible Person, Location): </label>
406
+ <input type="text" id="monitorFilter" placeholder="Type to filter..." aria-label="Filter permits for monitoring" />
407
+ </div>
408
+
409
+ <nav class="tabs" role="tablist" aria-label="Permit Status Filter Tabs">
410
+ <div class="tab active" data-view="active" role="tab" aria-selected="true" tabindex="0" aria-controls="permitsTable" id="tabActive">Active</div>
411
+ <div class="tab" data-view="nonactive" role="tab" aria-selected="false" tabindex="-1" aria-controls="permitsTable" id="tabNonActive">Non-Active</div>
412
+ <div class="tab" data-view="complete" role="tab" aria-selected="false" tabindex="-1" aria-controls="permitsTable" id="tabComplete">Complete</div>
413
+ </nav>
414
+
415
+ <table id="permitsTable" aria-live="polite" aria-describedby="permitsDesc" aria-label="List of permits">
416
+ <thead>
417
+ <tr>
418
+ <th>Permit ID</th>
419
+ <th>Work Title</th>
420
+ <th>Responsible Person</th>
421
+ <th>Location</th>
422
+ <th>Start Date</th>
423
+ <th>End Date</th>
424
+ <th>Status</th>
425
+ </tr>
426
+ </thead>
427
+ <tbody>
428
+ <!-- Dynamically populated -->
429
+ </tbody>
430
+ </table>
431
+
432
+ <div id="permitsDesc" class="sr-only">This table shows permits filtered by the selected status tab and filter above.</div>
433
+ </section>
434
+
435
+ <section id="approvalTabPanel" role="tabpanel" tabindex="0" aria-labelledby="mainTabApproval" hidden>
436
+ <h2>Approval Management</h2>
437
+
438
+ <div class="filter-container">
439
+ <label for="approvalFilter">Filter (search in ID, Title, Responsible Person, Location): </label>
440
+ <input type="text" id="approvalFilter" placeholder="Type to filter..." aria-label="Filter permits for approval" />
441
+ </div>
442
+
443
+ <nav class="tabs" role="tablist" aria-label="Approval Status Filter Tabs">
444
+ <div class="tab active" data-view="pending" role="tab" aria-selected="true" tabindex="0" aria-controls="approvalTable" id="tabPending">Pending Approval</div>
445
+ <div class="tab" data-view="approved" role="tab" aria-selected="false" tabindex="-1" aria-controls="approvalTable" id="tabApproved">Approved</div>
446
+ <div class="tab" data-view="rejected" role="tab" aria-selected="false" tabindex="-1" aria-controls="approvalTable" id="tabRejected">Rejected</div>
447
+ </nav>
448
+
449
+ <table id="approvalTable" aria-live="polite" aria-describedby="approvalDesc" aria-label="List of permits for approval">
450
+ <thead>
451
+ <tr>
452
+ <th>Permit ID</th>
453
+ <th>Work Title</th>
454
+ <th>Responsible Person</th>
455
+ <th>Location</th>
456
+ <th>Start Date</th>
457
+ <th>End Date</th>
458
+ <th>Status</th>
459
+ <th>Actions</th>
460
+ </tr>
461
+ </thead>
462
+ <tbody>
463
+ <!-- Dynamically populated -->
464
+ </tbody>
465
+ </table>
466
+
467
+ <div id="approvalDesc" class="sr-only">This table shows permits filtered by the selected approval status tab and filter above.</div>
468
+ </section>
469
+
470
+ <!-- Modal for viewing and updating permit -->
471
+ <div id="permitModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-describedby="modalDesc">
472
+ <div class="modal-content" tabindex="0">
473
+ <div class="modal-header">
474
+ <h3 id="modalTitle">Permit Details</h3>
475
+ <button class="modal-close" id="modalCloseBtn" aria-label="Close modal">&times;</button>
476
+ </div>
477
+ <div id="modalDesc" tabindex="0">
478
+ <!-- Permit details and monitoring form -->
479
+ </div>
480
+ </div>
481
+ </div>
482
+
483
+ <!-- Modal for printing approved permit -->
484
+ <div id="printModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="printModalTitle" aria-describedby="printModalDesc">
485
+ <div class="modal-content" tabindex="0">
486
+ <div class="modal-header">
487
+ <h3 id="printModalTitle">Printable Permit</h3>
488
+ <button class="modal-close" id="printModalCloseBtn" aria-label="Close print modal">&times;</button>
489
+ </div>
490
+ <div id="printModalDesc" tabindex="0" class="printable-permit">
491
+ <!-- Printable content -->
492
+ </div>
493
+ <div style="margin-top: 20px; text-align: center;">
494
+ <button type="button" id="printBtn" class="btn-print">Print Permit</button>
495
+ </div>
496
+ </div>
497
+ </div>
498
+ </div>
499
+
500
+ <script>
501
+ (() => {
502
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
503
+
504
+ // Utilities
505
+ function formatDateTimeLocal(date) {
506
+ if (!date) return '';
507
+ const d = new Date(date);
508
+ return d.toLocaleString([], { year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit' });
509
+ }
510
+ function generatePermitID() {
511
+ return 'PTW-' + Math.random().toString(36).substr(2, 6).toUpperCase();
512
+ }
513
+ function escapeHtml(unsafe) {
514
+ return unsafe.replace(/[&<"'>]/g, function(m) {
515
+ switch(m) {
516
+ case '&': return '&amp;';
517
+ case '<': return '&lt;';
518
+ case '>': return '&gt;';
519
+ case '"': return '&quot;';
520
+ case "'": return '&#039;';
521
+ default: return m;
522
+ }
523
+ });
524
+ }
525
+ function daysBetween(date1, date2) {
526
+ return Math.floor((date2 - date1) / (1000 * 60 * 60 * 24));
527
+ }
528
+
529
+ // State
530
+ let permits = JSON.parse(localStorage.getItem('permits') || '[]');
531
+ let currentEditingPermitId = null;
532
+ let currentView = 'active'; // monitor tab status view
533
+ let currentApprovalView = 'pending'; // approval tab status view
534
+
535
+ // DOM Elements
536
+ const createForm = document.getElementById('createPermitForm');
537
+ const permitsTableBody = document.querySelector('#permitsTable tbody');
538
+ const approvalTableBody = document.querySelector('#approvalTable tbody');
539
+
540
+ // Main tabs
541
+ const mainTabs = Array.from(document.querySelectorAll('.main-tab'));
542
+ const createTabPanel = document.getElementById('createTabPanel');
543
+ const monitorTabPanel = document.getElementById('monitorTabPanel');
544
+ const approvalTabPanel = document.getElementById('approvalTabPanel');
545
+
546
+ // Inner status tabs in monitor tab
547
+ const statusTabs = Array.from(monitorTabPanel.querySelectorAll('.tab'));
548
+ const monitorFilterInput = document.getElementById('monitorFilter');
549
+
550
+ // Inner status tabs in approval tab
551
+ const approvalStatusTabs = Array.from(approvalTabPanel.querySelectorAll('.tab'));
552
+ const approvalFilterInput = document.getElementById('approvalFilter');
553
+
554
+ const permitModal = document.getElementById('permitModal');
555
+ const modalCloseBtn = document.getElementById('modalCloseBtn');
556
+ const modalDesc = document.getElementById('modalDesc');
557
+ const modalTitle = document.getElementById('modalTitle');
558
+
559
+ const printModal = document.getElementById('printModal');
560
+ const printModalCloseBtn = document.getElementById('printModalCloseBtn');
561
+ const printModalDesc = document.getElementById('printModalDesc');
562
+ const printBtn = document.getElementById('printBtn');
563
+
564
+ // Save permits to localStorage
565
+ function savePermits() {
566
+ localStorage.setItem('permits', JSON.stringify(permits));
567
+ }
568
+
569
+ // Updated status categories according to new flow:
570
+ // "Pending Approval" on creation -> after approval = Active (In Progress)
571
+ // Non-Active = permits pending longer than 7 days (status Pending Approval + createdDate older than 7 days)
572
+ // Complete = Closed
573
+
574
+ // Filtering permits by current monitor view and filter input
575
+ function filterPermitsByView() {
576
+ let filtered = [];
577
+ const now = new Date();
578
+
579
+ switch(currentView) {
580
+ case 'active':
581
+ // Active = Approved permits (status 'Approved') considered active now
582
+ filtered = permits.filter(p => p.status === 'Approved' || p.status === 'In Progress');
583
+ break;
584
+ case 'nonactive':
585
+ // Non-Active = Pending Approval permits older than 7 days
586
+ filtered = permits.filter(p => {
587
+ if(p.status !== 'Pending Approval') return false;
588
+ const createdDate = new Date(p.createdDate || p.submittedDate || p.startDate || now);
589
+ return (now - createdDate) > SEVEN_DAYS_MS;
590
+ });
591
+ break;
592
+ case 'complete':
593
+ // Complete = Closed
594
+ filtered = permits.filter(p => p.status === 'Closed');
595
+ break;
596
+ default:
597
+ filtered = permits;
598
+ }
599
+ const filterText = monitorFilterInput.value.trim().toLowerCase();
600
+ if(filterText.length === 0) return filtered;
601
+
602
+ return filtered.filter(p =>
603
+ p.id.toLowerCase().includes(filterText) ||
604
+ p.workTitle.toLowerCase().includes(filterText) ||
605
+ p.responsiblePerson.toLowerCase().includes(filterText) ||
606
+ p.location.toLowerCase().includes(filterText)
607
+ );
608
+ }
609
+
610
+ // Filtering permits by current approval view and filter input
611
+ function filterPermitsByApprovalView() {
612
+ let filtered = [];
613
+ switch(currentApprovalView) {
614
+ case 'pending':
615
+ filtered = permits.filter(p => p.status === 'Pending Approval');
616
+ break;
617
+ case 'approved':
618
+ filtered = permits.filter(p => p.status === 'Approved' || p.status === 'In Progress');
619
+ break;
620
+ case 'rejected':
621
+ filtered = permits.filter(p => p.status === 'Rejected');
622
+ break;
623
+ default:
624
+ filtered = [];
625
+ }
626
+ const filterText = approvalFilterInput.value.trim().toLowerCase();
627
+ if(filterText.length === 0) return filtered;
628
+
629
+ return filtered.filter(p =>
630
+ p.id.toLowerCase().includes(filterText) ||
631
+ p.workTitle.toLowerCase().includes(filterText) ||
632
+ p.responsiblePerson.toLowerCase().includes(filterText) ||
633
+ p.location.toLowerCase().includes(filterText)
634
+ );
635
+ }
636
+
637
+ // Render monitoring table rows
638
+ function renderPermits() {
639
+ permitsTableBody.innerHTML = '';
640
+ let filteredPermits = filterPermitsByView();
641
+
642
+ if (filteredPermits.length === 0) {
643
+ const tr = document.createElement('tr');
644
+ const td = document.createElement('td');
645
+ td.colSpan = 7;
646
+ td.style.textAlign = 'center';
647
+ td.style.color = '#777';
648
+ td.textContent = {
649
+ active: 'No active permits found.',
650
+ nonactive: 'No non-active permits found.',
651
+ complete: 'No completed permits found.'
652
+ }[currentView] || 'No permits found.';
653
+ tr.appendChild(td);
654
+ permitsTableBody.appendChild(tr);
655
+ return;
656
+ }
657
+ filteredPermits.forEach(p => {
658
+ const tr = document.createElement('tr');
659
+ tr.tabIndex = 0;
660
+ tr.setAttribute('data-id', p.id);
661
+
662
+ let td = document.createElement('td');
663
+ td.textContent = p.id;
664
+ tr.appendChild(td);
665
+
666
+ td = document.createElement('td');
667
+ td.textContent = p.workTitle;
668
+ tr.appendChild(td);
669
+
670
+ td = document.createElement('td');
671
+ td.textContent = p.responsiblePerson;
672
+ tr.appendChild(td);
673
+
674
+ td = document.createElement('td');
675
+ td.textContent = p.location;
676
+ tr.appendChild(td);
677
+
678
+ td = document.createElement('td');
679
+ td.textContent = formatDateTimeLocal(p.startDate);
680
+ tr.appendChild(td);
681
+
682
+ td = document.createElement('td');
683
+ td.textContent = formatDateTimeLocal(p.endDate);
684
+ tr.appendChild(td);
685
+
686
+ // Map new statuses to style classes
687
+ let statusClass = 'status-' + p.status.toLowerCase().replace(/\s/g, '');
688
+ // Non-Active (custom style)
689
+ if(currentView === 'nonactive') statusClass = 'status-nonactive';
690
+ td = document.createElement('td');
691
+ td.textContent = p.status;
692
+ td.classList.add(statusClass);
693
+ tr.appendChild(td);
694
+
695
+ tr.addEventListener('click', () => openPermitModal(p.id));
696
+ tr.addEventListener('keypress', e => {
697
+ if(e.key === 'Enter' || e.key === ' ') {
698
+ e.preventDefault();
699
+ openPermitModal(p.id);
700
+ }
701
+ });
702
+
703
+ permitsTableBody.appendChild(tr);
704
+ });
705
+ }
706
+
707
+ // Render approval tab table rows
708
+ function renderApprovalPermits() {
709
+ approvalTableBody.innerHTML = '';
710
+ let filteredPermits = filterPermitsByApprovalView();
711
+
712
+ if (filteredPermits.length === 0) {
713
+ const tr = document.createElement('tr');
714
+ const td = document.createElement('td');
715
+ td.colSpan = 8;
716
+ td.style.textAlign = 'center';
717
+ td.style.color = '#777';
718
+ td.textContent = {
719
+ pending: 'No permits pending approval.',
720
+ approved: 'No approved permits found.',
721
+ rejected: 'No rejected permits found.'
722
+ }[currentApprovalView] || 'No permits found.';
723
+ tr.appendChild(td);
724
+ approvalTableBody.appendChild(tr);
725
+ return;
726
+ }
727
+
728
+ filteredPermits.forEach(p => {
729
+ const tr = document.createElement('tr');
730
+ tr.tabIndex = 0;
731
+ tr.setAttribute('data-id', p.id);
732
+
733
+ let td = document.createElement('td');
734
+ td.textContent = p.id;
735
+ tr.appendChild(td);
736
+
737
+ td = document.createElement('td');
738
+ td.textContent = p.workTitle;
739
+ tr.appendChild(td);
740
+
741
+ td = document.createElement('td');
742
+ td.textContent = p.responsiblePerson;
743
+ tr.appendChild(td);
744
+
745
+ td = document.createElement('td');
746
+ td.textContent = p.location;
747
+ tr.appendChild(td);
748
+
749
+ td = document.createElement('td');
750
+ td.textContent = formatDateTimeLocal(p.startDate);
751
+ tr.appendChild(td);
752
+
753
+ td = document.createElement('td');
754
+ td.textContent = formatDateTimeLocal(p.endDate);
755
+ tr.appendChild(td);
756
+
757
+ td = document.createElement('td');
758
+ td.textContent = p.status;
759
+ td.classList.add('status-'+p.status.toLowerCase().replace(/\s/g, ''));
760
+ tr.appendChild(td);
761
+
762
+ td = document.createElement('td');
763
+ if(p.status === 'Pending Approval') {
764
+ const btnApprove = document.createElement('button');
765
+ btnApprove.textContent = 'Approve';
766
+ btnApprove.className = 'btn-approve';
767
+ btnApprove.addEventListener('click', (e) => {
768
+ e.stopPropagation();
769
+ approvePermit(p.id);
770
+ });
771
+ td.appendChild(btnApprove);
772
+
773
+ const btnReject = document.createElement('button');
774
+ btnReject.textContent = 'Reject';
775
+ btnReject.className = 'btn-reject';
776
+ btnReject.style.marginLeft = '10px';
777
+ btnReject.addEventListener('click', (e) => {
778
+ e.stopPropagation();
779
+ rejectPermit(p.id);
780
+ });
781
+ td.appendChild(btnReject);
782
+ } else if(p.status === 'Approved' || p.status === 'In Progress') {
783
+ const btnPrint = document.createElement('button');
784
+ btnPrint.textContent = 'Print';
785
+ btnPrint.className = 'btn-print';
786
+ btnPrint.addEventListener('click', (e) => {
787
+ e.stopPropagation();
788
+ openPrintModal(p.id);
789
+ });
790
+ td.appendChild(btnPrint);
791
+ } else {
792
+ td.textContent = '-';
793
+ }
794
+ tr.appendChild(td);
795
+
796
+ tr.addEventListener('click', () => openPermitModal(p.id));
797
+ tr.addEventListener('keypress', e => {
798
+ if(e.key === 'Enter' || e.key === ' ') {
799
+ e.preventDefault();
800
+ openPermitModal(p.id);
801
+ }
802
+ });
803
+
804
+ approvalTableBody.appendChild(tr);
805
+ });
806
+ }
807
+
808
+ // Open permit modal with details
809
+ function openPermitModal(permitId) {
810
+ currentEditingPermitId = permitId;
811
+ const permit = permits.find(p => p.id === permitId);
812
+ if (!permit) return;
813
+
814
+ modalTitle.textContent = `Permit Details - ${permit.id}`;
815
+ modalDesc.innerHTML = '';
816
+
817
+ // Permit basic info display
818
+ const infoList = document.createElement('dl');
819
+ infoList.style.marginBottom = '20px';
820
+
821
+ function addInfoItem(label, value) {
822
+ const dt = document.createElement('dt');
823
+ dt.textContent = label;
824
+ dt.style.fontWeight = '600';
825
+ dt.style.marginTop = '8px';
826
+ const dd = document.createElement('dd');
827
+ dd.textContent = value;
828
+ dd.style.marginLeft = '0';
829
+ dd.style.marginBottom = '8px';
830
+ infoList.appendChild(dt);
831
+ infoList.appendChild(dd);
832
+ }
833
+
834
+ addInfoItem('Work Title:', permit.workTitle);
835
+ addInfoItem('Work Description:', permit.workDescription);
836
+ addInfoItem('Location:', permit.location);
837
+ addInfoItem('Responsible Person:', permit.responsiblePerson);
838
+ addInfoItem('Start Date & Time:', formatDateTimeLocal(permit.startDate));
839
+ addInfoItem('Expected End Date & Time:', formatDateTimeLocal(permit.endDate));
840
+ addInfoItem('Status:', permit.status);
841
+ addInfoItem('Approval Status:', ['Pending Approval', 'Approved', 'Rejected'].includes(permit.status) ? permit.status : 'Not submitted for approval');
842
+ addInfoItem('Safety Checks:', permit.safetyChecks.length > 0 ? permit.safetyChecks.join(', ') : 'None');
843
+ addInfoItem('Comments:', permit.comments.length > 0 ? permit.comments.join('; ') : 'None');
844
+
845
+ modalDesc.appendChild(infoList);
846
+
847
+ // Monitoring and update form in modal (only if permit not closed or rejected)
848
+ if (permit.status !== 'Closed' && permit.status !== 'Rejected') {
849
+ const formUpdate = document.createElement('form');
850
+ formUpdate.id = 'monitorForm';
851
+
852
+ // Safety Checks Completed (checkboxes)
853
+ if(permit.safetyChecks.length>0){
854
+ const checksLabel = document.createElement('label');
855
+ checksLabel.textContent = 'Mark Safety Checks Completed:';
856
+ checksLabel.style.fontWeight = '600';
857
+ formUpdate.appendChild(checksLabel);
858
+
859
+ permit.safetyChecks.forEach((check,idx)=>{
860
+ const div = document.createElement('div');
861
+ div.style.marginBottom = '6px';
862
+
863
+ const input = document.createElement('input');
864
+ input.type = 'checkbox';
865
+ input.id = 'check_'+idx;
866
+ input.name = 'safetyCheck';
867
+ input.value = check;
868
+ input.checked = permit.completedSafetyChecks.includes(check);
869
+
870
+ const label = document.createElement('label');
871
+ label.htmlFor = 'check_'+idx;
872
+ label.textContent = ' ' + check;
873
+
874
+ div.appendChild(input);
875
+ div.appendChild(label);
876
+ formUpdate.appendChild(div);
877
+ });
878
+ }
879
+
880
+ // Add comment
881
+ const commentLabel = document.createElement('label');
882
+ commentLabel.htmlFor = 'newComment';
883
+ commentLabel.textContent = 'Add Monitoring Comment:';
884
+ commentLabel.style.fontWeight = '600';
885
+ commentLabel.style.marginTop = '12px';
886
+ formUpdate.appendChild(commentLabel);
887
+
888
+ const commentInput = document.createElement('textarea');
889
+ commentInput.id = 'newComment';
890
+ commentInput.placeholder = 'Add notes or observations here...';
891
+ commentInput.rows = 3;
892
+ formUpdate.appendChild(commentInput);
893
+
894
+ // Status update buttons
895
+ const btnContainer = document.createElement('div');
896
+ btnContainer.style.marginTop = '18px';
897
+ btnContainer.style.display = 'flex';
898
+ btnContainer.style.gap = '12px';
899
+
900
+ // Depending on status, show possible actions
901
+ if (permit.status === 'Created') {
902
+ const submitApprovalBtn = document.createElement('button');
903
+ submitApprovalBtn.type = 'button';
904
+ submitApprovalBtn.textContent = 'Submit for Approval';
905
+ submitApprovalBtn.style.backgroundColor = '#0066cc';
906
+ submitApprovalBtn.style.color = 'white';
907
+ submitApprovalBtn.addEventListener('click', () => submitForApproval(permit.id));
908
+ btnContainer.appendChild(submitApprovalBtn);
909
+ }
910
+ /* Approved permits are Active now, so allow close and monitoring */
911
+ if (permit.status === 'Approved' || permit.status === 'In Progress') {
912
+ const closeBtn = document.createElement('button');
913
+ closeBtn.type = 'button';
914
+ closeBtn.textContent = 'Close Permit';
915
+ closeBtn.style.backgroundColor = '#308446';
916
+ closeBtn.style.color = 'white';
917
+ closeBtn.addEventListener('click', () => updatePermitStatus('Closed'));
918
+ btnContainer.appendChild(closeBtn);
919
+ }
920
+
921
+ const saveBtn = document.createElement('button');
922
+ saveBtn.type = 'submit';
923
+ saveBtn.textContent = 'Save Monitoring Updates';
924
+ btnContainer.appendChild(saveBtn);
925
+
926
+ formUpdate.appendChild(btnContainer);
927
+
928
+ formUpdate.addEventListener('submit', (e) => {
929
+ e.preventDefault();
930
+ saveMonitoringData();
931
+ });
932
+
933
+ modalDesc.appendChild(formUpdate);
934
+ } else if (permit.status === 'Rejected') {
935
+ const rejectedNote = document.createElement('p');
936
+ rejectedNote.textContent = 'This permit has been rejected and cannot be updated.';
937
+ rejectedNote.style.fontStyle = 'italic';
938
+ rejectedNote.style.color = '#cc3b3b';
939
+ modalDesc.appendChild(rejectedNote);
940
+ } else {
941
+ const closedNote = document.createElement('p');
942
+ closedNote.textContent = 'This permit is closed and can no longer be updated.';
943
+ closedNote.style.fontStyle = 'italic';
944
+ closedNote.style.color = '#308446';
945
+ modalDesc.appendChild(closedNote);
946
+ }
947
+
948
+ permitModal.classList.add('active');
949
+ modalDesc.focus();
950
+ }
951
+
952
+ // Save monitoring data from modal form
953
+ function saveMonitoringData() {
954
+ const permit = permits.find(p => p.id === currentEditingPermitId);
955
+ if (!permit) return;
956
+
957
+ // Update completed safety checks
958
+ const checkboxes = permitModal.querySelectorAll('input[name="safetyCheck"]');
959
+ permit.completedSafetyChecks = [];
960
+ checkboxes.forEach(cb => {
961
+ if(cb.checked){
962
+ permit.completedSafetyChecks.push(cb.value);
963
+ }
964
+ });
965
+
966
+ // Add new comment if any
967
+ const newComment = permitModal.querySelector('#newComment').value.trim();
968
+ if(newComment.length > 0){
969
+ permit.comments.push(newComment);
970
+ permitModal.querySelector('#newComment').value = '';
971
+ }
972
+
973
+ savePermits();
974
+ renderPermits();
975
+ renderApprovalPermits();
976
+
977
+ // Refresh modal to show new comments and updated info
978
+ openPermitModal(permit.id);
979
+ }
980
+
981
+ // Update permit status from modal buttons
982
+ function updatePermitStatus(newStatus) {
983
+ const permit = permits.find(p => p.id === currentEditingPermitId);
984
+ if (!permit) return;
985
+
986
+ permit.status = newStatus;
987
+ if(newStatus === 'In Progress'){
988
+ permit.monitoringStart = new Date().toISOString();
989
+ }
990
+ if(newStatus === 'Approved'){
991
+ permit.approvedDate = new Date().toISOString();
992
+ // After approval, consider active/In Progress
993
+ permit.status = 'Approved';
994
+ }
995
+ if(newStatus === 'Closed'){
996
+ permit.closedAt = new Date().toISOString();
997
+ }
998
+
999
+ savePermits();
1000
+ renderPermits();
1001
+ renderApprovalPermits();
1002
+ openPermitModal(permit.id);
1003
+ }
1004
+
1005
+ // Submit permit for approval
1006
+ function submitForApproval(id) {
1007
+ const permit = permits.find(p => p.id === id);
1008
+ if (!permit) return;
1009
+ if (permit.status !== 'Created') {
1010
+ alert('Only permits in "Created" status can be submitted for approval.');
1011
+ return;
1012
+ }
1013
+ permit.status = 'Pending Approval';
1014
+ permit.submittedDate = new Date().toISOString();
1015
+ savePermits();
1016
+ alert('Permit submitted for leader approval.');
1017
+ renderPermits();
1018
+ renderApprovalPermits();
1019
+ activateMainTab('approval');
1020
+ activateApprovalStatusTab('pending');
1021
+ }
1022
+
1023
+ // Approve permit
1024
+ function approvePermit(id) {
1025
+ const permit = permits.find(p => p.id === id);
1026
+ if (!permit) return;
1027
+ if (permit.status !== 'Pending Approval') {
1028
+ alert('Only permits pending approval can be approved.');
1029
+ return;
1030
+ }
1031
+ permit.status = 'Approved';
1032
+ permit.approvedDate = new Date().toISOString();
1033
+ savePermits();
1034
+ alert('Permit approved.');
1035
+ renderApprovalPermits();
1036
+ renderPermits();
1037
+ }
1038
+
1039
+ // Reject permit
1040
+ function rejectPermit(id) {
1041
+ const permit = permits.find(p => p.id === id);
1042
+ if (!permit) return;
1043
+ if (permit.status !== 'Pending Approval') {
1044
+ alert('Only permits pending approval can be rejected.');
1045
+ return;
1046
+ }
1047
+ permit.status = 'Rejected';
1048
+ savePermits();
1049
+ alert('Permit rejected.');
1050
+ renderApprovalPermits();
1051
+ renderPermits();
1052
+ }
1053
+
1054
+ // Open printable permit modal
1055
+ function openPrintModal(id) {
1056
+ const permit = permits.find(p => p.id === id);
1057
+ if (!permit) return;
1058
+
1059
+ let content = `
1060
+ <h2>Permit to Work (PTW)</h2>
1061
+ <dl>
1062
+ <dt>Permit ID:</dt><dd>${escapeHtml(permit.id)}</dd>
1063
+ <dt>Work Title:</dt><dd>${escapeHtml(permit.workTitle)}</dd>
1064
+ <dt>Work Description:</dt><dd>${escapeHtml(permit.workDescription)}</dd>
1065
+ <dt>Location:</dt><dd>${escapeHtml(permit.location)}</dd>
1066
+ <dt>Responsible Person:</dt><dd>${escapeHtml(permit.responsiblePerson)}</dd>
1067
+ <dt>Start Date & Time:</dt><dd>${formatDateTimeLocal(permit.startDate)}</dd>
1068
+ <dt>Expected End Date & Time:</dt><dd>${formatDateTimeLocal(permit.endDate)}</dd>
1069
+ <dt>Status:</dt><dd>${escapeHtml(permit.status)}</dd>
1070
+ <dt>Safety Checks:</dt><dd>${permit.safetyChecks.length > 0 ? escapeHtml(permit.safetyChecks.join(', ')) : 'None'}</dd>
1071
+ <dt>Comments:</dt><dd>${permit.comments.length > 0 ? escapeHtml(permit.comments.join('\n')) : 'None'}</dd>
1072
+ </dl>
1073
+ `;
1074
+ printModalDesc.innerHTML = content;
1075
+ printModal.classList.add('active');
1076
+ printModalDesc.focus();
1077
+ }
1078
+
1079
+ // Close modal functions
1080
+ function closeModal() {
1081
+ permitModal.classList.remove('active');
1082
+ currentEditingPermitId = null;
1083
+ }
1084
+ function closePrintModal() {
1085
+ printModal.classList.remove('active');
1086
+ }
1087
+
1088
+ // Print permit
1089
+ function printPermit() {
1090
+ const printWindow = window.open('', '', 'width=800,height=600');
1091
+ if (!printWindow) {
1092
+ alert('Pop-up blocked. Please allow pop-ups for this website to print.');
1093
+ return;
1094
+ }
1095
+ printWindow.document.write(`
1096
+ <html>
1097
+ <head>
1098
+ <title>Print Permit</title>
1099
+ <style>
1100
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 20px; }
1101
+ h2 { text-align: center; margin-bottom: 20px; }
1102
+ dl { display: grid; grid-template-columns: 1fr 2fr; gap: 8px 20px; }
1103
+ dt { font-weight: 700; color: #00467f; }
1104
+ dd { margin: 0 0 8px 0; white-space: pre-line; }
1105
+ </style>
1106
+ </head>
1107
+ <body>
1108
+ ${printModalDesc.innerHTML}
1109
+ <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>
1110
+ </html>
1111
+ `);
1112
+ printWindow.document.close();
1113
+ printWindow.focus();
1114
+ printWindow.print();
1115
+ printWindow.close();
1116
+ }
1117
+
1118
+ // Create permit form submission
1119
+ createForm.addEventListener('submit', (e) => {
1120
+ e.preventDefault();
1121
+
1122
+ const start = createForm.startDate.value;
1123
+ const end = createForm.endDate.value;
1124
+ if(start >= end){
1125
+ alert('Start date/time must be before end date/time.');
1126
+ return;
1127
+ }
1128
+
1129
+ const newPermit = {
1130
+ id: generatePermitID(),
1131
+ workTitle: createForm.workTitle.value.trim(),
1132
+ workDescription: createForm.workDescription.value.trim(),
1133
+ location: createForm.location.value.trim(),
1134
+ responsiblePerson: createForm.responsiblePerson.value.trim(),
1135
+ startDate: start,
1136
+ endDate: end,
1137
+ safetyChecks: createForm.safetyChecks.value.trim() ? createForm.safetyChecks.value.split(',').map(s => s.trim()).filter(s => s.length > 0) : [],
1138
+ completedSafetyChecks: [],
1139
+ comments: [],
1140
+ status: 'Pending Approval',
1141
+ monitoringStart: null,
1142
+ closedAt: null,
1143
+ submittedDate: new Date().toISOString()
1144
+ };
1145
+
1146
+ permits.unshift(newPermit);
1147
+ savePermits();
1148
+ renderPermits();
1149
+ renderApprovalPermits();
1150
+
1151
+ activateMainTab('approval');
1152
+ activateApprovalStatusTab('pending');
1153
+
1154
+ createForm.reset();
1155
+ });
1156
+
1157
+ // Filter inputs event listeners
1158
+ monitorFilterInput.addEventListener('input', () => {
1159
+ renderPermits();
1160
+ });
1161
+
1162
+ approvalFilterInput.addEventListener('input', () => {
1163
+ renderApprovalPermits();
1164
+ });
1165
+
1166
+ modalCloseBtn.addEventListener('click', closeModal);
1167
+ permitModal.addEventListener('click', (e) => {
1168
+ if(e.target === permitModal){
1169
+ closeModal();
1170
+ }
1171
+ });
1172
+
1173
+ printModalCloseBtn.addEventListener('click', closePrintModal);
1174
+ printModal.addEventListener('click', (e) => {
1175
+ if(e.target === printModal){
1176
+ closePrintModal();
1177
+ }
1178
+ });
1179
+ printBtn.addEventListener('click', printPermit);
1180
+
1181
+ // Main tab switching
1182
+ mainTabs.forEach(tab => {
1183
+ tab.addEventListener('click', () => {
1184
+ if(tab.classList.contains('active')) return;
1185
+ deactivateAllMainTabs();
1186
+ activateMainTab(tab.getAttribute('data-tab'));
1187
+ });
1188
+ tab.addEventListener('keypress', e => {
1189
+ if(e.key === 'Enter' || e.key === ' ') {
1190
+ e.preventDefault();
1191
+ tab.click();
1192
+ }
1193
+ });
1194
+ });
1195
+
1196
+ function deactivateAllMainTabs() {
1197
+ mainTabs.forEach(tab => {
1198
+ tab.classList.remove('active');
1199
+ tab.setAttribute('aria-selected', 'false');
1200
+ tab.tabIndex = -1;
1201
+ });
1202
+ createTabPanel.hidden = true;
1203
+ monitorTabPanel.hidden = true;
1204
+ approvalTabPanel.hidden = true;
1205
+ }
1206
+ function activateMainTab(tabName) {
1207
+ deactivateAllMainTabs();
1208
+ const tab = mainTabs.find(t => t.getAttribute('data-tab') === tabName);
1209
+ if(!tab) return;
1210
+ tab.classList.add('active');
1211
+ tab.setAttribute('aria-selected', 'true');
1212
+ tab.tabIndex = 0;
1213
+ if(tabName === 'create') {
1214
+ createTabPanel.hidden = false;
1215
+ createTabPanel.focus();
1216
+ } else if(tabName === 'monitor') {
1217
+ monitorTabPanel.hidden = false;
1218
+ monitorTabPanel.focus();
1219
+ } else if(tabName === 'approval') {
1220
+ approvalTabPanel.hidden = false;
1221
+ approvalTabPanel.focus();
1222
+ }
1223
+ }
1224
+
1225
+ // Status sub-tab switching within Monitor tab
1226
+ statusTabs.forEach(tab => {
1227
+ tab.addEventListener('click', () => {
1228
+ if(tab.classList.contains('active')) return;
1229
+ deactivateAllStatusTabs();
1230
+ activateStatusTab(tab.getAttribute('data-view'));
1231
+ });
1232
+ tab.addEventListener('keypress', e => {
1233
+ if(e.key === 'Enter' || e.key === ' ') {
1234
+ e.preventDefault();
1235
+ tab.click();
1236
+ }
1237
+ });
1238
+ });
1239
+
1240
+ function deactivateAllStatusTabs() {
1241
+ statusTabs.forEach(tab => {
1242
+ tab.classList.remove('active');
1243
+ tab.setAttribute('aria-selected', 'false');
1244
+ tab.tabIndex = -1;
1245
+ });
1246
+ }
1247
+ function activateStatusTab(viewName) {
1248
+ deactivateAllStatusTabs();
1249
+ const tab = statusTabs.find(t => t.getAttribute('data-view') === viewName);
1250
+ if(!tab) return;
1251
+ tab.classList.add('active');
1252
+ tab.setAttribute('aria-selected', 'true');
1253
+ tab.tabIndex = 0;
1254
+ currentView = viewName;
1255
+ renderPermits();
1256
+ }
1257
+
1258
+ // Status sub-tab switching within Approval tab
1259
+ approvalStatusTabs.forEach(tab => {
1260
+ tab.addEventListener('click', () => {
1261
+ if(tab.classList.contains('active')) return;
1262
+ deactivateAllApprovalStatusTabs();
1263
+ activateApprovalStatusTab(tab.getAttribute('data-view'));
1264
+ });
1265
+ tab.addEventListener('keypress', e => {
1266
+ if(e.key === 'Enter' || e.key === ' ') {
1267
+ e.preventDefault();
1268
+ tab.click();
1269
+ }
1270
+ });
1271
+ });
1272
+
1273
+ function deactivateAllApprovalStatusTabs() {
1274
+ approvalStatusTabs.forEach(tab => {
1275
+ tab.classList.remove('active');
1276
+ tab.setAttribute('aria-selected', 'false');
1277
+ tab.tabIndex = -1;
1278
+ });
1279
+ }
1280
+ function activateApprovalStatusTab(viewName) {
1281
+ deactivateAllApprovalStatusTabs();
1282
+ const tab = approvalStatusTabs.find(t => t.getAttribute('data-view') === viewName);
1283
+ if(!tab) return;
1284
+ tab.classList.add('active');
1285
+ tab.setAttribute('aria-selected', 'true');
1286
+ tab.tabIndex = 0;
1287
+ currentApprovalView = viewName;
1288
+ renderApprovalPermits();
1289
+ }
1290
+
1291
+ // Initial render & setup - default to "Create Permit" tab active and status tab active in monitor
1292
+ activateMainTab('create');
1293
+ activateStatusTab('active');
1294
+ activateApprovalStatusTab('pending');
1295
+
1296
+ renderPermits();
1297
+ renderApprovalPermits();
1298
+ })();
1299
+ </script>
1300
+ </body>
1301
  </html>
1302
+
prompts.txt ADDED
File without changes