oceanhelm 0.0.11 → 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.
@@ -1,32 +1,99 @@
1
1
  <template>
2
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 ── -->
3
34
  <div class="page-header d-flex justify-content-between align-items-center">
4
- <h4 style="margin-left: 20px;">Reports</h4>
5
- <div class="d-flex">
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>
6
43
  <button class="btn btn-primary" @click="showNewFolderModal = true">
7
44
  <i class="bi bi-folder-plus"></i> New Folder
8
45
  </button>
9
46
  </div>
10
47
  </div>
11
48
 
12
- <!-- Folders List -->
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>
13
83
  <div class="folders-section" v-if="folders.length > 0">
14
- <div class="folder-card"
15
- v-for="folder in folders"
16
- :key="folder.id"
17
- @mouseenter="showPreview(folder.id)"
18
- @mouseleave="hidePreview">
84
+ <div class="folder-card" v-for="folder in folders" :key="folder.id" @mouseenter="showPreview(folder.id)"
85
+ @mouseleave="hidePreview">
19
86
  <div class="folder-content" @click="toggleFolder(folder.id)">
20
87
  <i class="bi bi-folder-fill text-warning" style="font-size: 2.5rem;"></i>
21
88
  <div class="folder-name">{{ folder.name }}</div>
22
89
  <small class="text-muted">{{ folder.files.length }} file(s)</small>
23
90
  </div>
24
-
25
91
  <div class="folder-actions-overlay">
26
92
  <button class="btn btn-sm btn-icon" @click.stop="selectFolder(folder)" title="Add Files">
27
93
  <i class="bi bi-file-earmark-plus"></i>
28
94
  </button>
29
- <button class="btn btn-sm btn-icon" @click.stop="deleteFolder(folder.id)" title="Delete Folder">
95
+ <button class="btn btn-sm btn-icon btn-icon-danger" @click.stop="deleteFolder(folder.id)"
96
+ title="Delete Folder">
30
97
  <i class="bi bi-trash"></i>
31
98
  </button>
32
99
  </div>
@@ -34,31 +101,31 @@
34
101
  </div>
35
102
 
36
103
  <!-- Empty State -->
37
- <div class="empty-state text-center" v-else>
104
+ <div class="empty-state text-center" v-if="folders.length === 0 && linkedReports.length === 0">
38
105
  <i class="bi bi-folder2-open" style="font-size: 4rem; color: #ccc;"></i>
39
- <h5 class="mt-3 text-muted">No folders yet</h5>
40
- <p class="text-muted">Create a folder to start organizing your reports</p>
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>
41
108
  </div>
42
109
 
43
- <!-- Expanded Folder Modal (Click to Open) -->
110
+ <!-- ── Expanded Folder Modal ── -->
44
111
  <div class="folder-modal-overlay" v-if="expandedFolders.length > 0" @click="closeAllFolders">
45
112
  <div class="folder-expanded" @click.stop>
46
113
  <div class="expanded-header">
47
114
  <strong>{{ getExpandedFolder().name }}</strong>
48
- <button class="btn btn-sm btn-close-expanded" @click="closeAllFolders">
115
+ <button class="btn-close-expanded" @click="closeAllFolders">
49
116
  <i class="bi bi-x-lg"></i>
50
117
  </button>
51
118
  </div>
52
-
53
119
  <div class="files-list" v-if="getExpandedFolder().files.length > 0">
54
- <div class="file-item d-flex justify-content-between align-items-center"
55
- v-for="file in getExpandedFolder().files" :key="file.id">
120
+ <div class="file-item d-flex justify-content-between align-items-center"
121
+ v-for="file in getExpandedFolder().files" :key="file.id">
56
122
  <div class="file-info d-flex align-items-center">
57
123
  <i class="bi bi-file-earmark-text me-2"></i>
58
124
  <span>{{ file.name }}</span>
59
125
  <small class="text-muted ms-2">({{ formatFileSize(file.size) }})</small>
60
126
  </div>
61
- <button class="btn btn-sm btn-outline-danger" @click="removeFile(getExpandedFolder().id, file.id)">
127
+ <button class="btn btn-sm btn-outline-danger"
128
+ @click="removeFile(getExpandedFolder().id, file.id)">
62
129
  <i class="bi bi-x-circle"></i>
63
130
  </button>
64
131
  </div>
@@ -73,29 +140,24 @@
73
140
  </div>
