oceanhelm 0.0.10 → 0.0.12

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,1449 @@
1
+ <template>
2
+ <div class="reports-container">
3
+
4
+ <!-- ── Missing Reports Alert Banner ── -->
5
+ <transition name="slide-down">
6
+ <div class="missing-banner" v-if="pendingContext || missingReports.length > 0">
7
+ <div class="missing-banner-inner">
8
+ <div class="missing-icon">
9
+ <i class="bi bi-exclamation-triangle-fill"></i>
10
+ </div>
11
+ <div class="missing-content">
12
+ <strong v-if="pendingContext">
13
+ Action Required — Report for
14
+ <span class="ref-chip">{{ pendingContext.entity_ref }}</span>
15
+ must be submitted
16
+ </strong>
17
+ <strong v-else>
18
+ {{ missingReports.length }} report{{ missingReports.length > 1 ? 's' : '' }} awaiting
19
+ submission
20
+ </strong>
21
+ <p v-if="pendingContext">{{ pendingContext.title }}</p>
22
+ </div>
23
+ <button class="btn btn-warning btn-sm fw-bold" @click="openReportForm">
24
+ <i class="bi bi-pencil-square me-1"></i> Submit Report
25
+ </button>
26
+ <button class="btn-dismiss" @click="dismissBanner" title="Dismiss">
27
+ <i class="bi bi-x-lg"></i>
28
+ </button>
29
+ </div>
30
+ </div>
31
+ </transition>
32
+
33
+ <!-- ── Page Header ── -->
34
+ <div class="page-header d-flex justify-content-between align-items-center">
35
+ <div>
36
+ <h4 class="page-title">Reports</h4>
37
+ <p class="page-subtitle">Document Management &amp; QHSE Reports</p>
38
+ </div>
39
+ <div class="header-actions">
40
+ <button class="btn btn-outline-primary me-2" @click="openReportForm">
41
+ <i class="bi bi-file-earmark-plus"></i> New Report
42
+ </button>
43
+ <button class="btn btn-primary" @click="showNewFolderModal = true">
44
+ <i class="bi bi-folder-plus"></i> New Folder
45
+ </button>
46
+ </div>
47
+ </div>
48
+
49
+ <!-- ── Submitted Reports Section ── -->
50
+ <div class="submitted-section" v-if="linkedReports.length > 0">
51
+ <div class="section-label">
52
+ <i class="bi bi-file-earmark-check-fill text-success"></i>
53
+ Submitted QHSE Reports
54
+ </div>
55
+ <div class="report-cards">
56
+ <div v-for="report in linkedReports" :key="report.id" :ref="'report-' + report.entity_ref"
57
+ :class="['report-card', { 'report-card--highlighted': highlightedRef === report.entity_ref }]"
58
+ @click="viewReport(report)">
59
+ <div class="rc-badge" :class="report.entity_type">
60
+ {{ report.entity_type === 'drill' ? 'DRILL' : 'INC' }}
61
+ </div>
62
+ <div class="rc-body">
63
+ <strong class="rc-ref">{{ report.entity_ref }}</strong>
64
+ <span class="rc-title">{{ report.title }}</span>
65
+ <span class="rc-meta">
66
+ <i class="bi bi-person-fill me-1"></i>{{ report.submittedBy }}
67
+ &nbsp;·&nbsp;
68
+ <i class="bi bi-calendar3 me-1"></i>{{ formatDate(report.submittedAt) }}
69
+ </span>
70
+ </div>
71
+ <div class="rc-folder" v-if="report.folderName">
72
+ <i class="bi bi-folder-fill me-1"></i>{{ report.folderName }}
73
+ </div>
74
+ <i class="bi bi-chevron-right rc-arrow"></i>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <!-- ── Folders Section ── -->
80
+ <div class="section-label mt-4" v-if="folders.length > 0 || linkedReports.length === 0">
81
+ <i class="bi bi-folder-fill text-warning"></i> Document Folders
82
+ </div>
83
+ <div class="folders-section" v-if="folders.length > 0">
84
+ <div class="folder-card" v-for="folder in folders" :key="folder.id" @mouseenter="showPreview(folder.id)"
85
+ @mouseleave="hidePreview">
86
+ <div class="folder-content" @click="toggleFolder(folder.id)">
87
+ <i class="bi bi-folder-fill text-warning" style="font-size: 2.5rem;"></i>
88
+ <div class="folder-name">{{ folder.name }}</div>
89
+ <small class="text-muted">{{ folder.files.length }} file(s)</small>
90
+ </div>
91
+ <div class="folder-actions-overlay">
92
+ <button class="btn btn-sm btn-icon" @click.stop="selectFolder(folder)" title="Add Files">
93
+ <i class="bi bi-file-earmark-plus"></i>
94
+ </button>
95
+ <button class="btn btn-sm btn-icon btn-icon-danger" @click.stop="deleteFolder(folder.id)"
96
+ title="Delete Folder">
97
+ <i class="bi bi-trash"></i>
98
+ </button>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Empty State -->
104
+ <div class="empty-state text-center" v-if="folders.length === 0 && linkedReports.length === 0">
105
+ <i class="bi bi-folder2-open" style="font-size: 4rem; color: #ccc;"></i>
106
+ <h5 class="mt-3 text-muted">No reports or folders yet</h5>
107
+ <p class="text-muted">Submit a report or create a folder to get started</p>
108
+ </div>
109
+
110
+ <!-- ── Expanded Folder Modal ── -->
111
+ <div class="folder-modal-overlay" v-if="expandedFolders.length > 0" @click="closeAllFolders">
112
+ <div class="folder-expanded" @click.stop>
113
+ <div class="expanded-header">
114
+ <strong>{{ getExpandedFolder().name }}</strong>
115
+ <button class="btn-close-expanded" @click="closeAllFolders">
116
+ <i class="bi bi-x-lg"></i>
117
+ </button>
118
+ </div>
119
+ <div class="files-list" v-if="getExpandedFolder().files.length > 0">
120
+ <div class="file-item d-flex justify-content-between align-items-center"
121
+ v-for="file in getExpandedFolder().files" :key="file.id">
122
+ <div class="file-info d-flex align-items-center">
123
+ <i class="bi bi-file-earmark-text me-2"></i>
124
+ <span>{{ file.name }}</span>
125
+ <small class="text-muted ms-2">({{ formatFileSize(file.size) }})</small>
126
+ </div>
127
+ <button class="btn btn-sm btn-outline-danger"
128
+ @click="removeFile(getExpandedFolder().id, file.id)">
129
+ <i class="bi bi-x-circle"></i>
130
+ </button>
131
+ </div>
132
+ </div>
133
+ <div v-else class="empty-folder-message">
134
+ <i class="bi bi-inbox"></i>
135
+ <p>No files in this folder</p>
136
+ <button class="btn btn-sm btn-primary" @click="selectFolder(getExpandedFolder())">
137
+ <i class="bi bi-file-earmark-plus"></i> Add Files
138
+ </button>
139
+ </div>
140
+ </div>
141
+ </div>
142
+
143
+ <!-- ── New Folder Modal ── -->
144
+ <div class="modal" :class="{ 'show': showNewFolderModal }" @click.self="showNewFolderModal = false">
145
+ <div class="modal-dialog modal-dialog-centered">
146
+ <div class="modal-content">
147
+ <div class="modal-header">
148
+ <h5 class="modal-title"><i class="bi bi-folder-plus me-2"></i>Create New Folder</h5>
149
+ <button type="button" class="btn-close" @click="showNewFolderModal = false"></button>
150
+ </div>
151
+ <div class="modal-body">
152
+ <label class="form-label">Folder Name</label>
153
+ <input type="text" class="form-control" v-model="newFolderName" @keyup.enter="createFolder"
154
+ placeholder="e.g., Q1 2025 Drills" />
155
+ </div>
156
+ <div class="modal-footer">
157
+ <button type="button" class="btn btn-secondary"
158
+ @click="showNewFolderModal = false">Cancel</button>
159
+ <button type="button" class="btn btn-primary" @click="createFolder"
160
+ :disabled="!newFolderName.trim()">
161
+ Create Folder
162
+ </button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ <!-- ── Submit Report Modal ── -->
169
+ <div class="modal" :class="{ 'show': showReportModal }" @click.self="closeReportForm">
170
+ <div class="modal-dialog modal-dialog-centered modal-lg">
171
+ <div class="modal-content">
172
+ <div class="modal-header rpt-modal-header">
173
+ <h5 class="modal-title">
174
+ <i class="bi bi-file-earmark-text me-2"></i>Submit QHSE Report
175
+ </h5>
176
+ <button type="button" class="btn-close btn-close-white" @click="closeReportForm"></button>
177
+ </div>
178
+ <div class="modal-body">
179
+
180
+ <!-- Link to entity -->
181
+ <div class="rpt-entity-box" v-if="reportForm.entity_ref || missingReports.length > 0">
182
+ <label class="form-label fw-semibold">
183
+ <i class="bi bi-link-45deg me-1"></i> Link to Drill / Incident
184
+ </label>
185
+
186
+ <!-- Pre-filled from navigation -->
187
+ <div class="prefilled-entity" v-if="reportForm.entity_ref && reportForm.entity_type">
188
+ <span class="entity-chip" :class="reportForm.entity_type">
189
+ {{ reportForm.entity_type === 'drill' ? 'DRILL' : 'INCIDENT' }}
190
+ </span>
191
+ <strong>{{ reportForm.entity_ref }}</strong>
192
+ <span class="text-muted ms-2">{{ reportForm.entity_label }}</span>
193
+ <button class="btn btn-link btn-sm text-danger ms-auto" @click="clearEntityLink">
194
+ <i class="bi bi-x-circle"></i> Clear
195
+ </button>
196
+ </div>
197
+
198
+ <!-- Dropdown when no pre-fill -->
199
+ <div v-else>
200
+ <select class="form-control" v-model="reportForm.selectedMissing"
201
+ @change="applyMissingSelection">
202
+ <option value="">— Select a missing report (optional) —</option>
203
+ <optgroup label="Missing Drill Reports">
204
+ <option v-for="r in missingReports.filter(x => x.entity_type === 'drill')"
205
+ :key="r.entity_id" :value="JSON.stringify(r)">
206
+ {{ r.entity_ref }} — {{ r.label }}
207
+ </option>
208
+ </optgroup>
209
+ <optgroup label="Missing Incident Reports">
210
+ <option v-for="r in missingReports.filter(x => x.entity_type === 'incident')"
211
+ :key="r.entity_id" :value="JSON.stringify(r)">
212
+ {{ r.entity_ref }} — {{ r.label }}
213
+ </option>
214
+ </optgroup>
215
+ </select>
216
+ <small class="text-muted">Linking a report here will mark the drill/incident as
217
+ reported.</small>
218
+ </div>
219
+ </div>
220
+
221
+ <!-- Report Fields -->
222
+ <div class="input-row mt-3">
223
+ <div class="form-group">
224
+ <label class="form-label fw-semibold">Report Title *</label>
225
+ <input type="text" class="form-control" v-model="reportForm.title"
226
+ :placeholder="reportForm.entity_ref ? `Report — ${reportForm.entity_ref}` : 'Enter report title'" />
227
+ </div>
228
+ <div class="form-group">
229
+ <label class="form-label fw-semibold">Submitted By *</label>
230
+ <input type="text" class="form-control" v-model="reportForm.submittedBy"
231
+ placeholder="Your name" />
232
+ </div>
233
+ </div>
234
+
235
+ <div class="input-row">
236
+ <div class="form-group">
237
+ <label class="form-label fw-semibold">Save to Folder</label>
238
+ <select class="form-control" v-model="reportForm.folderId">
239
+ <option value="">— No folder —</option>
240
+ <option v-for="f in folders" :key="f.id" :value="f.id">{{ f.name }}</option>
241
+ </select>
242
+ </div>
243
+ <div class="form-group">
244
+ <label class="form-label fw-semibold">Date</label>
245
+ <input type="date" class="form-control" v-model="reportForm.date" />
246
+ </div>
247
+ </div>
248
+
249
+ <div class="form-group">
250
+ <label class="form-label fw-semibold">Findings / Summary *</label>
251
+ <textarea class="form-control" rows="4" v-model="reportForm.findings"
252
+ placeholder="Describe findings, observations, and outcomes..."></textarea>
253
+ </div>
254
+
255
+ <div class="form-group">
256
+ <label class="form-label fw-semibold">Corrective Actions</label>
257
+ <textarea class="form-control" rows="3" v-model="reportForm.correctiveActions"
258
+ placeholder="List any corrective actions required or taken..."></textarea>
259
+ </div>
260
+
261
+ <div class="form-group">
262
+ <label class="form-label fw-semibold">Attach Files</label>
263
+ <div class="file-drop-zone" @click="$refs.reportFileInput.click()" @dragover.prevent
264
+ @drop.prevent="handleReportFileDrop">
265
+ <i class="bi bi-cloud-upload" style="font-size:2rem;color:#6c757d;"></i>
266
+ <p class="mb-0 mt-2 text-muted">Click or drag files here</p>
267
+ <div class="attached-files" v-if="reportForm.files.length > 0">
268
+ <span class="attached-chip" v-for="(f, i) in reportForm.files" :key="i">
269
+ <i class="bi bi-paperclip"></i> {{ f.name }}
270
+ <button @click.stop="removeReportFile(i)"><i class="bi bi-x"></i></button>
271
+ </span>
272
+ </div>
273
+ </div>
274
+ <input ref="reportFileInput" type="file" multiple style="display:none"
275
+ @change="handleReportFileAttach" />
276
+ </div>
277
+
278
+ </div>
279
+ <div class="modal-footer">
280
+ <button type="button" class="btn btn-secondary" @click="closeReportForm">Cancel</button>
281
+ <button type="button" class="btn btn-success" @click="submitReport"
282
+ :disabled="!reportForm.title || !reportForm.submittedBy || !reportForm.findings">
283
+ <i class="bi bi-check-circle me-1"></i> Submit Report
284
+ </button>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+
290
+ <!-- ── Report Viewer Modal ── -->
291
+ <div class="modal" :class="{ 'show': showViewModal }" @click.self="showViewModal = false">
292
+ <div class="modal-dialog modal-dialog-centered modal-lg">
293
+ <div class="modal-content" v-if="viewingReport">
294
+ <div class="modal-header rpt-modal-header">
295
+ <div>
296
+ <span class="entity-chip me-2" :class="viewingReport.entity_type">
297
+ {{ viewingReport.entity_type === 'drill' ? 'DRILL' : 'INCIDENT' }}
298
+ </span>
299
+ <strong style="color:#fff">{{ viewingReport.title }}</strong>
300
+ </div>
301
+ <button type="button" class="btn-close btn-close-white" @click="showViewModal = false"></button>
302
+ </div>
303
+ <div class="modal-body">
304
+ <div class="view-grid">
305
+ <div class="view-item"><label>Reference</label><span class="ref-mono">{{
306
+ viewingReport.entity_ref }}</span></div>
307
+ <div class="view-item"><label>Type</label><span>{{ viewingReport.entity_type }}</span></div>
308
+ <div class="view-item"><label>Submitted By</label><span>{{ viewingReport.submittedBy
309
+ }}</span></div>
310
+ <div class="view-item"><label>Date</label><span>{{ viewingReport.date }}</span></div>
311
+ <div class="view-item"><label>Folder</label><span>{{ viewingReport.folderName || '—'
312
+ }}</span></div>
313
+ <div class="view-item"><label>Submitted At</label><span>{{
314
+ formatDate(viewingReport.submittedAt) }}</span></div>
315
+ </div>
316
+ <div class="view-section">
317
+ <label>Findings / Summary</label>
318
+ <div class="view-text">{{ viewingReport.findings }}</div>
319
+ </div>
320
+ <div class="view-section" v-if="viewingReport.correctiveActions">
321
+ <label>Corrective Actions</label>
322
+ <div class="view-text">{{ viewingReport.correctiveActions }}</div>
323
+ </div>
324
+ <div class="view-section" v-if="viewingReport.files && viewingReport.files.length > 0">
325
+ <label>Attachments</label>
326
+ <div class="attached-files mt-1">
327
+ <span class="attached-chip" v-for="(f, i) in viewingReport.files" :key="i">
328
+ <i class="bi bi-paperclip"></i> {{ f.name }}
329
+ </span>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ <div class="modal-footer">
334
+ <button class="btn btn-secondary" @click="showViewModal = false">Close</button>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <!-- Hidden file input for folders -->
341
+ <input ref="fileInput" type="file" multiple style="display: none;" @change="handleFileSelection" />
342
+ </div>
343
+ </template>
344
+
345
+ <script>
346
+ export default {
347
+ name: 'Reports',
348
+
349
+ props: {
350
+ // Passed from ReportsView when navigating from HSE
351
+ pendingContext: {
352
+ type: Object,
353
+ default: null
354
+ // { entity_type, entity_id, entity_ref, title }
355
+ },
356
+ // All missing reports (drills + incidents without reports)
357
+ missingReports: {
358
+ type: Array,
359
+ default: () => []
360
+ // [{ entity_type, entity_id, entity_ref, label }]
361
+ },
362
+ // Ref to scroll to / highlight (from ?ref= query param)
363
+ highlightRef: {
364
+ type: String,
365
+ default: null
366
+ }
367
+ },
368
+
369
+ emits: [
370
+ 'folder-created', 'files-added', 'file-removed', 'folder-deleted',
371
+ 'data-updated', 'report-submitted', 'report-viewed'
372
+ ],
373
+
374
+ data() {
375
+ return {
376
+ folders: [],
377
+ linkedReports: [], // submitted QHSE reports
378
+ showNewFolderModal: false,
379
+ newFolderName: '',
380
+ selectedFolder: null,
381
+ expandedFolders: [],
382
+ previewFolder: null,
383
+ showReportModal: false,
384
+ showViewModal: false,
385
+ viewingReport: null,
386
+ highlightedRef: null,
387
+ bannerDismissed: false,
388
+ reportForm: {
389
+ title: '',
390
+ submittedBy: '',
391
+ folderId: '',
392
+ date: new Date().toISOString().split('T')[0],
393
+ findings: '',
394
+ correctiveActions: '',
395
+ files: [],
396
+ entity_type: '',
397
+ entity_id: '',
398
+ entity_ref: '',
399
+ entity_label: '',
400
+ selectedMissing: '',
401
+ }
402
+ };
403
+ },
404
+
405
+ watch: {
406
+ pendingContext: {
407
+ immediate: true,
408
+ handler(ctx) {
409
+ if (ctx) {
410
+ this.reportForm.entity_type = ctx.entity_type;
411
+ this.reportForm.entity_id = ctx.entity_id;
412
+ this.reportForm.entity_ref = ctx.entity_ref;
413
+ this.reportForm.entity_label = ctx.title;
414
+ this.reportForm.title = ctx.title || `Report — ${ctx.entity_ref}`;
415
+ }
416
+ }
417
+ },
418
+ highlightRef: {
419
+ immediate: true,
420
+ handler(ref) {
421
+ if (ref) {
422
+ this.highlightedRef = ref;
423
+ this.$nextTick(() => this.scrollToReport(ref));
424
+ }
425
+ }
426
+ }
427
+ },
428
+
429
+ methods: {
430
+ // ── Banner ──────────────────────────────────────────────
431
+ dismissBanner() { this.bannerDismissed = true; },
432
+
433
+ openReportForm() {
434
+ if (this.pendingContext && !this.reportForm.entity_ref) {
435
+ this.reportForm.entity_type = this.pendingContext.entity_type;
436
+ this.reportForm.entity_id = this.pendingContext.entity_id;
437
+ this.reportForm.entity_ref = this.pendingContext.entity_ref;
438
+ this.reportForm.entity_label = this.pendingContext.title;
439
+ this.reportForm.title = this.pendingContext.title || `Report — ${this.pendingContext.entity_ref}`;
440
+ }
441
+ this.showReportModal = true;
442
+ },
443
+
444
+ closeReportForm() {
445
+ this.showReportModal = false;
446
+ },
447
+
448
+ clearEntityLink() {
449
+ this.reportForm.entity_type = '';
450
+ this.reportForm.entity_id = '';
451
+ this.reportForm.entity_ref = '';
452
+ this.reportForm.entity_label = '';
453
+ this.reportForm.selectedMissing = '';
454
+ },
455
+
456
+ applyMissingSelection() {
457
+ if (!this.reportForm.selectedMissing) return;
458
+ const r = JSON.parse(this.reportForm.selectedMissing);
459
+ this.reportForm.entity_type = r.entity_type;
460
+ this.reportForm.entity_id = r.entity_id;
461
+ this.reportForm.entity_ref = r.entity_ref;
462
+ this.reportForm.entity_label = r.label;
463
+ this.reportForm.title = this.reportForm.title || `Report — ${r.entity_ref}`;
464
+ },
465
+
466
+ // ── Submit Report ────────────────────────────────────────
467
+ submitReport() {
468
+ const folder = this.folders.find(f => f.id === this.reportForm.folderId);
469
+ const report = {
470
+ id: Date.now(),
471
+ title: this.reportForm.title,
472
+ submittedBy: this.reportForm.submittedBy,
473
+ date: this.reportForm.date,
474
+ findings: this.reportForm.findings,
475
+ correctiveActions: this.reportForm.correctiveActions,
476
+ files: [...this.reportForm.files],
477
+ entity_type: this.reportForm.entity_type,
478
+ entity_id: this.reportForm.entity_id,
479
+ entity_ref: this.reportForm.entity_ref,
480
+ folderId: this.reportForm.folderId || null,
481
+ folderName: folder?.name || null,
482
+ submittedAt: new Date(),
483
+ };
484
+
485
+ this.linkedReports.push(report);
486
+ this.$emit('report-submitted', report);
487
+ this.$emit('data-updated', {
488
+ folders: this.folders,
489
+ allFiles: this.getAllFilesData(),
490
+ linkedReports: this.linkedReports,
491
+ });
492
+
493
+ // Reset form
494
+ this.reportForm = {
495
+ title: '', submittedBy: '', folderId: '',
496
+ date: new Date().toISOString().split('T')[0],
497
+ findings: '', correctiveActions: '', files: [],
498
+ entity_type: '', entity_id: '', entity_ref: '',
499
+ entity_label: '', selectedMissing: '',
500
+ };
501
+ this.showReportModal = false;
502
+
503
+ // Highlight the new report
504
+ this.$nextTick(() => {
505
+ this.highlightedRef = report.entity_ref;
506
+ this.scrollToReport(report.entity_ref);
507
+ setTimeout(() => { this.highlightedRef = null; }, 3000);
508
+ });
509
+ },
510
+
511
+ // ── View Report ──────────────────────────────────────────
512
+ viewReport(report) {
513
+ this.viewingReport = report;
514
+ this.showViewModal = true;
515
+ this.$emit('report-viewed', report);
516
+ },
517
+
518
+ scrollToReport(ref) {
519
+ this.$nextTick(() => {
520
+ const el = this.$refs['report-' + ref];
521
+ if (el) {
522
+ const target = Array.isArray(el) ? el[0] : el;
523
+ target?.scrollIntoView({ behavior: 'smooth', block: 'center' });
524
+ }
525
+ });
526
+ },
527
+
528
+ // ── File Helpers ─────────────────────────────────────────
529
+ handleReportFileAttach(e) {
530
+ this.reportForm.files.push(...Array.from(e.target.files));
531
+ e.target.value = '';
532
+ },
533
+ handleReportFileDrop(e) {
534
+ this.reportForm.files.push(...Array.from(e.dataTransfer.files));
535
+ },
536
+ removeReportFile(i) { this.reportForm.files.splice(i, 1); },
537
+
538
+ // ── Folder Methods ───────────────────────────────────────
539
+ createFolder() {
540
+ if (!this.newFolderName.trim()) return;
541
+ const newFolder = { id: Date.now(), name: this.newFolderName.trim(), files: [], createdAt: new Date() };
542
+ this.folders.push(newFolder);
543
+ this.$emit('folder-created', newFolder);
544
+ this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData(), linkedReports: this.linkedReports });
545
+ this.newFolderName = '';
546
+ this.showNewFolderModal = false;
547
+ },
548
+
549
+ selectFolder(folder) { this.selectedFolder = folder; this.$refs.fileInput.click(); },
550
+
551
+ handleFileSelection(event) {
552
+ const files = Array.from(event.target.files);
553
+ if (!this.selectedFolder || files.length === 0) return;
554
+ const newFiles = files.map(file => ({ id: Date.now() + Math.random(), name: file.name, size: file.size, type: file.type, file, addedAt: new Date() }));
555
+ this.selectedFolder.files.push(...newFiles);
556
+ this.$emit('files-added', { folderId: this.selectedFolder.id, files: newFiles });
557
+ this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData(), linkedReports: this.linkedReports });
558
+ event.target.value = '';
559
+ this.selectedFolder = null;
560
+ },
561
+
562
+ removeFile(folderId, fileId) {
563
+ const folder = this.folders.find(f => f.id === folderId);
564
+ if (folder) {
565
+ const i = folder.files.findIndex(f => f.id === fileId);
566
+ if (i > -1) {
567
+ const removedFile = folder.files.splice(i, 1)[0];
568
+ this.$emit('file-removed', { folderId, file: removedFile });
569
+ this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData(), linkedReports: this.linkedReports });
570
+ }
571
+ }
572
+ },
573
+
574
+ deleteFolder(folderId) {
575
+ if (confirm('Delete this folder and all its files?')) {
576
+ const i = this.folders.findIndex(f => f.id === folderId);
577
+ if (i > -1) {
578
+ const deleted = this.folders.splice(i, 1)[0];
579
+ this.$emit('folder-deleted', deleted);
580
+ this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData(), linkedReports: this.linkedReports });
581
+ }
582
+ }
583
+ },
584
+
585
+ toggleFolder(folderId) {
586
+ const i = this.expandedFolders.indexOf(folderId);
587
+ this.expandedFolders = i > -1 ? [] : [folderId];
588
+ },
589
+ closeAllFolders() { this.expandedFolders = []; },
590
+ getExpandedFolder() {
591
+ return this.expandedFolders.length > 0
592
+ ? this.folders.find(f => f.id === this.expandedFolders[0]) || { files: [], name: '' }
593
+ : { files: [], name: '' };
594
+ },
595
+ showPreview(id) { this.previewFolder = id; },
596
+ hidePreview() { this.previewFolder = null; },
597
+
598
+ formatFileSize(bytes) {
599
+ if (!bytes) return '0 Bytes';
600
+ const k = 1024, sizes = ['Bytes', 'KB', 'MB', 'GB'];
601
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
602
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
603
+ },
604
+ formatDate(d) { return d ? new Date(d).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) : '—'; },
605
+ getAllFilesData() {
606
+ return this.folders.flatMap(folder =>
607
+ folder.files.map(file => ({ ...file, folderName: folder.name, folderId: folder.id }))
608
+ );
609
+ },
610
+ }
611
+ };
612
+ </script>
613
+
614
+ <style scoped>
615
+ .reports-container {
616
+ padding: 24px;
617
+ }
618
+
619
+ /* ── Banner ── */
620
+ .missing-banner {
621
+ background: linear-gradient(135deg, #fff3cd, #ffe8a1);
622
+ border: 1.5px solid #f0ad4e;
623
+ border-radius: 10px;
624
+ margin-bottom: 22px;
625
+ overflow: hidden;
626
+ }
627
+
628
+ .missing-banner-inner {
629
+ display: flex;
630
+ align-items: center;
631
+ gap: 14px;
632
+ padding: 14px 18px;
633
+ flex-wrap: wrap;
634
+ }
635
+
636
+ .missing-icon {
637
+ font-size: 22px;
638
+ color: #856404;
639
+ flex-shrink: 0;
640
+ }
641
+
642
+ .missing-content {
643
+ flex: 1;
644
+ min-width: 0;
645
+ }
646
+
647
+ .missing-content strong {
648
+ font-size: 14px;
649
+ color: #664d03;
650
+ display: block;
651
+ }
652
+
653
+ .missing-content p {
654
+ margin: 2px 0 0;
655
+ font-size: 12px;
656
+ color: #856404;
657
+ }
658
+
659
+ .ref-chip {
660
+ background: #856404;
661
+ color: #fff;
662
+ border-radius: 4px;
663
+ padding: 1px 8px;
664
+ font-family: monospace;
665
+ font-size: 12px;
666
+ font-weight: 700;
667
+ }
668
+
669
+ .btn-dismiss {
670
+ background: none;
671
+ border: none;
672
+ color: #856404;
673
+ cursor: pointer;
674
+ font-size: 15px;
675
+ padding: 4px 6px;
676
+ border-radius: 4px;
677
+ transition: background .15s;
678
+ }
679
+
680
+ .btn-dismiss:hover {
681
+ background: rgba(0, 0, 0, .08);
682
+ }
683
+
684
+ .slide-down-enter-active,
685
+ .slide-down-leave-active {
686
+ transition: all .25s;
687
+ }
688
+
689
+ .slide-down-enter-from,
690
+ .slide-down-leave-to {
691
+ opacity: 0;
692
+ transform: translateY(-12px);
693
+ }
694
+
695
+ /* ── Page Header ── */
696
+ .page-header {
697
+ margin-bottom: 24px;
698
+ border-bottom: 1px solid #e0e0e0;
699
+ padding-bottom: 16px;
700
+ }
701
+
702
+ .page-title {
703
+ font-size: 22px;
704
+ font-weight: 700;
705
+ color: #1e3c72;
706
+ margin: 0;
707
+ }
708
+
709
+ .page-subtitle {
710
+ font-size: 13px;
711
+ color: #6c757d;
712
+ margin: 2px 0 0;
713
+ }
714
+
715
+ .header-actions {
716
+ display: flex;
717
+ gap: 8px;
718
+ }
719
+
720
+ /* ── Section Label ── */
721
+ .section-label {
722
+ font-size: 12px;
723
+ font-weight: 700;
724
+ text-transform: uppercase;
725
+ letter-spacing: .06em;
726
+ color: #495057;
727
+ margin-bottom: 14px;
728
+ display: flex;
729
+ align-items: center;
730
+ gap: 7px;
731
+ }
732
+
733
+ /* ── Report Cards ── */
734
+ .report-cards {
735
+ display: flex;
736
+ flex-direction: column;
737
+ gap: 10px;
738
+ margin-bottom: 24px;
739
+ }
740
+
741
+ .report-card {
742
+ display: flex;
743
+ align-items: center;
744
+ gap: 14px;
745
+ background: #fff;
746
+ border: 1.5px solid #e9ecef;
747
+ border-radius: 10px;
748
+ padding: 14px 18px;
749
+ cursor: pointer;
750
+ transition: all .2s;
751
+ }
752
+
753
+ .report-card:hover {
754
+ border-color: #2a5298;
755
+ box-shadow: 0 2px 10px rgba(42, 82, 152, .1);
756
+ }
757
+
758
+ .report-card--highlighted {
759
+ border-color: #f0ad4e !important;
760
+ background: #fffbf0 !important;
761
+ box-shadow: 0 0 0 3px rgba(240, 173, 78, .25) !important;
762
+ animation: flash-highlight 1.5s ease;
763
+ }
764
+
765
+ @keyframes flash-highlight {
766
+
767
+ 0%,
768
+ 100% {
769
+ background: #fffbf0;
770
+ }
771
+
772
+ 50% {
773
+ background: #fff3cd;
774
+ }
775
+ }
776
+
777
+ .rc-badge {
778
+ font-size: 10px;
779
+ font-weight: 700;
780
+ padding: 3px 8px;
781
+ border-radius: 4px;
782
+ text-transform: uppercase;
783
+ white-space: nowrap;
784
+ flex-shrink: 0;
785
+ }
786
+
787
+ .rc-badge.drill {
788
+ background: #d1ecf1;
789
+ color: #0c5460;
790
+ }
791
+
792
+ .rc-badge.incident {
793
+ background: #f8d7da;
794
+ color: #721c24;
795
+ }
796
+
797
+ .rc-body {
798
+ display: flex;
799
+ flex-direction: column;
800
+ flex: 1;
801
+ min-width: 0;
802
+ }
803
+
804
+ .rc-ref {
805
+ font-family: monospace;
806
+ font-size: 13px;
807
+ color: #2a5298;
808
+ font-weight: 700;
809
+ }
810
+
811
+ .rc-title {
812
+ font-size: 14px;
813
+ color: #212529;
814
+ white-space: nowrap;
815
+ overflow: hidden;
816
+ text-overflow: ellipsis;
817
+ }
818
+
819
+ .rc-meta {
820
+ font-size: 11px;
821
+ color: #6c757d;
822
+ margin-top: 2px;
823
+ }
824
+
825
+ .rc-folder {
826
+ font-size: 11px;
827
+ color: #6c757d;
828
+ background: #f8f9fa;
829
+ border-radius: 4px;
830
+ padding: 2px 8px;
831
+ flex-shrink: 0;
832
+ }
833
+
834
+ .rc-arrow {
835
+ color: #ced4da;
836
+ font-size: 14px;
837
+ }
838
+
839
+ /* ── Folders ── */
840
+ .folders-section {
841
+ display: grid;
842
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
843
+ gap: 18px;
844
+ padding: 4px 0;
845
+ }
846
+
847
+ .folder-card {
848
+ position: relative;
849
+ border: 2px solid #e0e0e0;
850
+ border-radius: 10px;
851
+ padding: 15px;
852
+ background: #fff;
853
+ cursor: pointer;
854
+ aspect-ratio: 1;
855
+ display: flex;
856
+ flex-direction: column;
857
+ align-items: center;
858
+ justify-content: center;
859
+ transition: all .2s;
860
+ }
861
+
862
+ .folder-card:hover {
863
+ border-color: #2a5298;
864
+ box-shadow: 0 4px 12px rgba(0, 0, 0, .1);
865
+ transform: translateY(-2px);
866
+ }
867
+
868
+ .folder-content {
869
+ display: flex;
870
+ flex-direction: column;
871
+ align-items: center;
872
+ text-align: center;
873
+ width: 100%;
874
+ height: 100%;
875
+ justify-content: center;
876
+ }
877
+
878
+ .folder-name {
879
+ margin-top: 8px;
880
+ font-weight: 600;
881
+ font-size: .85rem;
882
+ word-break: break-word;
883
+ overflow: hidden;
884
+ text-overflow: ellipsis;
885
+ display: -webkit-box;
886
+ -webkit-line-clamp: 2;
887
+ -webkit-box-orient: vertical;
888
+ }
889
+
890
+ .folder-actions-overlay {
891
+ position: absolute;
892
+ top: 5px;
893
+ right: 5px;
894
+ display: flex;
895
+ gap: 4px;
896
+ opacity: 0;
897
+ transition: opacity .2s;
898
+ }
899
+
900
+ .folder-card:hover .folder-actions-overlay {
901
+ opacity: 1;
902
+ }
903
+
904
+ .btn-icon {
905
+ width: 28px;
906
+ height: 28px;
907
+ padding: 0;
908
+ display: flex;
909
+ align-items: center;
910
+ justify-content: center;
911
+ background: #fff;
912
+ border: 1px solid #dee2e6;
913
+ border-radius: 4px;
914
+ cursor: pointer;
915
+ }
916
+
917
+ .btn-icon:hover {
918
+ background: #f8f9fa;
919
+ border-color: #2a5298;
920
+ }
921
+
922
+ .btn-icon-danger:hover {
923
+ border-color: #dc3545;
924
+ color: #dc3545;
925
+ }
926
+
927
+ .btn-icon i {
928
+ font-size: .82rem;
929
+ }
930
+
931
+ /* ── Expanded Folder ── */
932
+ .folder-modal-overlay {
933
+ position: fixed;
934
+ inset: 0;
935
+ background: rgba(0, 0, 0, .5);
936
+ z-index: 1000;
937
+ display: flex;
938
+ align-items: center;
939
+ justify-content: center;
940
+ }
941
+
942
+ .folder-expanded {
943
+ background: #fff;
944
+ border-radius: 12px;
945
+ width: 90%;
946
+ max-width: 700px;
947
+ max-height: 80vh;
948
+ overflow: hidden;
949
+ display: flex;
950
+ flex-direction: column;
951
+ box-shadow: 0 10px 40px rgba(0, 0, 0, .3);
952
+ }
953
+
954
+ .expanded-header {
955
+ display: flex;
956
+ justify-content: space-between;
957
+ align-items: center;
958
+ padding: 18px 24px;
959
+ border-bottom: 2px solid #f0f0f0;
960
+ background: #f8f9fa;
961
+ }
962
+
963
+ .expanded-header strong {
964
+ font-size: 1.1rem;
965
+ }
966
+
967
+ .btn-close-expanded {
968
+ background: transparent;
969
+ border: none;
970
+ font-size: 1.1rem;
971
+ cursor: pointer;
972
+ padding: 5px 8px;
973
+ color: #666;
974
+ border-radius: 4px;
975
+ transition: color .2s;
976
+ }
977
+
978
+ .btn-close-expanded:hover {
979
+ color: #dc3545;
980
+ }
981
+
982
+ .files-list {
983
+ padding: 18px;
984
+ max-height: 55vh;
985
+ overflow-y: auto;
986
+ }
987
+
988
+ .file-item {
989
+ padding: 10px 14px;
990
+ background: #f8f9fa;
991
+ border-radius: 6px;
992
+ margin-bottom: 8px;
993
+ }
994
+
995
+ .empty-folder-message {
996
+ padding: 40px 20px;
997
+ text-align: center;
998
+ color: #6c757d;
999
+ }
1000
+
1001
+ .empty-folder-message i {
1002
+ font-size: 2.5rem;
1003
+ color: #dee2e6;
1004
+ }
1005
+
1006
+ .empty-folder-message p {
1007
+ margin: 12px 0;
1008
+ }
1009
+
1010
+ /* ── Modals ── */
1011
+ .modal {
1012
+ display: none;
1013
+ position: fixed;
1014
+ z-index: 1050;
1015
+ inset: 0;
1016
+ background: rgba(0, 0, 0, .5);
1017
+ align-items: center;
1018
+ justify-content: center;
1019
+ }
1020
+
1021
+ .modal.show {
1022
+ display: flex;
1023
+ }
1024
+
1025
+ .modal-dialog {
1026
+ max-width: 500px;
1027
+ width: 95%;
1028
+ margin: 1.5rem auto;
1029
+ }
1030
+
1031
+ .modal-dialog.modal-lg {
1032
+ max-width: 780px;
1033
+ }
1034
+
1035
+ .modal-content {
1036
+ background: #fff;
1037
+ border-radius: 12px;
1038
+ box-shadow: 0 10px 40px rgba(0, 0, 0, .25);
1039
+ overflow: hidden;
1040
+ }
1041
+
1042
+ .modal-header {
1043
+ padding: 18px 22px;
1044
+ border-bottom: 1px solid #dee2e6;
1045
+ display: flex;
1046
+ justify-content: space-between;
1047
+ align-items: center;
1048
+ }
1049
+
1050
+ .modal-body {
1051
+ padding: 22px;
1052
+ max-height: 70vh;
1053
+ overflow-y: auto;
1054
+ }
1055
+
1056
+ .modal-footer {
1057
+ padding: 14px 22px;
1058
+ border-top: 1px solid #dee2e6;
1059
+ display: flex;
1060
+ justify-content: flex-end;
1061
+ gap: 10px;
1062
+ }
1063
+
1064
+ .rpt-modal-header {
1065
+ background: linear-gradient(135deg, #1e3c72, #2a5298);
1066
+ color: #fff;
1067
+ }
1068
+
1069
+ .btn-close {
1070
+ background: transparent;
1071
+ border: none;
1072
+ font-size: 1.4rem;
1073
+ cursor: pointer;
1074
+ opacity: .6;
1075
+ }
1076
+
1077
+ .btn-close:hover {
1078
+ opacity: 1;
1079
+ }
1080
+
1081
+ /* ── Report Form ── */
1082
+ .rpt-entity-box {
1083
+ background: #f8f9fa;
1084
+ border: 1px solid #e0e0e0;
1085
+ border-radius: 8px;
1086
+ padding: 14px 16px;
1087
+ margin-bottom: 4px;
1088
+ }
1089
+
1090
+ .prefilled-entity {
1091
+ display: flex;
1092
+ align-items: center;
1093
+ gap: 10px;
1094
+ flex-wrap: wrap;
1095
+ }
1096
+
1097
+ .entity-chip {
1098
+ font-size: 10px;
1099
+ font-weight: 700;
1100
+ padding: 2px 8px;
1101
+ border-radius: 4px;
1102
+ text-transform: uppercase;
1103
+ }
1104
+
1105
+ .entity-chip.drill {
1106
+ background: #d1ecf1;
1107
+ color: #0c5460;
1108
+ }
1109
+
1110
+ .entity-chip.incident {
1111
+ background: #f8d7da;
1112
+ color: #721c24;
1113
+ }
1114
+
1115
+ .input-row {
1116
+ display: grid;
1117
+ grid-template-columns: 1fr 1fr;
1118
+ gap: 14px;
1119
+ margin-bottom: 14px;
1120
+ }
1121
+
1122
+ .form-group {
1123
+ display: flex;
1124
+ flex-direction: column;
1125
+ margin-bottom: 14px;
1126
+ }
1127
+
1128
+ .form-label {
1129
+ font-size: 13px;
1130
+ margin-bottom: 5px;
1131
+ color: #495057;
1132
+ }
1133
+
1134
+ .form-control {
1135
+ padding: 9px 12px;
1136
+ border: 1px solid #ced4da;
1137
+ border-radius: 6px;
1138
+ font-size: 14px;
1139
+ transition: border-color .2s;
1140
+ width: 100%;
1141
+ }
1142
+
1143
+ .form-control:focus {
1144
+ outline: none;
1145
+ border-color: #2a5298;
1146
+ box-shadow: 0 0 0 3px rgba(42, 82, 152, .1);
1147
+ }
1148
+
1149
+ textarea.form-control {
1150
+ resize: vertical;
1151
+ min-height: 80px;
1152
+ }
1153
+
1154
+ select.form-control {
1155
+ cursor: pointer;
1156
+ }
1157
+
1158
+ /* File drop zone */
1159
+ .file-drop-zone {
1160
+ border: 2px dashed #ced4da;
1161
+ border-radius: 8px;
1162
+ padding: 20px;
1163
+ text-align: center;
1164
+ cursor: pointer;
1165
+ transition: border-color .2s;
1166
+ min-height: 80px;
1167
+ }
1168
+
1169
+ .file-drop-zone:hover {
1170
+ border-color: #2a5298;
1171
+ }
1172
+
1173
+ .attached-files {
1174
+ display: flex;
1175
+ flex-wrap: wrap;
1176
+ gap: 6px;
1177
+ margin-top: 10px;
1178
+ justify-content: center;
1179
+ }
1180
+
1181
+ .attached-chip {
1182
+ background: #e9ecef;
1183
+ border-radius: 5px;
1184
+ padding: 3px 10px;
1185
+ font-size: 12px;
1186
+ display: flex;
1187
+ align-items: center;
1188
+ gap: 5px;
1189
+ }
1190
+
1191
+ .attached-chip button {
1192
+ background: none;
1193
+ border: none;
1194
+ cursor: pointer;
1195
+ color: #dc3545;
1196
+ padding: 0;
1197
+ font-size: 11px;
1198
+ }
1199
+
1200
+ /* ── Report Viewer ── */
1201
+ .view-grid {
1202
+ display: grid;
1203
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
1204
+ gap: 14px;
1205
+ margin-bottom: 20px;
1206
+ }
1207
+
1208
+ .view-item {
1209
+ display: flex;
1210
+ flex-direction: column;
1211
+ gap: 3px;
1212
+ }
1213
+
1214
+ .view-item label {
1215
+ font-size: 11px;
1216
+ font-weight: 700;
1217
+ text-transform: uppercase;
1218
+ letter-spacing: .05em;
1219
+ color: #6c757d;
1220
+ }
1221
+
1222
+ .view-item span {
1223
+ font-size: 14px;
1224
+ color: #212529;
1225
+ }
1226
+
1227
+ .ref-mono {
1228
+ font-family: monospace;
1229
+ font-size: 13px;
1230
+ color: #2a5298;
1231
+ font-weight: 700;
1232
+ }
1233
+
1234
+ .view-section {
1235
+ margin-bottom: 18px;
1236
+ }
1237
+
1238
+ .view-section label {
1239
+ font-size: 12px;
1240
+ font-weight: 700;
1241
+ text-transform: uppercase;
1242
+ letter-spacing: .05em;
1243
+ color: #6c757d;
1244
+ display: block;
1245
+ margin-bottom: 6px;
1246
+ }
1247
+
1248
+ .view-text {
1249
+ background: #f8f9fa;
1250
+ border-radius: 6px;
1251
+ padding: 12px 14px;
1252
+ font-size: 14px;
1253
+ line-height: 1.6;
1254
+ color: #212529;
1255
+ white-space: pre-wrap;
1256
+ }
1257
+
1258
+ /* ── Buttons ── */
1259
+ .btn {
1260
+ padding: 9px 18px;
1261
+ border: none;
1262
+ border-radius: 8px;
1263
+ font-size: 13px;
1264
+ font-weight: 600;
1265
+ cursor: pointer;
1266
+ transition: all .2s;
1267
+ display: inline-flex;
1268
+ align-items: center;
1269
+ gap: 6px;
1270
+ }
1271
+
1272
+ .btn:disabled {
1273
+ opacity: .6;
1274
+ cursor: not-allowed;
1275
+ }
1276
+
1277
+ .btn-primary {
1278
+ background: #2a5298;
1279
+ color: #fff;
1280
+ }
1281
+
1282
+ .btn-primary:hover:not(:disabled) {
1283
+ background: #1e3c72;
1284
+ }
1285
+
1286
+ .btn-success {
1287
+ background: #28a745;
1288
+ color: #fff;
1289
+ }
1290
+
1291
+ .btn-success:hover:not(:disabled) {
1292
+ background: #218838;
1293
+ }
1294
+
1295
+ .btn-secondary {
1296
+ background: #6c757d;
1297
+ color: #fff;
1298
+ }
1299
+
1300
+ .btn-secondary:hover:not(:disabled) {
1301
+ background: #5a6268;
1302
+ }
1303
+
1304
+ .btn-outline-primary {
1305
+ background: transparent;
1306
+ border: 1.5px solid #2a5298;
1307
+ color: #2a5298;
1308
+ }
1309
+
1310
+ .btn-outline-primary:hover {
1311
+ background: #2a5298;
1312
+ color: #fff;
1313
+ }
1314
+
1315
+ .btn-outline-danger {
1316
+ background: transparent;
1317
+ border: 1.5px solid #dc3545;
1318
+ color: #dc3545;
1319
+ padding: 4px 10px;
1320
+ font-size: 12px;
1321
+ }
1322
+
1323
+ .btn-outline-danger:hover {
1324
+ background: #dc3545;
1325
+ color: #fff;
1326
+ }
1327
+
1328
+ .btn-warning {
1329
+ background: #f0ad4e;
1330
+ color: #664d03;
1331
+ }
1332
+
1333
+ .btn-warning:hover {
1334
+ background: #e0972e;
1335
+ }
1336
+
1337
+ .btn-link {
1338
+ background: none;
1339
+ border: none;
1340
+ padding: 0;
1341
+ font-size: 13px;
1342
+ text-decoration: none;
1343
+ cursor: pointer;
1344
+ }
1345
+
1346
+ .btn-sm {
1347
+ padding: 5px 12px;
1348
+ font-size: 12px;
1349
+ }
1350
+
1351
+ .fw-bold {
1352
+ font-weight: 700;
1353
+ }
1354
+
1355
+ .fw-semibold {
1356
+ font-weight: 600;
1357
+ }
1358
+
1359
+ .me-1 {
1360
+ margin-right: 4px;
1361
+ }
1362
+
1363
+ .me-2 {
1364
+ margin-right: 8px;
1365
+ }
1366
+
1367
+ .ms-2 {
1368
+ margin-left: 8px;
1369
+ }
1370
+
1371
+ .ms-auto {
1372
+ margin-left: auto;
1373
+ }
1374
+
1375
+ .mt-3 {
1376
+ margin-top: 16px;
1377
+ }
1378
+
1379
+ .mt-4 {
1380
+ margin-top: 24px;
1381
+ }
1382
+
1383
+ .mt-1 {
1384
+ margin-top: 4px;
1385
+ }
1386
+
1387
+ .mb-0 {
1388
+ margin-bottom: 0;
1389
+ }
1390
+
1391
+ .mb-3 {
1392
+ margin-bottom: 16px;
1393
+ }
1394
+
1395
+ .text-muted {
1396
+ color: #6c757d !important;
1397
+ }
1398
+
1399
+ .text-danger {
1400
+ color: #dc3545 !important;
1401
+ }
1402
+
1403
+ .text-success {
1404
+ color: #28a745 !important;
1405
+ }
1406
+
1407
+ .text-warning {
1408
+ color: #f0ad4e !important;
1409
+ }
1410
+
1411
+ .d-flex {
1412
+ display: flex;
1413
+ }
1414
+
1415
+ .align-items-center {
1416
+ align-items: center;
1417
+ }
1418
+
1419
+ .justify-content-between {
1420
+ justify-content: space-between;
1421
+ }
1422
+
1423
+ .text-center {
1424
+ text-align: center;
1425
+ }
1426
+
1427
+ .empty-state {
1428
+ padding: 60px 20px;
1429
+ }
1430
+
1431
+ .empty-state h5 {
1432
+ margin-top: 14px;
1433
+ }
1434
+
1435
+ @media (max-width: 640px) {
1436
+ .input-row {
1437
+ grid-template-columns: 1fr;
1438
+ }
1439
+
1440
+ .header-actions {
1441
+ gap: 6px;
1442
+ }
1443
+
1444
+ .btn {
1445
+ padding: 8px 12px;
1446
+ font-size: 12px;
1447
+ }
1448
+ }
1449
+ </style>