aegon-dangerious 1.0.0

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.
Files changed (32) hide show
  1. package/build-report/components/AiPromptGenerateDialog.vue +349 -0
  2. package/build-report/components/CapsuleScrollbar.vue +145 -0
  3. package/build-report/components/ChapterTitleScroll.vue +292 -0
  4. package/build-report/components/PromptField.vue +314 -0
  5. package/build-report/components/RecentReportsTable.vue +408 -0
  6. package/build-report/components/ReportChapterPreview.vue +437 -0
  7. package/build-report/components/ReportChapterWorkspace.vue +923 -0
  8. package/build-report/components/ReportCollapsibleSection.vue +132 -0
  9. package/build-report/components/ReportConfigPanel.vue +609 -0
  10. package/build-report/components/ReportGenerateSettingsPanel.vue +403 -0
  11. package/build-report/components/ReportGeneratingPanel.vue +908 -0
  12. package/build-report/components/ReportHistoryList.vue +157 -0
  13. package/build-report/components/ReportHistoryPanel.vue +140 -0
  14. package/build-report/components/ReportPromptPanel.vue +445 -0
  15. package/build-report/components/ReportSectionConfirmPopover.vue +232 -0
  16. package/build-report/components/ReportSourceFileSummary.vue +544 -0
  17. package/build-report/components/ReportSourcesMoreOverlay.vue +279 -0
  18. package/build-report/components/ReportSourcesPanel.vue +1055 -0
  19. package/build-report/components/ReportTemplateDetail.vue +176 -0
  20. package/build-report/components/ReportTemplateEditorFormPanel.vue +602 -0
  21. package/build-report/components/ReportTemplatePicker.vue +802 -0
  22. package/build-report/components/ReportTemplatePromptField.vue +314 -0
  23. package/build-report/components/ReportWorkflowSidebar.vue +104 -0
  24. package/build-report/components/reportEdit.vue +334 -0
  25. package/build-report/index.vue +646 -0
  26. package/build-report/report-list.vue +407 -0
  27. package/build-report/report-template-data.ts +248 -0
  28. package/build-report/styles/report-workflow-dialog.css +33 -0
  29. package/build-report/useReportWorkflow.ts +343 -0
  30. package/build-report/utils/report-edit-route.ts +109 -0
  31. package/build-report/utils/scroll.ts +361 -0
  32. package/package.json +12 -0