74
141
  </div>
75
142
 
76
- <!-- New Folder Modal -->
143
+ <!-- ── New Folder Modal ── -->
77
144
  <div class="modal" :class="{ 'show': showNewFolderModal }" @click.self="showNewFolderModal = false">
78
145
  <div class="modal-dialog modal-dialog-centered">
79
146
  <div class="modal-content">
80
147
  <div class="modal-header">
81
- <h5 class="modal-title">Create New Folder</h5>
148
+ <h5 class="modal-title"><i class="bi bi-folder-plus me-2"></i>Create New Folder</h5>
82
149
  <button type="button" class="btn-close" @click="showNewFolderModal = false"></button>
83
150
  </div>
84
151
  <div class="modal-body">
85
- <div class="mb-3">
86
- <label class="form-label">Folder Name</label>
87
- <input
88
- type="text"
89
- class="form-control"
90
- v-model="newFolderName"
91
- @keyup.enter="createFolder"
92
- placeholder="Enter folder name"
93
- />
94
- </div>
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" />
95
155
  </div>
96
156
  <div class="modal-footer">
97
- <button type="button" class="btn btn-secondary" @click="showNewFolderModal = false">Cancel</button>
98
- <button type="button" class="btn btn-primary" @click="createFolder" :disabled="!newFolderName.trim()">
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()">
99
161
  Create Folder
100
162
  </button>
101
163
  </div>
@@ -103,87 +165,396 @@
103
165
  </div>
104
166
  </div>
105
167
 
106
- <!-- File Input (Hidden) -->
107
- <input
108
- ref="fileInput"
109
- type="file"
110
- multiple
111
- style="display: none;"
112
- @change="handleFileSelection"
113
- />
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" />
114
342
  </div>
115
343
  </template>
116
344
 
117
345
  <script>
118
346
  export default {
119
347
  name: 'Reports',
120
-
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
+
121
374
  data() {
122
375
  return {
123
376
  folders: [],
377
+ linkedReports: [], // submitted QHSE reports
124
378
  showNewFolderModal: false,
125
379
  newFolderName: '',
126
380
  selectedFolder: null,
127
381
  expandedFolders: [],
128
- previewFolder: null
129
- }
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
+ };
130
403
  },
131
404
 
132
- emits: [
133
- 'folder-created',
134
- 'files-added',
135
- 'file-removed',
136
- 'folder-deleted',
137
- 'data-updated'
138
- ],
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
+ },
139
428
 
140
429
  methods: {
141
- createFolder() {
142
- if (!this.newFolderName.trim()) return;
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
+ },
143
455
 
144
- const newFolder = {
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 = {
145
470
  id: Date.now(),
146
- name: this.newFolderName.trim(),
147
- files: [],
148
- createdAt: new Date()
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: '',
149
500
  };
501
+ this.showReportModal = false;
150
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() };
151
542
  this.folders.push(newFolder);
152
543
  this.$emit('folder-created', newFolder);
153
- this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData() });
154
-
544
+ this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData(), linkedReports: this.linkedReports });
155
545
  this.newFolderName = '';
156
546
  this.showNewFolderModal = false;
157
547
  },
158
548
 
159
- selectFolder(folder) {
160
- this.selectedFolder = folder;
161
- this.$refs.fileInput.click();
162
- },
549
+ selectFolder(folder) { this.selectedFolder = folder; this.$refs.fileInput.click(); },
163
550
 
164
551
  handleFileSelection(event) {
165
552
  const files = Array.from(event.target.files);
166
-
167
553
  if (!this.selectedFolder || files.length === 0) return;
168
-
169
- const newFiles = files.map(file => ({
170
- id: Date.now() + Math.random(),
171
- name: file.name,
172
- size: file.size,
173
- type: file.type,
174
- file: file,
175
- addedAt: new Date()
176
- }));
177
-
554
+ const newFiles = files.map(file => ({ id: Date.now() + Math.random(), name: file.name, size: file.size, type: file.type, file, addedAt: new Date() }));
178
555
  this.selectedFolder.files.push(...newFiles);
179
-
180
- this.$emit('files-added', {
181
- folderId: this.selectedFolder.id,
182
- files: newFiles
183
- });
184
- this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData() });
185
-
186
- // Reset file input
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 });
187
558
  event.target.value = '';
188
559
  this.selectedFolder = null;
