oceanhelm 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1778 @@
1
+ <template>
2
+ <div v-if="ready">
3
+ <div>
4
+ <nav v-show="!showReport">
5
+ <button v-for="section in sections" :key="section.id" class="nav-btn"
6
+ :class="{ active: activeSection === section.id }" @click="handleSectionClick(section)">
7
+ {{ section.icon }} {{ section.name }}
8
+ </button>
9
+ </nav>
10
+
11
+ <div class="content" v-show="!showReport">
12
+ <!-- Maintenance Tasks Form -->
13
+ <section :class="['form-section', { active: activeSection === 'maintenance' }]"
14
+ v-show="activeSection === 'maintenance'">
15
+ <h2>🛠️ Maintenance Tasks</h2>
16
+ <!-- Loading indicator -->
17
+ <div class="loading-container" v-if="isLoading">
18
+ <div class="loading-spinner"></div>
19
+ <p>Loading checklist...</p>
20
+ </div>
21
+ <form v-if="!isLoading">
22
+ <div class="container">
23
+ <div class="d-flex justify-content-between align-items-center">
24
+ <h1>Maintenance Checklist</h1>
25
+ <button v-if="showAddTaskButton" class="btn btn-outline-custom" @click.prevent="addTask()">
26
+ Manually Add Task
27
+ <i class="fas fa-plus"></i>
28
+ </button>
29
+ </div>
30
+
31
+ <div class="progress-container">
32
+ <div class="progress-info">
33
+ Progress: {{ progress }}% ({{ completedCount.length }}/{{ checklists.length }})
34
+ </div>
35
+ <div class="progress-bar">
36
+ <div class="progress-fill" :style="{ width: progress + '%' }"></div>
37
+ </div>
38
+ </div>
39
+
40
+ <ul class="checklist">
41
+ <li v-for="checklist in checklists" :key="checklist.id" class="checklist-item">
42
+ <div class="checkbox" :class="{ 'checked': checklist.completed }"
43
+ @click="toggleTask(checklist)">
44
+ <span v-if="checklist.completed">✓</span>
45
+ </div>
46
+ <span class="task-text" :class="{ 'completed': checklist.completed }"
47
+ @click="toggleTask(checklist)">
48
+ {{ checklist.text }}
49
+ </span>
50
+ <button v-if="showAddTaskButton" class="delete-btn" @click="deleteTask(checklist.id)"
51
+ title="Delete task">
52
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
53
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
54
+ <polyline points="3,6 5,6 21,6"></polyline>
55
+ <path
56
+ d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2">
57
+ </path>
58
+ <line x1="10" y1="11" x2="10" y2="17"></line>
59
+ <line x1="14" y1="11" x2="14" y2="17"></line>
60
+ </svg>
61
+ </button>
62
+ </li>
63
+ </ul>
64
+
65
+ <div v-if="after">
66
+ <label>Upload Image Evidence</label>
67
+ <ul class="checklist">
68
+ <li class="checklist-item">
69
+ <span class="task-text">
70
+ Image Evidence Already Uploaded
71
+ </span>
72
+ <button type="button" class="delete-btn" @click="deleteEvidence()"
73
+ title="Delete Image">
74
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
75
+ stroke="currentColor" stroke-width="2" stroke-linecap="round"
76
+ stroke-linejoin="round">
77
+ <polyline points="3,6 5,6 21,6"></polyline>
78
+ <path
79
+ d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2">
80
+ </path>
81
+ <line x1="10" y1="11" x2="10" y2="17"></line>
82
+ <line x1="14" y1="11" x2="14" y2="17"></line>
83
+ </svg>
84
+ </button>
85
+ </li>
86
+ </ul>
87
+ </div>
88
+
89
+ <div class="form-group" v-else :key="refreshKey || 'default'">
90
+ <label>Upload Image Evidence</label>
91
+ <p>You can only upload one image evidence here.</p>
92
+ <div class="attachment-area">
93
+ <p>{{ fileText }}</p>
94
+ <input type="file" id="evidence-files" class="file-input" @change="handleImg">
95
+ <label for="evidence-files" class="file-label">Browse Files</label>
96
+ </div>
97
+ </div>
98
+
99
+ <div class="status" v-if="completedCount === checklists.length">
100
+ All tasks completed! ✅
101
+ </div>
102
+
103
+ <button class="reset-button" @click.prevent="resetTasks">{{ checklistButtonLabel }}</button>
104
+ </div>
105
+ </form>
106
+ </section>
107
+
108
+ <!-- Maintenance Schedule Form -->
109
+ <section :class="['form-section', { active: activeSection === 'schedule' }]"
110
+ v-show="activeSection === 'schedule'">
111
+ <h2>📅 Maintenance Schedule</h2>
112
+ <form>
113
+ <div class="form-group">
114
+ <label for="task-name">Task Name</label>
115
+ <input type="text" id="task-name" v-model="form.taskName" required>
116
+ </div>
117
+
118
+ <div class="form-group">
119
+ <label for="task-description">Description</label>
120
+ <textarea id="task-description" v-model="form.description" required></textarea>
121
+ </div>
122
+
123
+ <div class="input-group">
124
+ <div class="form-group">
125
+ <label for="maintenance-type">Maintenance Type</label>
126
+ <select id="maintenance-type" v-model="form.maintenanceType" required>
127
+ <option value="">-- Select Type --</option>
128
+ <option value="preventive">Preventive</option>
129
+ <option value="corrective">Corrective</option>
130
+ <option value="predictive">Predictive</option>
131
+ <option value="condition">Condition-Based</option>
132
+ </select>
133
+ </div>
134
+
135
+ <div class="form-group">
136
+ <label for="component">Component/System</label>
137
+ <select id="component" v-model="form.component" required>
138
+ <option value="">-- Select Component --</option>
139
+ <option value="engine">Engine</option>
140
+ <option value="hull">Hull</option>
141
+ <option value="electronics">Electronics</option>
142
+ <option value="deck">Deck Machinery</option>
143
+ <option value="plumbing">Plumbing</option>
144
+ <option value="electrical">Electrical</option>
145
+ <option value="hvac">HVAC</option>
146
+ <option value="safety">Safety Systems</option>
147
+ <option value="Other">Other</option>
148
+ </select>
149
+ <input v-if="form.component === 'Other'" type="text"
150
+ placeholder="Enter custom component/system" v-model="form.customComponent"
151
+ style="margin-top: 8px;">
152
+ </div>
153
+ </div>
154
+
155
+ <div class="input-group">
156
+ <div class="form-group">
157
+ <label for="priority">Priority</label>
158
+ <select id="priority" v-model="form.priority" required>
159
+ <option value="low">Low</option>
160
+ <option value="medium">Medium</option>
161
+ <option value="high">High</option>
162
+ <option value="critical">Critical</option>
163
+ </select>
164
+ </div>
165
+ <div class="form-group">
166
+ <label for="status">Status</label>
167
+ <input type="text" id="status" v-model="form.status" readonly>
168
+ </div>
169
+ </div>
170
+
171
+ <div class="input-group">
172
+ <div class="form-group">
173
+ <label for="estimated-hours">Estimated Hours</label>
174
+ <input type="number" id="estimated-hours" v-model="form.estimatedHours" min="0" step="0.5">
175
+ </div>
176
+ </div>
177
+
178
+ <div class="form-group">
179
+ <label for="assigned-to">Assigned To</label>
180
+ <select id="assigned-to" v-model="form.assignedTo">
181
+ <option value="">-- Select Personnel --</option>
182
+ <option v-for="member in vesselCrew" :key="member.id"
183
+ :value="`${member.name} - ${member.role}`">
184
+ {{ member.name }} - {{ member.role }}
185
+ </option>
186
+ </select>
187
+ </div>
188
+
189
+ <div class="input-group">
190
+ <div class="form-group">
191
+ <label for="recurrence-type">Recurrence</label>
192
+ <select id="recurrence-type" v-model="form.recurrence" required>
193
+ <option value="once">One-time</option>
194
+ <option value="daily">Daily</option>
195
+ <option value="weekly">Weekly</option>
196
+ <option value="monthly">Monthly</option>
197
+ <option value="quarterly">Quarterly</option>
198
+ <option value="semi-annual">Semi-annually</option>
199
+ <option value="annual">Annually</option>
200
+ <option value="custom">Custom Interval</option>
201
+ </select>
202
+ </div>
203
+ </div>
204
+
205
+ <div class="input-group">
206
+ <div class="form-group">
207
+ <label for="last-performed">Last Performed Date</label>
208
+ <input type="date" id="last-performed" v-model="form.lastPerformed">
209
+ </div>
210
+
211
+ <div class="form-group">
212
+ <label for="next-due">Due Date</label>
213
+ <input type="date" id="next-due" v-model="form.nextDue" required>
214
+ </div>
215
+ </div>
216
+
217
+ <div class="form-group">
218
+ <label>Notifications</label>
219
+ <div class="checkbox-group">
220
+ <div class="checkbox-item">
221
+ <input type="checkbox" id="notify-email" v-model="form.notifyEmail">
222
+ <label for="notify-email">Email Notification</label>
223
+ </div>
224
+ <div class="checkbox-item">
225
+ <input type="checkbox" id="notify-sms" v-model="form.notifySms">
226
+ <label for="notify-sms">SMS Notification</label>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <div class="input-group">
232
+ <div class="form-group">
233
+ <label for="reminder-days">Reminder (Days Before)</label>
234
+ <select id="reminder-days" v-model="form.reminderDays">
235
+ <option value="1">1 day</option>
236
+ <option value="3">3 days</option>
237
+ <option value="7">1 week</option>
238
+ <option value="14">2 weeks</option>
239
+ <option value="30">1 month</option>
240
+ </select>
241
+ </div>
242
+ </div>
243
+
244
+ <div class="form-group">
245
+ <label for="schedule-notes">Notes</label>
246
+ <textarea id="schedule-notes" v-model="form.notes"></textarea>
247
+ </div>
248
+
249
+ <div class="form-group">
250
+ <label>Attachments</label>
251
+ <div class="attachment-area">
252
+ <p>{{ imgText }}</p>
253
+ <input type="file" id="maintenance-files" class="file-input" @change="handleFiles" multiple>
254
+ <label for="maintenance-files" class="file-label">Browse Files</label>
255
+ </div>
256
+ </div>
257
+
258
+ <div class="action-buttons">
259
+ <button type="button" class="btn btn-primary" @click.prevent="saveSchedule"
260
+ :disabled="isSaving">
261
+ {{ isSaving ? 'Saving...' : 'Save Schedule' }}
262
+ </button>
263
+ </div>
264
+ </form>
265
+ </section>
266
+
267
+ <!-- Inventory Form -->
268
+ <section :class="['form-section', { active: activeSection === 'inventory' }]"
269
+ v-show="activeSection === 'inventory'">
270
+ <h2>All Maintenance</h2>
271
+ <div class="task-table-wrapper">
272
+ <!-- Filters and Controls -->
273
+ <div class="table-controls">
274
+ <div class="filters">
275
+ <button :class="{ active: activeFilter === 'due' }" @click="setFilter('due')">Due</button>
276
+ <button :class="{ active: activeFilter === 'all' }" @click="setFilter('all')">All</button>
277
+ <button :class="{ active: activeFilter === 'completed' }"
278
+ @click="setFilter('completed')">Completed</button>
279
+ <input type="text" v-model="searchQuery" placeholder="Search..." />
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Task Table -->
284
+ <table class="task-table">
285
+ <thead>
286
+ <tr>
287
+ <th>Equipment</th>
288
+ <th>Task Name</th>
289
+ <th>Assigned To</th>
290
+ <th>Intervals</th>
291
+ <th>Remaining</th>
292
+ <th>Next Due</th>
293
+ <th>Status</th>
294
+ <th>Action</th>
295
+ </tr>
296
+ </thead>
297
+ <tbody>
298
+ <tr v-for="task in filteredTasks" :key="task.id">
299
+ <td>{{ task.component }}</td>
300
+ <td>{{ task.taskName }}</td>
301
+ <td>{{ task.assignedTo }}</td>
302
+ <td>{{ task.recurrence }}</td>
303
+ <td>{{ task.remainingDays }}</td>
304
+ <td>{{ task.nextDue }}</td>
305
+ <td>
306
+ <span :class="['status-badge', task.status.toLowerCase().replace(' ', '-')]">
307
+ {{ task.status }}
308
+ </span>
309
+ </td>
310
+ <td>
311
+ <button @click="printMaintenance(task.component)" v-if="task.status === 'Completed'"
312
+ class="status-action">Print</button>
313
+ <button @click="showMaintenance(task.component)" v-else
314
+ class="status-action">Start</button>
315
+ </td>
316
+ </tr>
317
+ </tbody>
318
+ </table>
319
+
320
+ <!-- Empty schedule -->
321
+ <div v-if="!tasks.length">
322
+ <div class="alert alert-primary" role="alert">
323
+ <h4 class="alert-heading">Such Empty!!!</h4>
324
+ <p>You have no maintenance, because you have not scheduled any for this ship.</p>
325
+ <hr>
326
+ <p class="mb-0">Navigate to the schedule tab, to start scheduling. Or click on this button
327
+ to
328
+ <button type="button" class="btn btn-primary"
329
+ @click="switchSchedule()">Schedule</button>
330
+ </p>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </section>
335
+ </div>
336
+
337
+ <div class="report-container" ref="reportContainer" v-show="showReport">
338
+ <div class="header">
339
+ <div class="report-title">MAINTENANCE TASK REPORT</div>
340
+ </div>
341
+
342
+ <div class="report-info">
343
+ <div class="info-box">
344
+ <div class="info-label">Report Generated:</div>
345
+ <div>{{ reportDate }}</div>
346
+ </div>
347
+ <div class="info-box">
348
+ <div class="info-label">Report ID:</div>
349
+ <div>{{ reportId }}</div>
350
+ </div>
351
+ <div class="info-box">
352
+ <div class="info-label">Total Tasks:</div>
353
+ <div>{{ maintenanceTasks.length }}</div>
354
+ </div>
355
+ <div class="info-box">
356
+ <div class="info-label">Generated By:</div>
357
+ <div>OceanHelm System</div>
358
+ </div>
359
+ </div>
360
+
361
+ <div class="section">
362
+ <div class="section-title">📊 Task Summary</div>
363
+ <div class="summary-grid">
364
+ <div class="summary-card">
365
+ <div class="summary-number">1</div>
366
+ <div class="summary-label">Completed</div>
367
+ </div>
368
+ <div class="summary-card">
369
+ <div class="summary-number">0</div>
370
+ <div class="summary-label">Pending</div>
371
+ </div>
372
+ <div class="summary-card">
373
+ <div class="summary-number">0</div>
374
+ <div class="summary-label">Overdue</div>
375
+ </div>
376
+ <div class="summary-card">
377
+ <div class="summary-number">{{ totalEstimatedHours }}</div>
378
+ <div class="summary-label">Total Hours</div>
379
+ </div>
380
+ </div>
381
+ </div>
382
+
383
+ <div class="section">
384
+ <div class="section-title">🔧 Maintenance Tasks</div>
385
+ <div v-for="task in maintenanceTasks" :key="task.taskName"
386
+ :class="['task-item', getTaskStatusClass(task)]">
387
+
388
+ <div class="task-header">
389
+ <div>
390
+ <div class="task-title">
391
+ {{ task.taskName }}
392
+ <span :class="['maintenance-type', 'type-' + task.maintenanceType]">
393
+ {{ task.maintenanceType }}
394
+ </span>
395
+ </div>
396
+ <div class="task-component">Component: {{ task.component }}</div>
397
+ </div>
398
+ <span :class="['status-badge', 'status-' + task.status.toLowerCase().replace(' ', '-')]">
399
+ {{ task.status }}
400
+ </span>
401
+ </div>
402
+
403
+ <div class="task-details">
404
+ <div class="detail-item">
405
+ <div class="detail-label">Assigned To</div>
406
+ <div class="detail-value">{{ task.assignedTo }}</div>
407
+ </div>
408
+ <div class="detail-item">
409
+ <div class="detail-label">Estimated Hours</div>
410
+ <div class="detail-value">{{ task.estimatedHours }} hours</div>
411
+ </div>
412
+ <div class="detail-item">
413
+ <div class="detail-label">Last Performed</div>
414
+ <div class="detail-value">{{ formatDate(task.lastPerformed) }}</div>
415
+ </div>
416
+ <div class="detail-item">
417
+ <div class="detail-label">Next Due</div>
418
+ <div class="detail-value">{{ formatDate(task.nextDue) }}</div>
419
+ </div>
420
+ <div class="detail-item">
421
+ <div class="detail-label">Recurrence</div>
422
+ <div class="detail-value">{{ task.recurrence }}</div>
423
+ </div>
424
+ <div class="detail-item">
425
+ <div class="detail-label">Remaining Days</div>
426
+ <div class="detail-value">{{ task.remainingDays }} days</div>
427
+ </div>
428
+ </div>
429
+
430
+ <div v-if="task.description"
431
+ style="margin: 15px 0; padding: 10px; background: white; border-radius: 5px;">
432
+ <div class="detail-label">Description</div>
433
+ <div class="detail-value">{{ task.description }}</div>
434
+ </div>
435
+
436
+ <div v-if="task.notes"
437
+ style="margin: 15px 0; padding: 10px; background: white; border-radius: 5px;">
438
+ <div class="detail-label">Notes</div>
439
+ <div class="detail-value">{{ task.notes }}</div>
440
+ </div>
441
+
442
+ <div v-if="task.checklistProgress && task.checklistProgress.length > 0" class="checklist-progress">
443
+ <div class="detail-label">Checklist Progress</div>
444
+ <div class="progress-bar">
445
+ <div class="progress-fill" :style="{ width: getChecklistProgress(task) + '%' }"></div>
446
+ </div>
447
+ <div style="font-size: 0.9em; color: #666; margin-top: 5px;">
448
+ {{ getCompletedChecklistItems(task) }} of {{ task.checklistProgress.length }} items
449
+ completed
450
+ ({{ getChecklistProgress(task) }}%)
451
+ </div>
452
+ <div class="checklist-items">
453
+ <div v-for="(item, index) in task.checklistProgress" :key="index" class="checklist-item">
454
+ <span class="checklist-icon">{{ item.completed ? '✅' : '⭕' }}</span>
455
+ <span>{{ item.text || 'Checklist Item ' + (index + 1) }}</span>
456
+ </div>
457
+ </div>
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ <div class="section">
463
+ <div class="section-title">📋 Recommendations</div>
464
+ <div class="info-box">
465
+ <ul>
466
+ <li v-for="recommendation in generateRecommendations()" :key="recommendation">
467
+ {{ recommendation }}
468
+ </li>
469
+ </ul>
470
+ </div>
471
+ </div>
472
+
473
+ <div class="signature-section">
474
+ <div class="signature-box">
475
+ <div><strong>Report Generated By</strong></div>
476
+ <div style="margin-top: 10px; color: #666;">OceanHelm Maintenance System</div>
477
+ </div>
478
+ <div class="signature-box">
479
+ <div><strong>Date</strong></div>
480
+ <div style="margin-top: 10px; color: #666;">{{ reportDate }}</div>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ </div>
485
+ </div>
486
+ </template>
487
+
488
+ <script>
489
+ export default {
490
+ name: 'OceanHelmMaintenance',
491
+ props: {
492
+ vesselInfo: {
493
+ type: Object,
494
+ required: true
495
+ },
496
+ tasks: {
497
+ type: Array,
498
+ default: () => []
499
+ },
500
+ vesselCrew: {
501
+ type: Array,
502
+ default: () => []
503
+ },
504
+ companyInfo: {
505
+ type: Object,
506
+ default: () => ({})
507
+ },
508
+ userProfile: {
509
+ type: Object,
510
+ required: true
511
+ }
512
+ },
513
+ emits: [
514
+ 'dashboard-navigate',
515
+ 'load-checklist',
516
+ 'save-schedule',
517
+ 'update-task',
518
+ 'upload-file',
519
+ 'delete-evidence',
520
+ 'access-denied',
521
+ 'show-message',
522
+ 'generate-checklist'
523
+ ],
524
+ data() {
525
+ return {
526
+ ready: true,
527
+ isSaving: false,
528
+ after: null,
529
+ lastSection: '',
530
+ currentTask: '',
531
+ imgText: 'Choose file to upload',
532
+ fileText: 'Drag and drop files here or',
533
+ fileattachments: {},
534
+ refreshKey: 0,
535
+ activeSection: 'inventory',
536
+ sections: [
537
+ { id: 'schedule', name: 'Schedule', icon: '📅' },
538
+ { id: 'inventory', name: 'All Maintenance', icon: '♻️' },
539
+ {
540
+ id: 'dashboard',
541
+ name: 'Dashboard',
542
+ icon: '☰',
543
+ onClick: () => this.$emit('dashboard-navigate')
544
+ }
545
+ ],
546
+ activeFilter: 'all',
547
+ searchQuery: '',
548
+ checklists: [],
549
+ isLoading: false,
550
+ form: {
551
+ taskName: '',
552
+ description: '',
553
+ maintenanceType: '',
554
+ component: '',
555
+ priority: '',
556
+ dueDate: '',
557
+ estimatedHours: null,
558
+ assignedTo: '',
559
+ recurrence: '',
560
+ lastPerformed: '',
561
+ nextDue: '',
562
+ notifyEmail: true,
563
+ notifySms: true,
564
+ reminderDays: '1',
565
+ estimatedDuration: null,
566
+ notes: '',
567
+ status: 'Soon',
568
+ email: '',
569
+ remainingDays: null,
570
+ attachments: {}
571
+ },
572
+ maintenanceTasks: [],
573
+ showReport: false,
574
+ reportDate: new Date().toLocaleDateString('en-US', {
575
+ year: 'numeric',
576
+ month: 'long',
577
+ day: 'numeric'
578
+ }),
579
+ reportId: 'MT-' + Math.random().toString(36).substr(2, 9).toUpperCase(),
580
+ };
581
+ },
582
+ watch: {
583
+ 'form.lastPerformed': 'calculateNextDue',
584
+ 'form.recurrence': 'calculateNextDue',
585
+ 'form.nextDue': function (newDate) {
586
+ this.calculateRemainingDays();
587
+ }
588
+ },
589
+ computed: {
590
+ filteredTasks() {
591
+ let result = [...this.tasks];
592
+ if (this.activeFilter === 'all') {
593
+ result = result.filter(task => task.status === 'Overdue' || task.status === 'Soon' || task.status === 'Completed');
594
+ }
595
+ else if (this.activeFilter === 'due') {
596
+ result = result.filter(task => task.status === 'Overdue' || task.status === 'Soon');
597
+ } else if (this.activeFilter === 'completed') {
598
+ result = result.filter(task => task.status === 'Completed');
599
+ }
600
+
601
+ if (this.searchQuery) {
602
+ const query = this.searchQuery.toLowerCase();
603
+ result = result.filter(task =>
604
+ task.component.toLowerCase().includes(query) ||
605
+ task.taskName.toLowerCase().includes(query) ||
606
+ task.assignedTo.toLowerCase().includes(query)
607
+ );
608
+ }
609
+
610
+ return result;
611
+ },
612
+ completedCount() {
613
+ return this.checklists.filter(item => item.completed);
614
+ },
615
+ progress() {
616
+ return Math.round((this.completedCount.length / this.checklists.length) * 100);
617
+ },
618
+ checklistButtonLabel() {
619
+ return this.lastSection === 'schedule' ? 'Approve Maintenance' : 'Save Checklist';
620
+ },
621
+ showAddTaskButton() {
622
+ return this.checklistButtonLabel !== 'Save Checklist';
623
+ },
624
+ totalEstimatedHours() {
625
+ return this.maintenanceTasks.reduce((total, task) => total + (task.estimatedHours || 0), 0);
626
+ },
627
+ },
628
+ methods: {
629
+ grantAccess(vessel) {
630
+ const profile = this.userProfile;
631
+ return profile.role === 'owner' ||
632
+ profile.role === 'staff' ||
633
+ (profile.role === 'captain' && profile.vessel === vessel);
634
+ },
635
+ deepAccess() {
636
+ const profile = this.userProfile;
637
+ if (profile.role === 'owner' || profile.role === 'captain') {
638
+ return true;
639
+ } else {
640
+ this.$emit('access-denied', 'You do not have access to do this');
641
+ return false;
642
+ }
643
+ },
644
+ deleteTask(taskId) {
645
+ this.checklists = this.checklists.filter(checklist => checklist.id !== taskId);
646
+ },
647
+ addTask(event) {
648
+ if (event) event.preventDefault();
649
+
650
+ this.$emit('show-message', {
651
+ type: 'prompt',
652
+ title: 'Add New Task',
653
+ message: 'Enter the task details',
654
+ callback: (result) => {
655
+ if (result) {
656
+ const newTask = {
657
+ id: Date.now(),
658
+ text: result,
659
+ completed: false
660
+ };
661
+ this.checklists.push(newTask);
662
+ }
663
+ }
664
+ });
665
+ },
666
+ setFilter(filter) {
667
+ this.activeFilter = filter;
668
+ },
669
+ loadChecklist(taskComponent) {
670
+ this.isLoading = true;
671
+ this.$emit('generate-checklist', {
672
+ component: taskComponent,
673
+ callback: (checklist) => {
674
+ this.checklists = checklist.map(item => ({
675
+ ...item,
676
+ completed: false
677
+ }));
678
+ this.isLoading = false;
679
+ }
680
+ });
681
+ },
682
+ showMaintenance(taskComponent) {
683
+ if (this.deepAccess()) {
684
+ const task = this.tasks.find(t => t.component === taskComponent);
685
+
686
+ if (task && task.checklistProgress) {
687
+ this.checklists = [...task.checklistProgress];
688
+ } else {
689
+ this.loadChecklist(taskComponent);
690
+ }
691
+
692
+ this.after = task?.after;
693
+ this.currentTask = taskComponent;
694
+ this.lastSection = 'inventory';
695
+ this.activeSection = 'maintenance';
696
+ }
697
+ },
698
+ printMaintenance(taskComponent) {
699
+ const task = this.tasks.find(t => t.component === taskComponent);
700
+ this.maintenanceTasks = [task];
701
+ this.loadMaintenanceData();
702
+
703
+ this.showReport = true;
704
+
705
+ this.$nextTick(() => {
706
+ setTimeout(() => {
707
+ window.print();
708
+ this.showReport = false;
709
+ }, 100);
710
+ });
711
+ },
712
+ switchSchedule() {
713
+ if (this.deepAccess()) {
714
+ this.activeSection = 'schedule';
715
+ }
716
+ },
717
+ handleFiles(event) {
718
+ this.imgText = event.target.files[0].name;
719
+ this.form.attachments = {
720
+ file: event.target.files[0]
721
+ };
722
+ },
723
+ handleImg(event) {
724
+ this.fileText = event.target.files[0].name;
725
+ this.fileattachments = {
726
+ file: event.target.files[0]
727
+ };
728
+ },
729
+ validateForm() {
730
+ const requiredFields = [
731
+ 'taskName',
732
+ 'description',
733
+ 'maintenanceType',
734
+ 'component',
735
+ 'priority',
736
+ 'estimatedHours',
737
+ 'recurrence',
738
+ 'lastPerformed',
739
+ 'assignedTo'
740
+ ];
741
+
742
+ for (const field of requiredFields) {
743
+ if (!this.form[field]) {
744
+ this.$emit('show-message', {
745
+ type: 'error',
746
+ title: 'Missing info',
747
+ message: `Please fill in the required field: ${field}`
748
+ });
749
+ return false;
750
+ }
751
+ }
752
+
753
+ return true;
754
+ },
755
+ handleSectionClick(section) {
756
+ this.activeSection = section.id;
757
+ if (typeof section.onClick === 'function') {
758
+ section.onClick();
759
+ }
760
+ },
761
+ calculateNextDue() {
762
+ const date = this.form.lastPerformed ? new Date(this.form.lastPerformed) : null;
763
+ const recurrence = this.form.recurrence;
764
+
765
+ if (!date || !recurrence) return;
766
+
767
+ let nextDate = new Date(date);
768
+
769
+ switch (recurrence) {
770
+ case 'daily':
771
+ nextDate.setDate(nextDate.getDate() + 1);
772
+ break;
773
+ case 'weekly':
774
+ nextDate.setDate(nextDate.getDate() + 7);
775
+ break;
776
+ case 'monthly':
777
+ nextDate.setMonth(nextDate.getMonth() + 1);
778
+ break;
779
+ case 'quarterly':
780
+ nextDate.setMonth(nextDate.getMonth() + 3);
781
+ break;
782
+ case 'semi-annual':
783
+ nextDate.setMonth(nextDate.getMonth() + 6);
784
+ break;
785
+ case 'annual':
786
+ nextDate.setFullYear(nextDate.getFullYear() + 1);
787
+ break;
788
+ case 'once':
789
+ case 'custom':
790
+ return;
791
+ }
792
+
793
+ this.form.nextDue = nextDate.toISOString().split('T')[0];
794
+ },
795
+ calculateRemainingDays() {
796
+ if (!this.form.nextDue) {
797
+ this.form.remainingDays = null;
798
+ return;
799
+ }
800
+
801
+ const today = new Date();
802
+ const nextDueDate = new Date(this.form.nextDue);
803
+
804
+ today.setHours(0, 0, 0, 0);
805
+ nextDueDate.setHours(0, 0, 0, 0);
806
+
807
+ const diffTime = nextDueDate - today;
808
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
809
+
810
+ this.form.remainingDays = `${diffDays} Days`;
811
+ },
812
+ async saveSchedule() {
813
+ this.isSaving = true;
814
+
815
+ if (!this.validateForm()) {
816
+ this.isSaving = false;
817
+ return;
818
+ }
819
+
820
+ const taskData = { ...this.form };
821
+
822
+ if (this.form.component === 'Other' && this.form.customComponent) {
823
+ taskData.component = this.form.customComponent;
824
+ }
825
+
826
+ taskData.email = this.companyInfo.email;
827
+
828
+ // Check for duplicate component
829
+ const hasDuplicateComponent = this.tasks.some(task => task.component === taskData.component);
830
+
831
+ if (hasDuplicateComponent) {
832
+ this.$emit('show-message', {
833
+ type: 'error',
834
+ title: 'Duplicate Component',
835
+ message: `A task with the component "${taskData.component}" already exists in maintenance`
836
+ });
837
+ this.isSaving = false;
838
+ return;
839
+ }
840
+
841
+ delete taskData.customComponent;
842
+
843
+ this.$emit('save-schedule', {
844
+ taskData,
845
+ file: taskData.attachments.file,
846
+ callback: (success) => {
847
+ this.isSaving = false;
848
+ if (success) {
849
+ this.loadChecklist(taskData.component);
850
+ this.lastSection = 'schedule';
851
+ this.currentTask = taskData.component;
852
+ this.resetForm();
853
+ this.activeSection = 'maintenance';
854
+ }
855
+ }
856
+ });
857
+ },
858
+ resetForm() {
859
+ this.form = {
860
+ taskName: '',
861
+ description: '',
862
+ maintenanceType: '',
863
+ component: '',
864
+ priority: '',
865
+ dueDate: '',
866
+ estimatedHours: null,
867
+ assignedTo: '',
868
+ recurrence: '',
869
+ lastPerformed: '',
870
+ nextDue: '',
871
+ notifyEmail: true,
872
+ notifySms: true,
873
+ reminderDays: '1',
874
+ estimatedDuration: null,
875
+ notes: '',
876
+ status: 'Soon',
877
+ remainingDays: null,
878
+ attachments: {}
879
+ };
880
+ },
881
+ toggleTask(task) {
882
+ task.completed = !task.completed;
883
+ },
884
+ async resetTasks() {
885
+ const allCompleted = this.checklists.every(item => item.completed);
886
+ const status = allCompleted ? 'Completed' : 'In Progress';
887
+
888
+ const updateData = {
889
+ checklistProgress: [...this.checklists],
890
+ status,
891
+ component: this.currentTask,
892
+ after: this.after
893
+ };
894
+
895
+ this.$emit('update-task', {
896
+ updateData,
897
+ file: this.fileattachments.file,
898
+ callback: (success, updatedAfter) => {
899
+ if (success) {
900
+ if (updatedAfter) {
901
+ this.after = updatedAfter;
902
+ }
903
+ this.fileattachments = {};
904
+ this.fileText = 'Drag and drop files here or';
905
+ this.refreshKey += 1;
906
+ this.activeSection = 'inventory';
907
+ }
908
+ }
909
+ });
910
+ },
911
+ loadMaintenanceData() {
912
+ this.reportId = 'MT-' + Math.random().toString(36).substr(2, 9).toUpperCase();
913
+ this.reportDate = new Date().toLocaleDateString('en-US', {
914
+ year: 'numeric',
915
+ month: 'long',
916
+ day: 'numeric'
917
+ });
918
+ },
919
+ formatDate(dateString) {
920
+ if (!dateString) return 'N/A';
921
+ return new Date(dateString).toLocaleDateString('en-US', {
922
+ year: 'numeric',
923
+ month: 'short',
924
+ day: 'numeric'
925
+ });
926
+ },
927
+ getTaskStatusClass(task) {
928
+ if (task.status === 'Completed') return 'completed';
929
+ if (task.status === 'Overdue') return 'overdue';
930
+ if (task.status === 'In Progress') return 'in-progress';
931
+ return 'pending';
932
+ },
933
+ getChecklistProgress(task) {
934
+ if (!task.checklistProgress || task.checklistProgress.length === 0) return 0;
935
+ const completed = task.checklistProgress.filter(item => item.completed).length;
936
+ return Math.round((completed / task.checklistProgress.length) * 100);
937
+ },
938
+ getCompletedChecklistItems(task) {
939
+ if (!task.checklistProgress) return 0;
940
+ return task.checklistProgress.filter(item => item.completed).length;
941
+ },
942
+ generateRecommendations() {
943
+ return [
944
+ 'Maintain regular communication with assigned technicians',
945
+ 'Ensure all safety protocols are followed during maintenance activities',
946
+ 'Update task progress and notes in real-time for accurate reporting'
947
+ ];
948
+ },
949
+ deleteEvidence() {
950
+ this.$emit('delete-evidence', {
951
+ currentTask: this.currentTask,
952
+ callback: (success) => {
953
+ if (success) {
954
+ this.after = null;
955
+ this.fileText = 'Drag and drop files here or';
956
+ this.fileattachments = {};
957
+ this.refreshKey += 1;
958
+ }
959
+ }
960
+ });
961
+ }
962
+ }
963
+ };
964
+ </script>
965
+
966
+ <style>
967
+ .progress-container {
968
+ margin-bottom: 20px;
969
+ }
970
+
971
+ .progress-bar {
972
+ background-color: #e9ecef;
973
+ border-radius: 4px;
974
+ height: 10px;
975
+ margin-top: 8px;
976
+ }
977
+
978
+ .progress-fill {
979
+ background-color: #00a8e8;
980
+ height: 100%;
981
+ border-radius: 4px;
982
+ transition: width 0.3s ease;
983
+ }
984
+
985
+ .checklist {
986
+ list-style-type: none;
987
+ padding: 0;
988
+ }
989
+
990
+ .checklist-item {
991
+ display: flex;
992
+ align-items: center;
993
+ padding: 10px 0;
994
+ border-bottom: 1px solid #eee;
995
+ cursor: pointer;
996
+ }
997
+
998
+ .checkbox {
999
+ margin-right: 15px;
1000
+ width: 20px;
1001
+ height: 20px;
1002
+ border: 2px solid #00a8e8;
1003
+ border-radius: 50%;
1004
+ display: flex;
1005
+ align-items: center;
1006
+ justify-content: center;
1007
+ flex-shrink: 0;
1008
+ }
1009
+
1010
+ .checkbox.checked {
1011
+ background-color: #00a8e8;
1012
+ color: white;
1013
+ }
1014
+
1015
+ .task-text {
1016
+ flex-grow: 1;
1017
+ }
1018
+
1019
+ .task-text.completed {
1020
+ text-decoration: line-through;
1021
+ color: #6c757d;
1022
+ }
1023
+
1024
+ .status {
1025
+ margin-top: 20px;
1026
+ font-weight: bold;
1027
+ text-align: center;
1028
+ }
1029
+
1030
+ .reset-button {
1031
+ background-color: #005792;
1032
+ color: white;
1033
+ border: none;
1034
+ padding: 8px 16px;
1035
+ border-radius: 4px;
1036
+ cursor: pointer;
1037
+ margin-top: 20px;
1038
+ font-weight: bold;
1039
+ }
1040
+
1041
+ .btn-outline-custom {
1042
+ color: #005792;
1043
+ /* your custom text color */
1044
+ border: 2px solid #005792;
1045
+ /* your custom border color */
1046
+ background-color: transparent;
1047
+ padding: 8px 16px;
1048
+ border-radius: 4px;
1049
+ font-weight: 500;
1050
+ cursor: pointer;
1051
+ transition: background-color 0.3s, color 0.3s;
1052
+ }
1053
+
1054
+ .btn-outline-custom:hover {
1055
+ background-color: #005792;
1056
+ /* custom hover background */
1057
+ color: white;
1058
+ /* text color on hover */
1059
+ }
1060
+
1061
+ .reset-button:hover {
1062
+ background-color: #003d5b;
1063
+ }
1064
+
1065
+ header {
1066
+ background-color: var(--maitprimary);
1067
+ color: white;
1068
+ padding: 1rem;
1069
+ text-align: center;
1070
+ border-radius: 5px 5px 0 0;
1071
+ }
1072
+
1073
+ nav {
1074
+ background-color: var(--maitsecondary);
1075
+ padding: 10px;
1076
+ display: flex;
1077
+ justify-content: center;
1078
+ }
1079
+
1080
+ nav button {
1081
+ background-color: var(--maitsecondary);
1082
+ color: white;
1083
+ border: none;
1084
+ padding: 10px 20px;
1085
+ margin: 0 5px;
1086
+ cursor: pointer;
1087
+ font-weight: bold;
1088
+ border-radius: 3px;
1089
+ transition: background-color 0.3s;
1090
+ }
1091
+
1092
+ nav button:hover,
1093
+ nav button.active {
1094
+ background-color: var(--maitprimary);
1095
+ }
1096
+
1097
+ .content {
1098
+ background-color: white;
1099
+ padding: 20px;
1100
+ border-radius: 0 0 5px 5px;
1101
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
1102
+ }
1103
+
1104
+ h2 {
1105
+ color: var(--dark);
1106
+ margin-bottom: 20px;
1107
+ border-bottom: 2px solid var(--light);
1108
+ padding-bottom: 10px;
1109
+ }
1110
+
1111
+ .form-section {
1112
+ display: none;
1113
+ animation: fadeIn 0.5s;
1114
+ }
1115
+
1116
+ .form-section.active {
1117
+ display: block;
1118
+ }
1119
+
1120
+ .form-row {
1121
+ margin-bottom: 15px;
1122
+ }
1123
+
1124
+ .form-group {
1125
+ margin-bottom: 20px;
1126
+ }
1127
+
1128
+ label {
1129
+ display: block;
1130
+ margin-bottom: 5px;
1131
+ font-weight: bold;
1132
+ color: var(--dark);
1133
+ }
1134
+
1135
+ input,
1136
+ select,
1137
+ textarea {
1138
+ width: 100%;
1139
+ padding: 10px;
1140
+ border: 1px solid #ddd;
1141
+ border-radius: 4px;
1142
+ font-size: 16px;
1143
+ }
1144
+
1145
+ .input-group {
1146
+ display: flex;
1147
+ gap: 10px;
1148
+ }
1149
+
1150
+ .input-group>div {
1151
+ flex: 1;
1152
+ }
1153
+
1154
+ textarea {
1155
+ height: 120px;
1156
+ resize: vertical;
1157
+ }
1158
+
1159
+ .btn {
1160
+ padding: 10px 20px;
1161
+ border: none;
1162
+ border-radius: 4px;
1163
+ cursor: pointer;
1164
+ font-size: 16px;
1165
+ font-weight: bold;
1166
+ transition: background-color 0.3s;
1167
+ }
1168
+
1169
+ .btn-primary {
1170
+ background-color: var(--maitprimary);
1171
+ color: white;
1172
+ }
1173
+
1174
+ .btn-primary:hover {
1175
+ background-color: var(--dark);
1176
+ }
1177
+
1178
+ .btn-success {
1179
+ background-color: var(--success);
1180
+ color: white;
1181
+ }
1182
+
1183
+ .btn-success:hover {
1184
+ background-color: #219653;
1185
+ }
1186
+
1187
+ .action-buttons {
1188
+ display: flex;
1189
+ justify-content: flex-end;
1190
+ gap: 10px;
1191
+ margin-top: 20px;
1192
+ }
1193
+
1194
+ .status-badge {
1195
+ display: inline-block;
1196
+ padding: 5px 10px;
1197
+ border-radius: 15px;
1198
+ font-size: 14px;
1199
+ font-weight: bold;
1200
+ color: white;
1201
+ }
1202
+
1203
+ .status-pending {
1204
+ background-color: var(--warning);
1205
+ }
1206
+
1207
+ .status-progress {
1208
+ background-color: var(--accent);
1209
+ }
1210
+
1211
+ .status-completed {
1212
+ background-color: var(--success);
1213
+ }
1214
+
1215
+ .attachment-area {
1216
+ border: 2px dashed #ddd;
1217
+ padding: 20px;
1218
+ text-align: center;
1219
+ border-radius: 5px;
1220
+ margin-bottom: 20px;
1221
+ }
1222
+
1223
+ .file-input {
1224
+ display: none;
1225
+ }
1226
+
1227
+ .file-label {
1228
+ display: inline-block;
1229
+ padding: 10px 20px;
1230
+ background-color: var(--accent);
1231
+ color: white;
1232
+ border-radius: 4px;
1233
+ cursor: pointer;
1234
+ transition: background-color 0.3s;
1235
+ }
1236
+
1237
+ .file-label:hover {
1238
+ background-color: var(--maitsecondary);
1239
+ }
1240
+
1241
+ @keyframes fadeIn {
1242
+ from {
1243
+ opacity: 0;
1244
+ }
1245
+
1246
+ to {
1247
+ opacity: 1;
1248
+ }
1249
+ }
1250
+
1251
+ .form-section h3 {
1252
+ margin-top: 30px;
1253
+ margin-bottom: 15px;
1254
+ color: var(--maitsecondary);
1255
+ }
1256
+
1257
+ .checkbox-group {
1258
+ display: flex;
1259
+ flex-wrap: wrap;
1260
+ gap: 15px;
1261
+ margin-top: 10px;
1262
+ }
1263
+
1264
+ .checkbox-item {
1265
+ display: flex;
1266
+ align-items: center;
1267
+ gap: 5px;
1268
+ }
1269
+
1270
+ .checkbox-item input {
1271
+ width: auto;
1272
+ }
1273
+
1274
+ .status-badge {
1275
+ padding: 0.3em 0.6em;
1276
+ border-radius: 4px;
1277
+ color: white;
1278
+ font-weight: bold;
1279
+ font-size: 0.85rem;
1280
+ }
1281
+
1282
+ .status-action {
1283
+ padding: 0.3em 0.6em;
1284
+ border-radius: 4px;
1285
+ color: white;
1286
+ font-weight: bold;
1287
+ font-size: 0.85rem;
1288
+ background-color: var(--maitprimary);
1289
+ }
1290
+
1291
+ .status-badge.overdue {
1292
+ background-color: red;
1293
+ }
1294
+
1295
+ .status-badge.soon {
1296
+ background-color: orange;
1297
+ }
1298
+
1299
+ /* Wrapper layout */
1300
+ .task-table-wrapper {
1301
+ font-family: 'Inter', sans-serif;
1302
+ padding: 1rem;
1303
+ }
1304
+
1305
+ /* Controls section */
1306
+ .table-controls {
1307
+ display: flex;
1308
+ justify-content: space-between;
1309
+ align-items: center;
1310
+ margin-bottom: 1rem;
1311
+ flex-wrap: wrap;
1312
+ gap: 1rem;
1313
+ }
1314
+
1315
+ .filters {
1316
+ display: flex;
1317
+ gap: 0.5rem;
1318
+ flex-wrap: wrap;
1319
+ }
1320
+
1321
+ .filters button,
1322
+ .filters input {
1323
+ padding: 0.5rem 1rem;
1324
+ border-radius: 6px;
1325
+ border: 1px solid #ccc;
1326
+ background-color: #f5f5f5;
1327
+ font-weight: 500;
1328
+ cursor: pointer;
1329
+ }
1330
+
1331
+ .filters button.active {
1332
+ background-color: #002f6c;
1333
+ color: white;
1334
+ }
1335
+
1336
+ .filters input {
1337
+ border: 1px solid #bbb;
1338
+ }
1339
+
1340
+ .filter-badge {
1341
+ background-color: #eee;
1342
+ color: #333;
1343
+ }
1344
+
1345
+ /* Print and Add buttons */
1346
+ .table-controls>div:last-child button {
1347
+ padding: 0.5rem 1rem;
1348
+ border: 1px solid var(--maitprimary);
1349
+ border-radius: 6px;
1350
+ background-color: white;
1351
+ color: var(--maitprimary);
1352
+ ;
1353
+ font-weight: 600;
1354
+ cursor: pointer;
1355
+ transition: background 0.2s;
1356
+ }
1357
+
1358
+ .table-controls>div:last-child button:last-child {
1359
+ background-color: var(--maitprimary);
1360
+ ;
1361
+ color: white;
1362
+ }
1363
+
1364
+ /* Table */
1365
+ .task-table {
1366
+ width: 100%;
1367
+ border-collapse: collapse;
1368
+ box-shadow: 0 0 0 1px #ccc;
1369
+ }
1370
+
1371
+ .task-table thead {
1372
+ background-color: var(--maitprimary);
1373
+ ;
1374
+ color: white;
1375
+ }
1376
+
1377
+ .task-table th,
1378
+ .task-table td {
1379
+ padding: 0.75rem;
1380
+ text-align: left;
1381
+ border-bottom: 1px solid #eee;
1382
+ white-space: pre-line;
1383
+ /* so \n works */
1384
+ }
1385
+
1386
+ .task-table tbody tr:hover {
1387
+ background-color: #f9f9f9;
1388
+ }
1389
+
1390
+ /* Status badges */
1391
+ .status-badge {
1392
+ padding: 0.3em 0.6em;
1393
+ border-radius: 6px;
1394
+ font-weight: bold;
1395
+ font-size: 0.85rem;
1396
+ color: white;
1397
+ display: inline-block;
1398
+ text-align: center;
1399
+ min-width: 90px;
1400
+ }
1401
+
1402
+ .status-badge.complete {
1403
+ background-color: #4dffd0;
1404
+ }
1405
+
1406
+ .status-badge.soon {
1407
+ background-color: #ffa500;
1408
+ }
1409
+
1410
+ .status-badge.completed {
1411
+ background-color: #4caf50;
1412
+ }
1413
+
1414
+ .delete-btn {
1415
+ background: none;
1416
+ border: none;
1417
+ color: #dc3545;
1418
+ cursor: pointer;
1419
+ padding: 6px;
1420
+ border-radius: 4px;
1421
+ display: flex;
1422
+ align-items: center;
1423
+ justify-content: center;
1424
+ transition: all 0.2s ease;
1425
+ opacity: 1;
1426
+ margin-left: 8px;
1427
+ flex-shrink: 0;
1428
+ transform: scale(1.1);
1429
+ }
1430
+
1431
+ .checklist-item:hover .delete-btn {
1432
+ opacity: 1;
1433
+ }
1434
+
1435
+ .delete-btn:hover {
1436
+ background-color: #dc3545;
1437
+ color: white;
1438
+ transform: scale(1.1);
1439
+ }
1440
+
1441
+ .delete-btn:active {
1442
+ transform: scale(0.95);
1443
+ }
1444
+
1445
+ .print-only-container {
1446
+ text-align: center;
1447
+ }
1448
+
1449
+ .initial-print-btn {
1450
+ background: linear-gradient(135deg, #0066cc, #004499);
1451
+ color: white;
1452
+ border: none;
1453
+ padding: 20px 40px;
1454
+ font-size: 1.3em;
1455
+ border-radius: 12px;
1456
+ cursor: pointer;
1457
+ transition: all 0.3s ease;
1458
+ box-shadow: 0 6px 20px rgba(0, 102, 204, 0.3);
1459
+ display: flex;
1460
+ align-items: center;
1461
+ gap: 15px;
1462
+ margin: 0 auto;
1463
+ }
1464
+
1465
+ .initial-print-btn:hover {
1466
+ background: linear-gradient(135deg, #004499, #003366);
1467
+ transform: translateY(-3px);
1468
+ box-shadow: 0 8px 25px rgba(0, 102, 204, 0.4);
1469
+ }
1470
+
1471
+ .initial-print-btn:active {
1472
+ transform: translateY(0);
1473
+ }
1474
+
1475
+ .company-branding {
1476
+ margin-bottom: 30px;
1477
+ }
1478
+
1479
+ .company-logo {
1480
+ font-size: 3em;
1481
+ font-weight: bold;
1482
+ color: #0066cc;
1483
+ margin-bottom: 10px;
1484
+ }
1485
+
1486
+ .company-tagline {
1487
+ font-size: 1.1em;
1488
+ color: #666;
1489
+ }
1490
+
1491
+ /* Report Styles - Hidden Initially */
1492
+ .report-container {
1493
+ display: none;
1494
+ max-width: 900px;
1495
+ margin: 0 auto;
1496
+ background: white;
1497
+ padding: 30px;
1498
+ border-radius: 10px;
1499
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
1500
+ }
1501
+
1502
+ .header {
1503
+ text-align: center;
1504
+ border-bottom: 3px solid #0066cc;
1505
+ padding-bottom: 20px;
1506
+ margin-bottom: 30px;
1507
+ }
1508
+
1509
+ .report-logo {
1510
+ font-size: 2.5em;
1511
+ font-weight: bold;
1512
+ color: #0066cc;
1513
+ margin-bottom: 10px;
1514
+ }
1515
+
1516
+ .report-title {
1517
+ font-size: 1.8em;
1518
+ color: #333;
1519
+ margin: 10px 0;
1520
+ }
1521
+
1522
+ .report-info {
1523
+ display: grid;
1524
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1525
+ gap: 20px;
1526
+ margin-bottom: 30px;
1527
+ }
1528
+
1529
+ .info-box {
1530
+ background: #f8f9fa;
1531
+ padding: 15px;
1532
+ border-radius: 8px;
1533
+ border-left: 4px solid #0066cc;
1534
+ }
1535
+
1536
+ .info-box ul li {
1537
+ margin-left: 15px;
1538
+ }
1539
+
1540
+ .info-label {
1541
+ font-weight: bold;
1542
+ color: #0066cc;
1543
+ margin-bottom: 5px;
1544
+ }
1545
+
1546
+ .section {
1547
+ margin-bottom: 30px;
1548
+ }
1549
+
1550
+ .section-title {
1551
+ font-size: 1.3em;
1552
+ font-weight: bold;
1553
+ color: #0066cc;
1554
+ border-bottom: 2px solid #e9ecef;
1555
+ padding-bottom: 10px;
1556
+ margin-bottom: 15px;
1557
+ }
1558
+
1559
+ .task-item {
1560
+ background: #f8f9fa;
1561
+ margin: 15px 0;
1562
+ padding: 20px;
1563
+ border-radius: 8px;
1564
+ border-left: 4px solid #28a745;
1565
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
1566
+ }
1567
+
1568
+ .task-item.pending {
1569
+ border-left-color: #ffc107;
1570
+ }
1571
+
1572
+ .task-item.overdue {
1573
+ border-left-color: #dc3545;
1574
+ }
1575
+
1576
+ .task-item.in-progress {
1577
+ border-left-color: #17a2b8;
1578
+ }
1579
+
1580
+ .task-header {
1581
+ display: flex;
1582
+ justify-content: space-between;
1583
+ align-items: flex-start;
1584
+ margin-bottom: 15px;
1585
+ flex-wrap: wrap;
1586
+ gap: 10px;
1587
+ }
1588
+
1589
+ .task-title {
1590
+ font-size: 1.2em;
1591
+ font-weight: bold;
1592
+ color: #333;
1593
+ }
1594
+
1595
+ .task-component {
1596
+ font-size: 0.9em;
1597
+ color: #666;
1598
+ margin-top: 5px;
1599
+ }
1600
+
1601
+ .status-badge {
1602
+ display: inline-block;
1603
+ padding: 6px 12px;
1604
+ border-radius: 20px;
1605
+ font-size: 0.8em;
1606
+ font-weight: bold;
1607
+ text-transform: uppercase;
1608
+ }
1609
+
1610
+ .status-completed {
1611
+ background: #d4edda;
1612
+ color: #155724;
1613
+ }
1614
+
1615
+ .status-pending {
1616
+ background: #fff3cd;
1617
+ color: #856404;
1618
+ }
1619
+
1620
+ .status-overdue {
1621
+ background: #f8d7da;
1622
+ color: #721c24;
1623
+ }
1624
+
1625
+ .status-in-progress {
1626
+ background: #d1ecf1;
1627
+ color: #0c5460;
1628
+ }
1629
+
1630
+ .task-details {
1631
+ display: grid;
1632
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1633
+ gap: 15px;
1634
+ margin: 15px 0;
1635
+ }
1636
+
1637
+ .detail-item {
1638
+ display: flex;
1639
+ flex-direction: column;
1640
+ }
1641
+
1642
+ .detail-label {
1643
+ font-weight: bold;
1644
+ font-size: 0.85em;
1645
+ color: #0066cc;
1646
+ margin-bottom: 3px;
1647
+ }
1648
+
1649
+ .detail-value {
1650
+ font-size: 0.95em;
1651
+ color: #333;
1652
+ }
1653
+
1654
+ .checklist-progress {
1655
+ margin-top: 15px;
1656
+ }
1657
+
1658
+ .progress-bar {
1659
+ width: 100%;
1660
+ height: 20px;
1661
+ background: #e9ecef;
1662
+ border-radius: 10px;
1663
+ overflow: hidden;
1664
+ margin: 10px 0;
1665
+ }
1666
+
1667
+ .progress-fill {
1668
+ height: 100%;
1669
+ background: linear-gradient(90deg, #28a745, #20c997);
1670
+ transition: width 0.3s ease;
1671
+ }
1672
+
1673
+ .checklist-items {
1674
+ display: grid;
1675
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1676
+ gap: 10px;
1677
+ margin-top: 10px;
1678
+ }
1679
+
1680
+ .checklist-item {
1681
+ display: flex;
1682
+ align-items: center;
1683
+ padding: 8px;
1684
+ background: white;
1685
+ border-radius: 5px;
1686
+ font-size: 0.9em;
1687
+ }
1688
+
1689
+ .checklist-icon {
1690
+ margin-right: 8px;
1691
+ font-size: 1.1em;
1692
+ }
1693
+
1694
+ .summary-grid {
1695
+ display: grid;
1696
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
1697
+ gap: 15px;
1698
+ margin: 20px 0;
1699
+ }
1700
+
1701
+ .summary-card {
1702
+ background: white;
1703
+ padding: 20px;
1704
+ border-radius: 8px;
1705
+ text-align: center;
1706
+ border: 2px solid #e9ecef;
1707
+ }
1708
+
1709
+ .summary-number {
1710
+ font-size: 2em;
1711
+ font-weight: bold;
1712
+ color: #0066cc;
1713
+ }
1714
+
1715
+ .summary-label {
1716
+ font-size: 0.9em;
1717
+ color: #666;
1718
+ margin-top: 5px;
1719
+ }
1720
+
1721
+ .signature-section {
1722
+ margin-top: 40px;
1723
+ display: grid;
1724
+ grid-template-columns: 1fr 1fr;
1725
+ gap: 40px;
1726
+ }
1727
+
1728
+ .signature-box {
1729
+ padding-top: 10px;
1730
+ text-align: center;
1731
+ }
1732
+
1733
+ .maintenance-type {
1734
+ display: inline-block;
1735
+ padding: 4px 8px;
1736
+ border-radius: 12px;
1737
+ font-size: 0.75em;
1738
+ font-weight: bold;
1739
+ text-transform: uppercase;
1740
+ margin-left: 10px;
1741
+ }
1742
+
1743
+ .type-corrective {
1744
+ background: #fff3cd;
1745
+ color: #856404;
1746
+ }
1747
+
1748
+ .type-preventive {
1749
+ background: #d4edda;
1750
+ color: #155724;
1751
+ }
1752
+
1753
+ .type-predictive {
1754
+ background: #d1ecf1;
1755
+ color: #0c5460;
1756
+ }
1757
+
1758
+ /* Print Styles */
1759
+ @media print {
1760
+ body {
1761
+ background: white;
1762
+ padding: 0;
1763
+ display: block;
1764
+ }
1765
+
1766
+ .print-only-container {
1767
+ display: none !important;
1768
+ }
1769
+
1770
+ .report-container {
1771
+ display: block !important;
1772
+ box-shadow: none;
1773
+ padding: 0;
1774
+ margin: 0;
1775
+ max-width: none;
1776
+ }
1777
+ }
1778
+ </style>