@@ -0,0 +1,1055 @@
1
+ <template>
2
+ <section class="report-sources-panel" :class="{ 'is-embedded': embedded, 'is-sources-readonly': sourcesReadonly }">
3
+ <h2 v-if="!embedded" class="report-sources-panel__title">資訊源檔案</h2>
4
+
5
+ <div class="report-sources-stage-wrapper">
6
+ <div class="report-sources-stage">
7
+ <div class="report-upload-zone" role="button" tabindex="0" @click="triggerFilePick"
8
+ @keydown.enter="triggerFilePick">
9
+ <span class="report-upload-zone__plus">+</span>
10
+ <span class="report-upload-zone__label">添加資訊源</span>
11
+ <p class="report-upload-zone__hint">
12
+ (可一次多選,單個檔案上限100 MB,接受格式
13
+ txt、doc、docx、pdf、xls、xlsx、json、csv、pptx、PNG、JPG、JPEG、GIF,任何外部檔案需從可掃描病毒的方法接收,如內部電郵系統。)
14
+ </p>
15
+ <input ref="fileInputRef" type="file" class="report-upload-zone__input" multiple
16
+ accept=".txt,.doc,.docx,.pdf,.xls,.xlsx,.json,.csv,.pptx,.png,.jpg,.jpeg,.gif" @change="onFilesPicked" />
17
+ </div>
18
+
19
+ <div v-if="pendingFiles.length" class="report-pending">
20
+ <div class="report-pending__header">
21
+ <span class="report-pending__name-col">檔案名稱</span>
22
+ <span class="report-pending__size-col">大小</span>
23
+ <span class="report-pending__delete-col">刪除</span>
24
+ </div>
25
+ <div v-for="file in pendingFiles" :key="file.id" class="report-pending__row">
26
+ <span class="report-pending__name">{{ file.name }}</span>
27
+ <span class="report-pending__size" :class="{ 'is-over-limit': file.sizeKb > FILE_SIZE_LIMIT_KB }">
28
+ {{ file.sizeKb }}KB
29
+ </span>
30
+ <span class="report-pending__delete-col">
31
+ <button type="button" class="report-icon-btn report-pending__remove" aria-label="刪除"
32
+ @click="requestRemovePending(file.id, $event)">
33
+ ×
34
+ </button>
35
+ </span>
36
+ </div>
37
+ </div>
38
+ <div class="report-pending__actions report-pending__actions--center"
39
+ :class="{ 'report-pending__actions--standalone': !pendingFiles.length }">
40
+ <button type="button" class="report-btn report-btn--primary"
41
+ :disabled="!pendingFiles.length || submitLoading" @click="requestSubmitPending($event)">
42
+ 提交
43
+ </button>
44
+ <button type="button" class="report-btn report-btn--outline" :disabled="!pendingFiles.length"
45
+ @click="clearPending">
46
+ 清空
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="report-sources-library">
53
+ <div class="report-source-toolbar">
54
+ <div class="report-source-toolbar__tabs">
55
+ <button type="button" class="report-source-tab"
56
+ :class="{ 'report-source-tab--active': sourceFilterTab === 'selected' }"
57
+ @click="setSourceFilterTab('selected')">
58
+ 已選中 ({{ selectedSourceCount }})
59
+ </button>
60
+ <button type="button" class="report-source-tab"
61
+ :class="{ 'report-source-tab--active': sourceFilterTab === 'unselected' }"
62
+ @click="setSourceFilterTab('unselected')">
63
+ 未選中 ({{ unselectedSourceCount }})
64
+ </button>
65
+ </div>
66
+ <div class="report-source-toolbar__search">
67
+ <span class="report-source-toolbar__search-label">Search:</span>
68
+ <input v-model="sourceSearch" type="text" class="report-source-toolbar__search-input" />
69
+ <button type="button" class="report-btn report-btn--outline report-btn--sm" @click="refreshSources">
70
+ Refresh
71
+ </button>
72
+ </div>
73
+ </div>
74
+
75
+ <div class="report-source-table-wrap">
76
+ <div class="report-source-table__bulk">
77
+ <label class="report-source-table__select-all">
78
+ <span class="report-source-table__bulk-check">
79
+ <input v-model="allSourcesSelected" type="checkbox" :disabled="sourcesReadonly" @change="toggleAllSources" />
80
+ </span>
81
+ <span class="report-source-table__bulk-label">全選/全不選</span>
82
+ </label>
83
+ <button v-if="showMoreSourcesLink" type="button" class="report-sources-more report-source-table__bulk-more"
84
+ @click="handleShowMore">
85
+ 顯示更多 &gt;&gt;
86
+ </button>
87
+ </div>
88
+ <table class="report-source-table">
89
+ <colgroup>
90
+ <col class="report-source-table__col-check" />
91
+ <col class="report-source-table__col-name" />
92
+ <col class="report-source-table__col-action" />
93
+ <col class="report-source-table__col-action" />
94
+ <col class="report-source-table__col-action" />
95
+ </colgroup>
96
+ <thead>
97
+ <tr>
98
+ <th class="report-source-table__check" />
99
+ <th>檔案名稱</th>
100
+ <th class="report-source-table__action">預覽</th>
101
+ <th class="report-source-table__action">下載</th>
102
+ <th class="report-source-table__action">刪除</th>
103
+ </tr>
104
+ </thead>
105
+ <tbody>
106
+ <tr v-for="file in visibleSources" :key="file.id">
107
+ <td class="report-source-table__check">
108
+ <input v-model="file.selected" type="checkbox" :disabled="sourcesReadonly" />
109
+ </td>
110
+ <td class="report-source-table__name">{{ file.name }}</td>
111
+ <td class="report-source-table__action">
112
+ <button type="button" class="report-icon-btn" aria-label="預覽" @click="previewSource(file)">
113
+ <el-icon>
114
+ <View />
115
+ </el-icon>
116
+ </button>
117
+ </td>
118
+ <td class="report-source-table__action">
119
+ <button type="button" class="report-icon-btn" aria-label="下載" @click="downloadSource(file)">
120
+ <el-icon>
121
+ <Download />
122
+ </el-icon>
123
+ </button>
124
+ </td>
125
+ <td class="report-source-table__action">
126
+ <button type="button" class="report-icon-btn" aria-label="刪除"
127
+ @click="requestDeleteSource(file.id, $event)">
128
+ <el-icon>
129
+ <Close />
130
+ </el-icon>
131
+ </button>
132
+ </td>
133
+ </tr>
134
+ <tr v-if="!visibleSources.length">
135
+ <td colspan="5" class="report-source-table__empty">暫無匹配檔案</td>
136
+ </tr>
137
+ </tbody>
138
+ </table>
139
+ </div>
140
+ </div>
141
+ </section>
142
+
143
+ <ReportSectionConfirmPopover v-if="embedded" :visible="deleteConfirmOpen" :virtual-ref="deleteConfirmVirtualRef"
144
+ :boundary-el="sectionBoundaryEl ?? null" :trigger-btn="deleteConfirmTriggerBtn" :message="deleteConfirmMessage"
145
+ :instant="popoverInstant" @confirm="confirmDelete" @cancel="closeDeleteConfirm" />
146
+ <ReportSectionConfirmPopover v-if="embedded" :visible="submitConfirmOpen" :virtual-ref="submitConfirmVirtualRef"
147
+ :boundary-el="sectionBoundaryEl ?? null" :trigger-btn="submitConfirmTriggerBtn" title="確認提交"
148
+ :message="submitConfirmMessage" :instant="popoverInstant" @confirm="confirmSubmit" @cancel="closeSubmitConfirm" />
149
+ </template>
150
+
151
+ <script setup lang="ts">
152
+ import { Close, Download, View } from '@element-plus/icons-vue'
153
+ import { ElIcon, ElMessage, ElMessageBox } from 'element-plus'
154
+ import { computed, ref, watch } from 'vue'
155
+ import ReportSectionConfirmPopover from './ReportSectionConfirmPopover.vue'
156
+ import {
157
+ FILE_SIZE_LIMIT_KB,
158
+ MOCK_SOURCE_FILES,
159
+ SOURCE_VISIBLE_LIMIT,
160
+ useReportWorkflow,
161
+ type PendingFile,
162
+ type SourceFile,
163
+ type SourceFilterTab,
164
+ } from '../useReportWorkflow'
165
+
166
+ const props = withDefaults(
167
+ defineProps<{
168
+ syncWorkflow?: boolean
169
+ embedded?: boolean
170
+ sectionBoundaryEl?: HTMLElement | null
171
+ panelOpen?: boolean
172
+ sourcesReadonly?: boolean
173
+ useMoreOverlay?: boolean
174
+ }>(),
175
+ {
176
+ syncWorkflow: false,
177
+ embedded: false,
178
+ sectionBoundaryEl: null,
179
+ panelOpen: true,
180
+ sourcesReadonly: false,
181
+ useMoreOverlay: false,
182
+ },
183
+ )
184
+
185
+ const emit = defineEmits<{
186
+ submitted: []
187
+ 'show-more': []
188
+ }>()
189
+
190
+ const workflow = useReportWorkflow()
191
+
192
+ const fileInputRef = ref<HTMLInputElement | null>(null)
193
+ const sourceSearch = ref('')
194
+ const sourceFilterTab = ref<SourceFilterTab>('all')
195
+ const showAllSources = ref(false)
196
+ const allSourcesSelected = ref(false)
197
+
198
+ const deleteConfirmOpen = ref(false)
199
+ const submitConfirmOpen = ref(false)
200
+ const submitLoading = ref(false)
201
+ const popoverInstant = ref(false)
202
+ const deleteConfirmMessage = ref('')
203
+ const submitConfirmMessage = ref('')
204
+ const deleteConfirmVirtualRef = ref<HTMLElement | null>(null)
205
+ const submitConfirmVirtualRef = ref<HTMLElement | null>(null)
206
+ const deleteConfirmTriggerBtn = ref<HTMLElement | null>(null)
207
+ const submitConfirmTriggerBtn = ref<HTMLElement | null>(null)
208
+ const deleteTarget = ref<{ type: 'source' | 'pending'; id: string } | null>(null)
209
+
210
+ const localPendingFiles = ref<PendingFile[]>([])
211
+ const localSourceFiles = ref<SourceFile[]>([...MOCK_SOURCE_FILES])
212
+
213
+ const pendingFiles = computed({
214
+ get: () => (props.syncWorkflow ? workflow.pendingFiles.value : localPendingFiles.value),
215
+ set: (value) => {
216
+ if (props.syncWorkflow) workflow.pendingFiles.value = value
217
+ else localPendingFiles.value = value
218
+ },
219
+ })
220
+
221
+ const sourceFiles = computed({
222
+ get: () => (props.syncWorkflow ? workflow.sourceFiles.value : localSourceFiles.value),
223
+ set: (value) => {
224
+ if (props.syncWorkflow) workflow.sourceFiles.value = value
225
+ else localSourceFiles.value = value
226
+ },
227
+ })
228
+
229
+ const selectedSourceCount = computed(() => sourceFiles.value.filter((f) => f.selected).length)
230
+ const unselectedSourceCount = computed(() => sourceFiles.value.filter((f) => !f.selected).length)
231
+
232
+ const tabFilteredSources = computed(() => {
233
+ if (sourceFilterTab.value === 'selected') {
234
+ return sourceFiles.value.filter((file) => file.selected)
235
+ }
236
+ if (sourceFilterTab.value === 'unselected') {
237
+ return sourceFiles.value.filter((file) => !file.selected)
238
+ }
239
+ return sourceFiles.value
240
+ })
241
+
242
+ const filteredSources = computed(() => {
243
+ const q = sourceSearch.value.trim().toLowerCase()
244
+ const base = tabFilteredSources.value
245
+ if (!q) return base
246
+ return base.filter((file) => file.name.toLowerCase().includes(q))
247
+ })
248
+
249
+ const visibleSources = computed(() => {
250
+ if (showAllSources.value) return filteredSources.value
251
+ return filteredSources.value.slice(0, SOURCE_VISIBLE_LIMIT)
252
+ })
253
+
254
+ const showMoreSourcesLink = computed(
255
+ () => !showAllSources.value && filteredSources.value.length > SOURCE_VISIBLE_LIMIT,
256
+ )
257
+
258
+ function handleShowMore() {
259
+ if (props.useMoreOverlay) {
260
+ emit('show-more')
261
+ return
262
+ }
263
+ showAllSources.value = true
264
+ }
265
+
266
+ watch(sourceFilterTab, () => {
267
+ showAllSources.value = false
268
+ const list = visibleSources.value
269
+ allSourcesSelected.value = list.length > 0 && list.every((f) => f.selected)
270
+ })
271
+
272
+ watch(
273
+ () => visibleSources.value.map((file) => file.selected),
274
+ () => {
275
+ const list = visibleSources.value
276
+ allSourcesSelected.value = list.length > 0 && list.every((file) => file.selected)
277
+ },
278
+ )
279
+
280
+ function triggerFilePick() {
281
+ if (props.sourcesReadonly) return
282
+ fileInputRef.value?.click()
283
+ }
284
+
285
+ function onFilesPicked(event: Event) {
286
+ if (props.sourcesReadonly) return
287
+ const input = event.target as HTMLInputElement
288
+ const files = input.files
289
+ if (!files?.length) return
290
+
291
+ for (const file of files) {
292
+ pendingFiles.value = [
293
+ ...pendingFiles.value,
294
+ {
295
+ id: `pending-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
296
+ name: file.name,
297
+ sizeKb: Math.ceil(file.size / 1024),
298
+ },
299
+ ]
300
+ }
301
+ input.value = ''
302
+ }
303
+
304
+ function removePending(id: string) {
305
+ pendingFiles.value = pendingFiles.value.filter((f) => f.id !== id)
306
+ }
307
+
308
+ function resolveDeletePopoverAnchors(event: MouseEvent) {
309
+ const btn = event.currentTarget as HTMLElement
310
+ const row = btn.closest('tr, .report-pending__row') as HTMLElement | null
311
+ return {
312
+ virtualRef: row ?? btn,
313
+ triggerBtn: btn,
314
+ }
315
+ }
316
+
317
+ function requestRemovePending(id: string, event: MouseEvent) {
318
+ if (props.sourcesReadonly) return
319
+ if (!props.embedded || !props.sectionBoundaryEl) {
320
+ removePending(id)
321
+ return
322
+ }
323
+ const file = pendingFiles.value.find((item) => item.id === id)
324
+ if (!file) return
325
+ const anchors = resolveDeletePopoverAnchors(event)
326
+ deleteConfirmVirtualRef.value = anchors.virtualRef
327
+ deleteConfirmTriggerBtn.value = anchors.triggerBtn
328
+ deleteTarget.value = { type: 'pending', id }
329
+ deleteConfirmMessage.value = `確定刪除「${file.name}」?`
330
+ deleteConfirmOpen.value = true
331
+ }
332
+
333
+ function clearPending() {
334
+ if (props.sourcesReadonly) return
335
+ if (!pendingFiles.value.length) return
336
+
337
+ if (props.syncWorkflow) {
338
+ workflow.pendingFiles.value = []
339
+ } else {
340
+ localPendingFiles.value = []
341
+ }
342
+
343
+ if (fileInputRef.value) {
344
+ fileInputRef.value.value = ''
345
+ }
346
+ }
347
+
348
+ function hasOverLimitPendingFiles() {
349
+ return pendingFiles.value.some((file) => file.sizeKb > FILE_SIZE_LIMIT_KB)
350
+ }
351
+
352
+ function getSubmitConfirmMessage() {
353
+ return hasOverLimitPendingFiles()
354
+ ? '是否跳過超出限制的檔案,上傳列表中的檔案?'
355
+ : '是否提交?'
356
+ }
357
+
358
+ function requestSubmitPending(event: MouseEvent) {
359
+ if (props.sourcesReadonly) return
360
+ if (!pendingFiles.value.length || submitLoading.value) return
361
+
362
+ const message = getSubmitConfirmMessage()
363
+ if (props.embedded && props.sectionBoundaryEl) {
364
+ const btn = event.currentTarget as HTMLElement
365
+ submitConfirmVirtualRef.value = btn
366
+ submitConfirmTriggerBtn.value = btn
367
+ submitConfirmMessage.value = message
368
+ submitConfirmOpen.value = true
369
+ return
370
+ }
371
+
372
+ void ElMessageBox.confirm(message, '確認提交', {
373
+ confirmButtonText: '確認',
374
+ cancelButtonText: '取消',
375
+ type: 'warning',
376
+ })
377
+ .then(() => performSubmit())
378
+ .catch(() => undefined)
379
+ }
380
+
381
+ function confirmSubmit() {
382
+ closeSubmitConfirm()
383
+ void performSubmit()
384
+ }
385
+
386
+ function closeSubmitConfirm() {
387
+ submitConfirmOpen.value = false
388
+ }
389
+
390
+ async function uploadPendingFiles(files: PendingFile[]) {
391
+ void files
392
+ await new Promise<void>((resolve) => {
393
+ window.setTimeout(resolve, 300)
394
+ })
395
+ }
396
+
397
+ async function performSubmit() {
398
+ if (!pendingFiles.value.length || submitLoading.value) return
399
+
400
+ const valid = pendingFiles.value.filter((file) => file.sizeKb <= FILE_SIZE_LIMIT_KB)
401
+ submitLoading.value = true
402
+ try {
403
+ await uploadPendingFiles(valid)
404
+ valid.forEach((file) => {
405
+ sourceFiles.value.push({ id: file.id, name: file.name, selected: false })
406
+ })
407
+ pendingFiles.value = pendingFiles.value.filter((file) => file.sizeKb > FILE_SIZE_LIMIT_KB)
408
+ ElMessage.success('提交成功')
409
+ emit('submitted')
410
+ } catch {
411
+ ElMessage.warning('請稍後重試')
412
+ } finally {
413
+ submitLoading.value = false
414
+ }
415
+ }
416
+
417
+ function setSourceFilterTab(tab: 'selected' | 'unselected') {
418
+ sourceFilterTab.value = sourceFilterTab.value === tab ? 'all' : tab
419
+ }
420
+
421
+ function toggleAllSources() {
422
+ if (props.sourcesReadonly) return
423
+ const next = allSourcesSelected.value
424
+ visibleSources.value.forEach((file) => {
425
+ file.selected = next
426
+ })
427
+ }
428
+
429
+ function refreshSources() {
430
+ sourceSearch.value = ''
431
+ sourceFilterTab.value = 'all'
432
+ showAllSources.value = false
433
+ }
434
+
435
+ function previewSource(file: SourceFile) {
436
+ console.log('preview', file.name)
437
+ }
438
+
439
+ function downloadSource(file: SourceFile) {
440
+ console.log('download', file.name)
441
+ }
442
+
443
+ function deleteSource(id: string) {
444
+ sourceFiles.value = sourceFiles.value.filter((f) => f.id !== id)
445
+ }
446
+
447
+ function requestDeleteSource(id: string, event: MouseEvent) {
448
+ if (props.sourcesReadonly) return
449
+ if (!props.embedded || !props.sectionBoundaryEl) {
450
+ deleteSource(id)
451
+ return
452
+ }
453
+ const file = sourceFiles.value.find((item) => item.id === id)
454
+ if (!file) return
455
+ const anchors = resolveDeletePopoverAnchors(event)
456
+ deleteConfirmVirtualRef.value = anchors.virtualRef
457
+ deleteConfirmTriggerBtn.value = anchors.triggerBtn
458
+ deleteTarget.value = { type: 'source', id }
459
+ deleteConfirmMessage.value = `確定刪除「${file.name}」?`
460
+ deleteConfirmOpen.value = true
461
+ }
462
+
463
+ function confirmDelete() {
464
+ if (!deleteTarget.value) {
465
+ closeDeleteConfirm()
466
+ return
467
+ }
468
+ if (deleteTarget.value.type === 'source') {
469
+ deleteSource(deleteTarget.value.id)
470
+ } else {
471
+ removePending(deleteTarget.value.id)
472
+ }
473
+ closeDeleteConfirm()
474
+ }
475
+
476
+ function closeDeleteConfirm() {
477
+ deleteConfirmOpen.value = false
478
+ deleteTarget.value = null
479
+ }
480
+
481
+ function hasOpenOverlays() {
482
+ return deleteConfirmOpen.value || submitConfirmOpen.value
483
+ }
484
+
485
+ function closeAllOverlays(instant = false) {
486
+ if (!deleteConfirmOpen.value && !submitConfirmOpen.value) return
487
+ if (instant) popoverInstant.value = true
488
+ closeDeleteConfirm()
489
+ closeSubmitConfirm()
490
+ if (instant) {
491
+ window.setTimeout(() => {
492
+ popoverInstant.value = false
493
+ }, 320)
494
+ }
495
+ }
496
+
497
+ watch(
498
+ () => props.panelOpen,
499
+ (open) => {
500
+ if (!open) closeAllOverlays()
501
+ },
502
+ )
503
+
504
+ defineExpose({
505
+ sourceFiles: props.syncWorkflow ? workflow.sourceFiles : localSourceFiles,
506
+ pendingFiles: props.syncWorkflow ? workflow.pendingFiles : localPendingFiles,
507
+ hasOpenOverlays,
508
+ closeAllOverlays,
509
+ })
510
+ </script>
511
+
512
+ <style scoped>
513
+ .report-sources-panel {
514
+ display: flex;
515
+ flex-direction: column;
516
+ height: 100%;
517
+ padding: 20px 24px 24px;
518
+ min-width: 0;
519
+ border: 1px solid #ebeef5;
520
+ border-radius: 8px;
521
+ background: #fff;
522
+ box-sizing: border-box;
523
+ --report-source-check-col: 36px;
524
+ --report-source-action-col: 56px;
525
+ --report-source-grid-cols: var(--report-source-check-col) 1fr repeat(3, var(--report-source-action-col));
526
+ --report-source-row-h: 36px;
527
+ }
528
+
529
+ .report-sources-panel__title {
530
+ margin: 0 0 16px;
531
+ font-size: 15px;
532
+ font-weight: 700;
533
+ color: #1a1a1a;
534
+ flex-shrink: 0;
535
+ }
536
+
537
+ .report-sources-panel.is-embedded {
538
+ display: flex;
539
+ flex-direction: column;
540
+ flex: 1 1 auto;
541
+ height: 100%;
542
+ min-height: 0;
543
+ padding: 0;
544
+ border: 0;
545
+ border-radius: 0;
546
+ background: transparent;
547
+ }
548
+
549
+ .report-sources-panel.is-embedded.is-section-collapsed {
550
+ flex: 0 0 auto;
551
+ }
552
+
553
+ .report-sources-panel.is-embedded .report-sources-library {
554
+ flex: 0 0 auto;
555
+ overflow: visible;
556
+ }
557
+
558
+ .report-sources-panel.is-embedded .report-source-toolbar__search-input {
559
+ min-width: 0;
560
+ }
561
+
562
+ .report-sources-panel.is-embedded .report-source-table-wrap {
563
+ overflow-x: auto;
564
+ }
565
+
566
+ .report-sources-stage-wrapper {
567
+ flex-shrink: 0;
568
+ box-sizing: border-box;
569
+ padding: 5px;
570
+ border: 1px solid #ebeef5;
571
+ border-radius: 8px 8px 0 0;
572
+ }
573
+
574
+ .report-sources-panel.is-sources-readonly .report-upload-zone,
575
+ .report-sources-panel.is-sources-readonly .report-pending__actions,
576
+ .report-sources-panel.is-sources-readonly .report-pending__remove,
577
+ .report-sources-panel.is-sources-readonly .report-source-table__action .report-icon-btn {
578
+ opacity: 0.55;
579
+ pointer-events: none;
580
+ cursor: not-allowed;
581
+ }
582
+
583
+ .report-sources-stage {
584
+ flex-shrink: 0;
585
+ border-bottom-left-radius: 8px;
586
+ border-bottom-right-radius: 8px;
587
+ overflow: hidden;
588
+ }
589
+
590
+ .report-sources-library {
591
+ display: flex;
592
+ flex: 1 1 auto;
593
+ flex-direction: column;
594
+ box-sizing: border-box;
595
+ padding: 5px;
596
+ border: 1px solid #ebeef5;
597
+ border-top: 0;
598
+ border-radius: 0 0 8px 8px;
599
+ overflow: hidden;
600
+ }
601
+
602
+ .report-sources-more {
603
+ padding: 0;
604
+ border: 0;
605
+ background: none;
606
+ font-size: 13px;
607
+ color: #409eff;
608
+ text-decoration: none;
609
+ cursor: pointer;
610
+ }
611
+
612
+ .report-sources-more:hover {
613
+ color: #66b1ff;
614
+ }
615
+
616
+ .report-upload-zone {
617
+ position: relative;
618
+ display: flex;
619
+ flex-direction: column;
620
+ align-items: center;
621
+ justify-content: center;
622
+ gap: 6px;
623
+ min-height: 120px;
624
+ padding: 16px;
625
+ border: 1px dashed #c9cdd4;
626
+ border-radius: 8px;
627
+ background: #fafafa;
628
+ cursor: pointer;
629
+ text-align: center;
630
+ transition: background 0.2s ease, border-color 0.2s ease;
631
+ }
632
+
633
+ .report-upload-zone:hover {
634
+ background: #f5f5f5;
635
+ border-color: #c53355;
636
+ }
637
+
638
+ .report-upload-zone__plus {
639
+ font-size: 28px;
640
+ line-height: 1;
641
+ color: #86909c;
642
+ font-weight: 300;
643
+ margin-top: -20px;
644
+ }
645
+
646
+ .report-upload-zone__label {
647
+ font-size: 13px;
648
+ color: #4e5969;
649
+ }
650
+
651
+ .report-upload-zone__hint {
652
+ margin: 4px 0 0;
653
+ max-width: 100%;
654
+ font-size: 11px;
655
+ line-height: 1.5;
656
+ color: #86909c;
657
+ }
658
+
659
+ .report-upload-zone__input {
660
+ position: absolute;
661
+ width: 0;
662
+ height: 0;
663
+ opacity: 0;
664
+ pointer-events: none;
665
+ }
666
+
667
+ .report-pending {
668
+ margin-top: 5px;
669
+ box-sizing: border-box;
670
+ }
671
+
672
+ .report-pending__header,
673
+ .report-pending__row {
674
+ display: grid;
675
+ grid-template-columns: var(--report-source-grid-cols);
676
+ align-items: center;
677
+ height: var(--report-source-row-h);
678
+ font-size: 13px;
679
+ }
680
+
681
+ .report-pending__row {
682
+ border-radius: 4px;
683
+ transition: background-color 0.2s ease;
684
+ }
685
+
686
+ .report-pending__row:hover {
687
+ background-color: #f5f7fa;
688
+ }
689
+
690
+ .report-pending__header {
691
+ background: #fafafa;
692
+ font-weight: 600;
693
+ color: #1a1a1a;
694
+ }
695
+
696
+ .report-pending__name-col,
697
+ .report-pending__name {
698
+ grid-column: 1 / 4;
699
+ padding: 0 10px;
700
+ min-width: 0;
701
+ height: var(--report-source-row-h);
702
+ display: flex;
703
+ align-items: center;
704
+ }
705
+
706
+ .report-pending__size-col,
707
+ .report-pending__size {
708
+ grid-column: 4;
709
+ padding: 0 10px;
710
+ height: var(--report-source-row-h);
711
+ display: flex;
712
+ align-items: center;
713
+ justify-content: flex-end;
714
+ text-align: right;
715
+ box-sizing: border-box;
716
+ }
717
+
718
+ .report-pending__delete-col {
719
+ grid-column: 5;
720
+ padding: 0 10px;
721
+ height: var(--report-source-row-h);
722
+ display: flex;
723
+ align-items: center;
724
+ justify-content: flex-end;
725
+ text-align: right;
726
+ box-sizing: border-box;
727
+ }
728
+
729
+ .report-pending__name {
730
+ overflow: hidden;
731
+ text-overflow: ellipsis;
732
+ white-space: nowrap;
733
+ }
734
+
735
+ .report-pending__size {
736
+ color: #4e5969;
737
+ white-space: nowrap;
738
+ }
739
+
740
+ .report-pending__size.is-over-limit {
741
+ color: #c53355;
742
+ font-weight: 600;
743
+ }
744
+
745
+ .report-pending__remove {
746
+ border: 0;
747
+ background: none;
748
+ font-size: 18px;
749
+ line-height: 1;
750
+ color: #86909c;
751
+ cursor: pointer;
752
+ flex-shrink: 0;
753
+ }
754
+
755
+ .report-pending__remove:hover {
756
+ color: #c53355;
757
+ }
758
+
759
+ .report-pending__actions {
760
+ display: flex;
761
+ gap: 10px;
762
+ padding: 10px;
763
+ }
764
+
765
+ .report-pending__actions--center {
766
+ justify-content: center;
767
+ }
768
+
769
+ .report-btn {
770
+ min-width: 72px;
771
+ padding: 6px 16px;
772
+ border-radius: 3px;
773
+ font-size: 13px;
774
+ font-weight: 600;
775
+ cursor: pointer;
776
+ transition: all 0.2s ease;
777
+ }
778
+
779
+ .report-btn--primary {
780
+ border: 1px solid #c53355;
781
+ background: #c53355;
782
+ color: #fff;
783
+ }
784
+
785
+ .report-btn--primary:hover {
786
+ background: #a82b48;
787
+ border-color: #a82b48;
788
+ }
789
+
790
+ .report-btn--primary:disabled,
791
+ .report-btn--outline:disabled {
792
+ opacity: 0.45;
793
+ cursor: not-allowed;
794
+ }
795
+
796
+ .report-btn--primary:disabled:hover {
797
+ background: #c53355;
798
+ border-color: #c53355;
799
+ }
800
+
801
+ .report-btn--outline:disabled:hover {
802
+ background: #fff;
803
+ }
804
+
805
+ .report-pending__actions--standalone {
806
+ margin-top: 8px;
807
+ }
808
+
809
+ .report-btn--outline {
810
+ border: 1px solid #c53355;
811
+ background: #fff;
812
+ color: #c53355;
813
+ }
814
+
815
+ .report-btn--outline:hover {
816
+ background: #fff5f6;
817
+ }
818
+
819
+ .report-btn--sm {
820
+ min-width: auto;
821
+ padding: 4px 12px;
822
+ font-size: 12px;
823
+ margin-right: -16px;
824
+ }
825
+
826
+ .report-source-toolbar {
827
+ display: flex;
828
+ align-items: center;
829
+ justify-content: space-between;
830
+ flex-wrap: wrap;
831
+ gap: 10px;
832
+ margin: 8px 0;
833
+ flex-shrink: 0;
834
+ }
835
+
836
+ .report-source-toolbar__tabs {
837
+ display: inline-flex;
838
+ align-items: center;
839
+ gap: 8px;
840
+ flex-wrap: wrap;
841
+ }
842
+
843
+ .report-source-tab {
844
+ min-width: 88px;
845
+ padding: 4px 12px;
846
+ border: 1px solid #e8eaec;
847
+ border-radius: 2px;
848
+ background: #fff;
849
+ font-size: 13px;
850
+ color: #4e5969;
851
+ cursor: pointer;
852
+ transition: all 0.2s ease;
853
+ }
854
+
855
+ .report-source-tab--active {
856
+ border-color: #c53355;
857
+ color: #c53355;
858
+ background: #fff5f6;
859
+ }
860
+
861
+ .report-source-toolbar__search {
862
+ display: flex;
863
+ align-items: center;
864
+ gap: 8px;
865
+ flex-wrap: wrap;
866
+ }
867
+
868
+ .report-source-toolbar__search-label {
869
+ font-size: 13px;
870
+ color: #4e5969;
871
+ }
872
+
873
+ .report-source-toolbar__search-input {
874
+ width: min(140px, 100%);
875
+ height: 28px;
876
+ padding: 2px 8px;
877
+ border: 1px solid #c9cdd4;
878
+ border-radius: 2px;
879
+ font-size: 13px;
880
+ box-sizing: border-box;
881
+ }
882
+
883
+ .report-source-table-wrap {
884
+ overflow: visible;
885
+ }
886
+
887
+ .report-source-table__bulk {
888
+ display: grid;
889
+ grid-template-columns: var(--report-source-grid-cols);
890
+ align-items: center;
891
+ padding: 8px 0 6px;
892
+ }
893
+
894
+ .report-source-table {
895
+ width: 100%;
896
+ table-layout: fixed;
897
+ border-collapse: separate;
898
+ border-spacing: 0;
899
+ font-size: 13px;
900
+ }
901
+
902
+ .report-source-table__col-check {
903
+ width: var(--report-source-check-col);
904
+ }
905
+
906
+ .report-source-table__col-action {
907
+ width: var(--report-source-action-col);
908
+ }
909
+
910
+ .report-source-table th,
911
+ .report-source-table td {
912
+ height: var(--report-source-row-h);
913
+ padding: 0 10px;
914
+ text-align: left;
915
+ background: #fff;
916
+ vertical-align: middle;
917
+ box-sizing: border-box;
918
+ transition: background-color 0.2s ease;
919
+ }
920
+
921
+ .report-source-table th.report-source-table__action,
922
+ .report-source-table td.report-source-table__action {
923
+ width: var(--report-source-action-col);
924
+ padding: 0 10px;
925
+ text-align: right;
926
+ vertical-align: middle;
927
+ }
928
+
929
+ .report-source-table td.report-source-table__action .report-icon-btn {
930
+ vertical-align: middle;
931
+ }
932
+
933
+ .report-source-table tbody tr:hover>td {
934
+ background-color: #f5f7fa;
935
+ }
936
+
937
+ .report-source-table tbody tr:hover>td:first-child {
938
+ border-radius: 4px 0 0 4px;
939
+ }
940
+
941
+ .report-source-table tbody tr:hover>td:last-child {
942
+ border-radius: 0 4px 4px 0;
943
+ }
944
+
945
+ .report-source-table th {
946
+ background: #fafafa;
947
+ font-weight: 600;
948
+ color: #1a1a1a;
949
+ }
950
+
951
+ .report-source-table__check {
952
+ width: var(--report-source-check-col);
953
+ padding-left: 10px;
954
+ padding-right: 0;
955
+ text-align: left;
956
+ vertical-align: middle;
957
+ }
958
+
959
+ .report-source-table__select-all {
960
+ display: contents;
961
+ cursor: pointer;
962
+ }
963
+
964
+ .report-source-table__bulk-check {
965
+ display: flex;
966
+ align-items: center;
967
+ padding-left: 10px;
968
+ }
969
+
970
+ .report-source-table__bulk-label {
971
+ padding: 0 10px;
972
+ font-size: 13px;
973
+ font-weight: 600;
974
+ color: #1a1a1a;
975
+ white-space: nowrap;
976
+ }
977
+
978
+ .report-source-table__bulk-more {
979
+ grid-column: 3 / -1;
980
+ justify-self: end;
981
+ align-self: center;
982
+ padding-right: 10px;
983
+ }
984
+
985
+ .report-source-table__check input {
986
+ accent-color: #c53355;
987
+ }
988
+
989
+ .report-source-table__name {
990
+ overflow: hidden;
991
+ text-overflow: ellipsis;
992
+ white-space: nowrap;
993
+ }
994
+
995
+ .report-source-table__empty {
996
+ text-align: center;
997
+ color: #86909c;
998
+ height: auto;
999
+ padding: 24px;
1000
+ }
1001
+
1002
+ @media (max-width: 960px) {
1003
+ .report-sources-panel {
1004
+ height: auto;
1005
+ }
1006
+
1007
+ .report-source-toolbar {
1008
+ flex-direction: column;
1009
+ align-items: stretch;
1010
+ }
1011
+
1012
+ .report-source-toolbar__search {
1013
+ width: 100%;
1014
+ }
1015
+
1016
+ .report-source-toolbar__search-input {
1017
+ flex: 1;
1018
+ width: auto;
1019
+ min-width: 0;
1020
+ }
1021
+
1022
+ .report-source-table__bulk {
1023
+ grid-template-columns: var(--report-source-check-col) 1fr;
1024
+ gap: 6px;
1025
+ }
1026
+
1027
+ .report-source-table__bulk-more {
1028
+ grid-column: 1 / -1;
1029
+ justify-self: end;
1030
+ padding-right: 10px;
1031
+ }
1032
+ }
1033
+ </style>
1034
+
1035
+ <style>
1036
+ .report-icon-btn {
1037
+ display: inline-flex;
1038
+ align-items: center;
1039
+ justify-content: center;
1040
+ width: 24px;
1041
+ height: 24px;
1042
+ padding: 0;
1043
+ border: 0;
1044
+ background: none;
1045
+ color: #4e5969;
1046
+ cursor: pointer;
1047
+ border-radius: 4px;
1048
+ vertical-align: middle;
1049
+ }
1050
+
1051
+ .report-icon-btn:hover {
1052
+ color: #c53355;
1053
+ background: #fff5f6;
1054
+ }
1055
+ </style>