189
560
  },
@@ -191,226 +562,324 @@ export default {
191
562
  removeFile(folderId, fileId) {
192
563
  const folder = this.folders.find(f => f.id === folderId);
193
564
  if (folder) {
194
- const fileIndex = folder.files.findIndex(f => f.id === fileId);
195
- if (fileIndex > -1) {
196
- const removedFile = folder.files.splice(fileIndex, 1)[0];
197
- this.$emit('file-removed', {
198
- folderId,
199
- file: removedFile
200
- });
201
- this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData() });
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 });
202
570
  }
203
571
  }
204
572
  },
205
573
 
206
574
  deleteFolder(folderId) {
207
- if (confirm('Are you sure you want to delete this folder and all its files?')) {
208
- const folderIndex = this.folders.findIndex(f => f.id === folderId);
209
- if (folderIndex > -1) {
210
- const deletedFolder = this.folders.splice(folderIndex, 1)[0];
211
- this.$emit('folder-deleted', deletedFolder);
212
- this.$emit('data-updated', { folders: this.folders, allFiles: this.getAllFilesData() });
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 });
213
581
  }
214
582
  }
215
583
  },
216
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
+
217
598
  formatFileSize(bytes) {
218
- if (bytes === 0) return '0 Bytes';
219
- const k = 1024;
220
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
599
+ if (!bytes) return '0 Bytes';
600
+ const k = 1024, sizes = ['Bytes', 'KB', 'MB', 'GB'];
221
601
  const i = Math.floor(Math.log(bytes) / Math.log(k));
222
602
  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
223
603
  },
224
-
604
+ formatDate(d) { return d ? new Date(d).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) : '—'; },
225
605
  getAllFilesData() {
226
- return this.folders.flatMap(folder =>
227
- folder.files.map(file => ({
228
- ...file,
229
- folderName: folder.name,
230
- folderId: folder.id
231
- }))
606
+ return this.folders.flatMap(folder =>
607
+ folder.files.map(file => ({ ...file, folderName: folder.name, folderId: folder.id }))
232
608
  );
233
609
  },
610
+ }
611
+ };
612
+ </script>
234
613
 
235
- toggleFolder(folderId) {
236
- const index = this.expandedFolders.indexOf(folderId);
237
- if (index > -1) {
238
- this.expandedFolders.splice(index, 1);
239
- } else {
240
- this.expandedFolders = [folderId]; // Only one folder expanded at a time
241
- }
242
- },
614
+ <style scoped>
615
+ .reports-container {
616
+ padding: 24px;
617
+ }
243
618
 
244
- closeAllFolders() {
245
- this.expandedFolders = [];
246
- },
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
+ }
247
627
 
248
- getExpandedFolder() {
249
- if (this.expandedFolders.length > 0) {
250
- return this.folders.find(f => f.id === this.expandedFolders[0]) || { files: [], name: '' };
251
- }
252
- return { files: [], name: '' };
253
- },
628
+ .missing-banner-inner {
629
+ display: flex;
630
+ align-items: center;
631
+ gap: 14px;
632
+ padding: 14px 18px;
633
+ flex-wrap: wrap;
634
+ }
254
635
 
255
- showPreview(folderId) {
256
- this.previewFolder = folderId;
257
- },
636
+ .missing-icon {
637
+ font-size: 22px;
638
+ color: #856404;
639
+ flex-shrink: 0;
640
+ }
258
641
 
259
- hidePreview() {
260
- this.previewFolder = null;
261
- }
262
- }
642
+ .missing-content {
643
+ flex: 1;
644
+ min-width: 0;
263
645
  }
264
- </script>
265
646
 
266
- <style scoped>
267
- .reports-container {
268
- padding: 20px;
647
+ .missing-content strong {
648
+ font-size: 14px;
649
+ color: #664d03;
650
+ display: block;
269
651
  }
270
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 ── */
271
696
  .page-header {
272
- margin-bottom: 30px;
697
+ margin-bottom: 24px;
273
698
  border-bottom: 1px solid #e0e0e0;
274
- padding-bottom: 15px;
699
+ padding-bottom: 16px;
275
700
  }
276
701
 
277
- .folders-section {
278
- display: grid;
279
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
280
- gap: 20px;
281
- padding: 10px;
702
+ .page-title {
703
+ font-size: 22px;
704
+ font-weight: 700;
705
+ color: #1e3c72;
706
+ margin: 0;
282
707
  }
283
708
 
284
- @media (max-width: 768px) {
285
- .folders-section {
286
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
287
- gap: 15px;
288
- }
709
+ .page-subtitle {
710
+ font-size: 13px;
711
+ color: #6c757d;
712
+ margin: 2px 0 0;
289
713
  }
290
714
 
291
- @media (min-width: 1400px) {
292
- .folders-section {
293
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
294
- }
715
+ .header-actions {
716
+ display: flex;
717
+ gap: 8px;
295
718
  }
296
719
 
297
- .folder-card {
298
- position: relative;
299
- border: 2px solid #e0e0e0;
300
- border-radius: 8px;
301
- padding: 15px;
302
- background-color: #fff;
303
- transition: all 0.2s;
304
- cursor: pointer;
305
- aspect-ratio: 1;
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 {
306
735
  display: flex;
307
736
  flex-direction: column;
737
+ gap: 10px;
738
+ margin-bottom: 24px;
739
+ }
740
+
741
+ .report-card {
742
+ display: flex;
308
743
  align-items: center;
309
- justify-content: 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;
310
751
  }
311
752
 
312
- .folder-card:hover {
313
- border-color: #0d6efd;
314
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
315
- transform: translateY(-2px);
753
+ .report-card:hover {
754
+ border-color: #2a5298;
755
+ box-shadow: 0 2px 10px rgba(42, 82, 152, .1);
316
756
  }
317
757
 
318
- /* Hover Preview Popup */
319
- .folder-preview {
320
- position: absolute;
321
- top: 50%;
322
- left: 50%;
323
- transform: translate(-50%, -50%);
324
- background: white;
325
- border: 2px solid #0d6efd;
326
- border-radius: 8px;
327
- box-shadow: 0 8px 24px rgba(0,0,0,0.15);
328
- padding: 0;
329
- z-index: 100;
330
- min-width: 280px;
331
- max-width: 320px;
332
- pointer-events: none;
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;
333
763
  }
334
764
 
335
- .preview-header {
336
- background: #f8f9fa;
337
- padding: 12px 15px;
338
- border-bottom: 1px solid #e0e0e0;
339
- border-radius: 6px 6px 0 0;
765
+ @keyframes flash-highlight {
766
+
767
+ 0%,
768
+ 100% {
769
+ background: #fffbf0;
770
+ }
771
+
772
+ 50% {
773
+ background: #fff3cd;
774
+ }
340
775
  }
341
776
 
342
- .preview-header strong {
343
- font-size: 0.95rem;
344
- color: #333;
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;
345
785
  }
346
786
 
347
- .preview-content {
348
- padding: 10px;
349
- max-height: 200px;
350
- overflow-y: auto;
787
+ .rc-badge.drill {
788
+ background: #d1ecf1;
789
+ color: #0c5460;
351
790
  }
352
791
 
353
- .preview-file {
354
- display: flex;
355
- align-items: center;
356
- gap: 8px;
357
- padding: 6px 8px;
358
- font-size: 0.85rem;
359
- color: #495057;
360
- border-bottom: 1px solid #f0f0f0;
792
+ .rc-badge.incident {
793
+ background: #f8d7da;
794
+ color: #721c24;
361
795
  }
362
796
 
363
- .preview-file:last-child {
364
- border-bottom: none;
797
+ .rc-body {
798
+ display: flex;
799
+ flex-direction: column;
800
+ flex: 1;
801
+ min-width: 0;
365
802
  }
366
803
 
367
- .preview-file i {
368
- color: #6c757d;
369
- font-size: 0.9rem;
804
+ .rc-ref {
805
+ font-family: monospace;
806
+ font-size: 13px;
807
+ color: #2a5298;
808
+ font-weight: 700;
370
809
  }
371
810
 
372
- .preview-file span {
811
+ .rc-title {
812
+ font-size: 14px;
813
+ color: #212529;
373
814
  white-space: nowrap;
374
815
  overflow: hidden;
375
816
  text-overflow: ellipsis;
376
817
  }
377
818
 
378
- .preview-more {
379
- text-align: center;
380
- padding: 8px;
381
- font-size: 0.8rem;
819
+ .rc-meta {
820
+ font-size: 11px;
382
821
  color: #6c757d;
383
- font-style: italic;
822
+ margin-top: 2px;
384
823
  }
385
824
 
386
- .preview-empty {
387
- padding: 30px 20px;
388
- text-align: center;
389
- color: #adb5bd;
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;
390
832
  }
391
833
 
392
- .preview-empty i {
393
- font-size: 2rem;
394
- display: block;
395
- margin-bottom: 8px;
834
+ .rc-arrow {
835
+ color: #ced4da;
836
+ font-size: 14px;
396
837
  }
397
838
 
398
- .folder-content {
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;
399
855
  display: flex;
400
856
  flex-direction: column;
401
857
  align-items: center;
402
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;
403
872
  text-align: center;
404
873
  width: 100%;
405
874
  height: 100%;
875
+ justify-content: center;
406
876
  }
407
877
 
408
878
  .folder-name {
409
- margin-top: 10px;
879
+ margin-top: 8px;
410
880
  font-weight: 600;
411
- font-size: 0.9rem;
881
+ font-size: .85rem;
412
882
  word-break: break-word;
413
- max-width: 100%;
414
883
  overflow: hidden;
415
884
  text-overflow: ellipsis;
416
885
  display: -webkit-box;
@@ -423,9 +892,9 @@ export default {
423
892
  top: 5px;
424
893
  right: 5px;
425
894
  display: flex;
426
- gap: 5px;
895
+ gap: 4px;
427
896
  opacity: 0;
428
- transition: opacity 0.2s;
897
+ transition: opacity .2s;
429
898
  }
430
899
 
431
900
  .folder-card:hover .folder-actions-overlay {
@@ -439,28 +908,31 @@ export default {
439
908
  display: flex;
440
909
  align-items: center;
441
910
  justify-content: center;
442
- background: white;
911
+ background: #fff;
443
912
  border: 1px solid #dee2e6;
444
913
  border-radius: 4px;
914
+ cursor: pointer;
445
915
  }
446
916
 
447
917
  .btn-icon:hover {
448
918
  background: #f8f9fa;
449
- border-color: #0d6efd;
919
+ border-color: #2a5298;
920
+ }
921
+
922
+ .btn-icon-danger:hover {
923
+ border-color: #dc3545;
924
+ color: #dc3545;
450
925
  }
451
926
 
452
927
  .btn-icon i {
453
- font-size: 0.85rem;
928
+ font-size: .82rem;
454
929
  }
455
930
 
456
- /* Expanded Folder Modal (Click to Open) */
931
+ /* ── Expanded Folder ── */
457
932
  .folder-modal-overlay {
458
933
  position: fixed;
459
- top: 0;
460
- left: 0;
461
- right: 0;
462
- bottom: 0;
463
- background: rgba(0,0,0,0.5);
934
+ inset: 0;
935
+ background: rgba(0, 0, 0, .5);
464
936
  z-index: 1000;
465
937
  display: flex;
466
938
  align-items: center;
@@ -468,41 +940,39 @@ export default {
468
940
  }
469
941
 
470
942
  .folder-expanded {
471
- background: white;
943
+ background: #fff;
472
944
  border-radius: 12px;
473
- box-shadow: 0 10px 40px rgba(0,0,0,0.3);
474
945
  width: 90%;
475
946
  max-width: 700px;
476
947
  max-height: 80vh;
477
948
  overflow: hidden;
478
949
  display: flex;
479
950
  flex-direction: column;
951
+ box-shadow: 0 10px 40px rgba(0, 0, 0, .3);
480
952
  }
481
953
 
482
954
  .expanded-header {
483
955
  display: flex;
484
956
  justify-content: space-between;
485
957
  align-items: center;
486
- padding: 20px 25px;
958
+ padding: 18px 24px;
487
959
  border-bottom: 2px solid #f0f0f0;
488
960
  background: #f8f9fa;
489
961
  }
490
962
 
491
963
  .expanded-header strong {
492
- font-size: 1.3rem;
493
- color: #333;
964
+ font-size: 1.1rem;
494
965
  }
495
966
 
496
967
  .btn-close-expanded {
497
968
  background: transparent;
498
969
  border: none;
499
- font-size: 1.2rem;
970
+ font-size: 1.1rem;
500
971
  cursor: pointer;
501
- padding: 5px 10px;
502
- display: flex;
503
- align-items: center;
504
- justify-content: center;
505
- transition: color 0.2s;
972
+ padding: 5px 8px;
973
+ color: #666;
974
+ border-radius: 4px;
975
+ transition: color .2s;
506
976
  }
507
977
 
508
978
  .btn-close-expanded:hover {
@@ -510,22 +980,18 @@ export default {
510
980
  }
511
981
 
512
982
  .files-list {
513
- padding: 20px;
514
- max-height: 60vh;
983
+ padding: 18px;
984
+ max-height: 55vh;
515
985
  overflow-y: auto;
516
986
  }
517
987
 
518
988
  .file-item {
519
- padding: 12px;
520
- background-color: #f8f9fa;
521
- border-radius: 4px;
989
+ padding: 10px 14px;
990
+ background: #f8f9fa;
991
+ border-radius: 6px;
522
992
  margin-bottom: 8px;
523
993
  }
524
994
 
525
- .file-item:last-child {
526
- margin-bottom: 0;
527
- }
528
-
529
995
  .empty-folder-message {
530
996
  padding: 40px 20px;
531
997
  text-align: center;
@@ -533,29 +999,21 @@ export default {
533
999
  }
534
1000
 
535
1001
  .empty-folder-message i {
536
- font-size: 3rem;
1002
+ font-size: 2.5rem;
537
1003
  color: #dee2e6;
538
1004
  }
539
1005
 
540
1006
  .empty-folder-message p {
541
- margin: 15px 0;
1007
+ margin: 12px 0;
542
1008
  }
543
1009
 
544
- .empty-state {
545
- padding: 60px 20px;
546
- }
547
-
548
- /* Modal Styles */
1010
+ /* ── Modals ── */
549
1011
  .modal {
550
1012
  display: none;
551
1013
  position: fixed;
552
1014
  z-index: 1050;
553
- left: 0;
554
- top: 0;
555
- width: 100%;
556
- height: 100%;
557
- overflow: hidden;
558
- background-color: rgba(0,0,0,0.5);
1015
+ inset: 0;
1016
+ background: rgba(0, 0, 0, .5);
559
1017
  align-items: center;
560
1018
  justify-content: center;
561
1019
  }
@@ -566,17 +1024,23 @@ export default {
566
1024
 
567
1025
  .modal-dialog {
568
1026
  max-width: 500px;
569
- margin: 1.75rem auto;
1027
+ width: 95%;
1028
+ margin: 1.5rem auto;
1029
+ }
1030
+
1031
+ .modal-dialog.modal-lg {
1032
+ max-width: 780px;
570
1033
  }
571
1034
 
572
1035
  .modal-content {
573
- background-color: #fff;
574
- border-radius: 8px;
575
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
1036
+ background: #fff;
1037
+ border-radius: 12px;
1038
+ box-shadow: 0 10px 40px rgba(0, 0, 0, .25);
1039
+ overflow: hidden;
576
1040
  }
577
1041
 
578
1042
  .modal-header {
579
- padding: 1rem;
1043
+ padding: 18px 22px;
580
1044
  border-bottom: 1px solid #dee2e6;
581
1045
  display: flex;
582
1046
  justify-content: space-between;
@@ -584,29 +1048,402 @@ export default {
584
1048
  }
585
1049
 
586
1050
  .modal-body {
587
- padding: 1rem;
1051
+ padding: 22px;
1052
+ max-height: 70vh;
1053
+ overflow-y: auto;
588
1054
  }
589
1055
 
590
1056
  .modal-footer {
591
- padding: 1rem;
1057
+ padding: 14px 22px;
592
1058
  border-top: 1px solid #dee2e6;
593
1059
  display: flex;
594
1060
  justify-content: flex-end;
595
1061
  gap: 10px;
596
1062
  }
597
1063
 
1064
+ .rpt-modal-header {
1065
+ background: linear-gradient(135deg, #1e3c72, #2a5298);
1066
+ color: #fff;
1067
+ }
1068
+
598
1069
  .btn-close {
599
1070
  background: transparent;
600
1071
  border: none;
601
- font-size: 1.5rem;
602
- font-weight: 700;
603
- line-height: 1;
604
- color: #000;
605
- opacity: 0.5;
1072
+ font-size: 1.4rem;
606
1073
  cursor: pointer;
1074
+ opacity: .6;
607
1075
  }
608
1076
 
609
1077
  .btn-close:hover {
610
- opacity: 0.75;
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
+ }
611
1448
  }
612
1449
  </style>