@conduction/nextcloud-vue 0.1.0-beta.6 → 0.1.0-beta.7
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.
- package/dist/nextcloud-vue.cjs.js +13606 -1918
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.css +1238 -270
- package/dist/nextcloud-vue.esm.js +13548 -1880
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +9 -4
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +1 -11
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +5 -1
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +1 -1
- package/src/components/CnCard/CnCard.vue +415 -0
- package/src/components/CnCard/index.js +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +20 -20
- package/src/components/CnChartWidget/CnChartWidget.vue +3 -1
- package/src/components/CnCopyDialog/CnCopyDialog.vue +7 -1
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +4 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +6 -2
- package/src/components/CnDeleteDialog/CnDeleteDialog.vue +7 -1
- package/src/components/CnDetailCard/CnDetailCard.vue +12 -1
- package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
- package/src/components/CnDetailGrid/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +157 -11
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +934 -920
- package/src/components/CnIcon/CnIcon.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +51 -9
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +37 -9
- package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
- package/src/components/CnInfoWidget/index.js +1 -0
- package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
- package/src/components/CnJsonViewer/index.js +1 -0
- package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +7 -1
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +7 -1
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +1 -1
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +1 -1
- package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
- package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +45 -668
- package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
- package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
- package/src/components/CnObjectSidebar/index.js +5 -0
- package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
- package/src/components/CnProgressBar/index.js +1 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +1 -1
- package/src/components/CnStatsBlock/CnStatsBlock.vue +27 -11
- package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
- package/src/components/CnStatsPanel/index.js +1 -0
- package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +5 -1
- package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
- package/src/components/CnTableWidget/index.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +36 -1
- package/src/components/index.js +11 -0
- package/src/composables/useDashboardView.js +58 -12
- package/src/composables/useDetailView.js +3 -2
- package/src/composables/useListView.js +7 -6
- package/src/composables/useSubResource.js +3 -3
- package/src/css/badge.css +32 -0
- package/src/css/card.css +1 -0
- package/src/css/detail-page.css +74 -7
- package/src/index.js +16 -0
- package/src/mixins/gridLayout.js +118 -0
- package/src/store/createCrudStore.js +360 -0
- package/src/store/createSubResourcePlugin.js +5 -15
- package/src/store/index.js +1 -0
- package/src/store/plugins/auditTrails.js +346 -6
- package/src/store/plugins/lifecycle.js +4 -4
- package/src/store/plugins/registerMapping.js +18 -8
- package/src/store/plugins/relations.js +1 -1
- package/src/store/plugins/search.js +21 -8
- package/src/store/useObjectStore.js +30 -36
- package/src/utils/getTheme.js +9 -0
- package/src/utils/headers.js +13 -3
- package/src/utils/index.js +1 -0
- package/src/utils/schema.js +3 -3
- package/src/utils/widgetVisibility.js +162 -0
- package/src/components/CnObjectCard/eslint-setup.md +0 -235
- package/src/components/CnObjectCard/package.json-or.json +0 -132
|
@@ -28,7 +28,7 @@ const _registry = {
|
|
|
28
28
|
* Each key must be the PascalCase icon name matching the
|
|
29
29
|
* vue-material-design-icons file name (e.g. "Sword" for Sword.vue).
|
|
30
30
|
*
|
|
31
|
-
* @param {Record<string, import('vue').Component>} icons
|
|
31
|
+
* @param {Record<string, import('vue').Component>} icons - Map of PascalCase icon names to Vue components
|
|
32
32
|
*
|
|
33
33
|
* @example
|
|
34
34
|
* import { registerIcons } from '@conduction/nextcloud-vue'
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
:view-mode="currentViewMode"
|
|
29
29
|
:show-view-toggle="showViewToggle"
|
|
30
30
|
:refreshing="refreshing"
|
|
31
|
+
:show-add="showAdd"
|
|
31
32
|
@add="onAddClick"
|
|
32
33
|
@refresh="$emit('refresh')"
|
|
33
34
|
@show-import="showImportDialog = true"
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
ref="massDeleteDialog"
|
|
53
54
|
:items="selectedObjects"
|
|
54
55
|
:name-field="massActionNameField"
|
|
56
|
+
:name-formatter="nameFormatter"
|
|
55
57
|
@confirm="onMassDeleteConfirm"
|
|
56
58
|
@close="showMassDeleteDialog = false" />
|
|
57
59
|
|
|
@@ -61,6 +63,7 @@
|
|
|
61
63
|
ref="massCopyDialog"
|
|
62
64
|
:items="selectedObjects"
|
|
63
65
|
:name-field="massActionNameField"
|
|
66
|
+
:name-formatter="nameFormatter"
|
|
64
67
|
@confirm="onMassCopyConfirm"
|
|
65
68
|
@close="showMassCopyDialog = false" />
|
|
66
69
|
|
|
@@ -94,6 +97,7 @@
|
|
|
94
97
|
ref="singleDeleteDialog"
|
|
95
98
|
:item="actionTargetItem"
|
|
96
99
|
:name-field="massActionNameField"
|
|
100
|
+
:name-formatter="nameFormatter"
|
|
97
101
|
@confirm="onSingleDeleteConfirm"
|
|
98
102
|
@close="closeSingleDelete" />
|
|
99
103
|
</slot>
|
|
@@ -108,6 +112,7 @@
|
|
|
108
112
|
ref="singleCopyDialog"
|
|
109
113
|
:item="actionTargetItem"
|
|
110
114
|
:name-field="massActionNameField"
|
|
115
|
+
:name-formatter="nameFormatter"
|
|
111
116
|
@confirm="onSingleCopyConfirm"
|
|
112
117
|
@close="closeSingleCopy" />
|
|
113
118
|
</slot>
|
|
@@ -500,6 +505,11 @@ export default {
|
|
|
500
505
|
type: String,
|
|
501
506
|
default: 'title',
|
|
502
507
|
},
|
|
508
|
+
/** Optional function to format item names in dialogs. Receives the item, returns a string. Overrides massActionNameField when provided. */
|
|
509
|
+
nameFormatter: {
|
|
510
|
+
type: Function,
|
|
511
|
+
default: null,
|
|
512
|
+
},
|
|
503
513
|
/** Available export formats for the export dialog */
|
|
504
514
|
exportFormats: {
|
|
505
515
|
type: Array,
|
|
@@ -563,6 +573,11 @@ export default {
|
|
|
563
573
|
type: Boolean,
|
|
564
574
|
default: false,
|
|
565
575
|
},
|
|
576
|
+
/** Whether to show the Add button in the actions bar */
|
|
577
|
+
showAdd: {
|
|
578
|
+
type: Boolean,
|
|
579
|
+
default: true,
|
|
580
|
+
},
|
|
566
581
|
/**
|
|
567
582
|
* Store instance for automatic save integration. When provided alongside
|
|
568
583
|
* objectType, the form dialog saves directly to the store instead of
|
|
@@ -751,28 +766,40 @@ export default {
|
|
|
751
766
|
this.$emit('mass-import', payload)
|
|
752
767
|
},
|
|
753
768
|
|
|
754
|
-
/**
|
|
769
|
+
/**
|
|
770
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
771
|
+
* @public
|
|
772
|
+
*/
|
|
755
773
|
setMassDeleteResult(resultData) {
|
|
756
774
|
if (this.$refs.massDeleteDialog) {
|
|
757
775
|
this.$refs.massDeleteDialog.setResult(resultData)
|
|
758
776
|
}
|
|
759
777
|
},
|
|
760
778
|
|
|
761
|
-
/**
|
|
779
|
+
/**
|
|
780
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
781
|
+
* @public
|
|
782
|
+
*/
|
|
762
783
|
setMassCopyResult(resultData) {
|
|
763
784
|
if (this.$refs.massCopyDialog) {
|
|
764
785
|
this.$refs.massCopyDialog.setResult(resultData)
|
|
765
786
|
}
|
|
766
787
|
},
|
|
767
788
|
|
|
768
|
-
/**
|
|
789
|
+
/**
|
|
790
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
791
|
+
* @public
|
|
792
|
+
*/
|
|
769
793
|
setExportResult(resultData) {
|
|
770
794
|
if (this.$refs.exportDialog) {
|
|
771
795
|
this.$refs.exportDialog.setResult(resultData)
|
|
772
796
|
}
|
|
773
797
|
},
|
|
774
798
|
|
|
775
|
-
/**
|
|
799
|
+
/**
|
|
800
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
801
|
+
* @public
|
|
802
|
+
*/
|
|
776
803
|
setImportResult(resultData) {
|
|
777
804
|
if (this.$refs.importDialog) {
|
|
778
805
|
this.$refs.importDialog.setResult(resultData)
|
|
@@ -780,11 +807,17 @@ export default {
|
|
|
780
807
|
},
|
|
781
808
|
|
|
782
809
|
// --- Backward-compatible aliases ---
|
|
783
|
-
/**
|
|
810
|
+
/**
|
|
811
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
812
|
+
* @public
|
|
813
|
+
*/
|
|
784
814
|
setDeleteResult(resultData) {
|
|
785
815
|
this.setMassDeleteResult(resultData)
|
|
786
816
|
},
|
|
787
|
-
/**
|
|
817
|
+
/**
|
|
818
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
819
|
+
* @public
|
|
820
|
+
*/
|
|
788
821
|
setCopyResult(resultData) {
|
|
789
822
|
this.setMassCopyResult(resultData)
|
|
790
823
|
},
|
|
@@ -837,21 +870,30 @@ export default {
|
|
|
837
870
|
this.editItem = null
|
|
838
871
|
},
|
|
839
872
|
|
|
840
|
-
/**
|
|
873
|
+
/**
|
|
874
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
875
|
+
* @public
|
|
876
|
+
*/
|
|
841
877
|
setSingleDeleteResult(resultData) {
|
|
842
878
|
if (this.$refs.singleDeleteDialog) {
|
|
843
879
|
this.$refs.singleDeleteDialog.setResult(resultData)
|
|
844
880
|
}
|
|
845
881
|
},
|
|
846
882
|
|
|
847
|
-
/**
|
|
883
|
+
/**
|
|
884
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
885
|
+
* @public
|
|
886
|
+
*/
|
|
848
887
|
setSingleCopyResult(resultData) {
|
|
849
888
|
if (this.$refs.singleCopyDialog) {
|
|
850
889
|
this.$refs.singleCopyDialog.setResult(resultData)
|
|
851
890
|
}
|
|
852
891
|
},
|
|
853
892
|
|
|
854
|
-
/**
|
|
893
|
+
/**
|
|
894
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
895
|
+
* @public
|
|
896
|
+
*/
|
|
855
897
|
setFormResult(resultData) {
|
|
856
898
|
if (this.$refs.formDialog) {
|
|
857
899
|
this.$refs.formDialog.setResult(resultData)
|
|
@@ -414,24 +414,36 @@ export default {
|
|
|
414
414
|
},
|
|
415
415
|
|
|
416
416
|
methods: {
|
|
417
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* Handle tab change from NcAppSidebar
|
|
419
|
+
* @param {string} tabId Tab identifier
|
|
420
|
+
*/
|
|
418
421
|
onTabChange(tabId) {
|
|
419
422
|
this.internalActiveTab = tabId
|
|
420
423
|
this.$emit('tab-change', tabId)
|
|
421
424
|
},
|
|
422
425
|
|
|
423
|
-
/**
|
|
426
|
+
/**
|
|
427
|
+
* Check if a column is currently visible
|
|
428
|
+
* @param {string} key Column key
|
|
429
|
+
*/
|
|
424
430
|
isColumnVisible(key) {
|
|
425
431
|
if (this.visibleColumns === null) return true
|
|
426
432
|
return this.visibleColumns.includes(key)
|
|
427
433
|
},
|
|
428
434
|
|
|
429
|
-
/**
|
|
435
|
+
/**
|
|
436
|
+
* Check if all columns in a group are visible
|
|
437
|
+
* @param {string[]} columns Array of column keys
|
|
438
|
+
*/
|
|
430
439
|
isGroupAllVisible(columns) {
|
|
431
440
|
return columns.every((col) => this.isColumnVisible(col.key))
|
|
432
441
|
},
|
|
433
442
|
|
|
434
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* Toggle a single column's visibility
|
|
445
|
+
* @param {string} key Column key
|
|
446
|
+
*/
|
|
435
447
|
toggleColumn(key) {
|
|
436
448
|
let newVisible
|
|
437
449
|
if (this.visibleColumns === null) {
|
|
@@ -444,7 +456,10 @@ export default {
|
|
|
444
456
|
this.$emit('columns-change', newVisible)
|
|
445
457
|
},
|
|
446
458
|
|
|
447
|
-
/**
|
|
459
|
+
/**
|
|
460
|
+
* Select or deselect all columns in a group
|
|
461
|
+
* @param {string[]} columns Array of column keys
|
|
462
|
+
*/
|
|
448
463
|
toggleGroupAll(columns) {
|
|
449
464
|
const groupKeys = columns.map((c) => c.key)
|
|
450
465
|
const allVisible = this.isGroupAllVisible(columns)
|
|
@@ -465,12 +480,18 @@ export default {
|
|
|
465
480
|
this.$emit('columns-change', newVisible)
|
|
466
481
|
},
|
|
467
482
|
|
|
468
|
-
/**
|
|
483
|
+
/**
|
|
484
|
+
* Toggle a group's expanded state
|
|
485
|
+
* @param {string} groupId Filter group identifier
|
|
486
|
+
*/
|
|
469
487
|
toggleGroup(groupId) {
|
|
470
488
|
this.$set(this.expandedGroups, groupId, !this.expandedGroups[groupId])
|
|
471
489
|
},
|
|
472
490
|
|
|
473
|
-
/**
|
|
491
|
+
/**
|
|
492
|
+
* Get filter options for a filter definition
|
|
493
|
+
* @param {object} filter Filter object
|
|
494
|
+
*/
|
|
474
495
|
getFilterOptions(filter) {
|
|
475
496
|
const facet = this.facetData[filter.key]
|
|
476
497
|
if (facet?.values?.length > 0) {
|
|
@@ -482,7 +503,10 @@ export default {
|
|
|
482
503
|
return filter.options || []
|
|
483
504
|
},
|
|
484
505
|
|
|
485
|
-
/**
|
|
506
|
+
/**
|
|
507
|
+
* Get currently selected options for a filter
|
|
508
|
+
* @param {object} filter Filter object
|
|
509
|
+
*/
|
|
486
510
|
getSelectedFilterOptions(filter) {
|
|
487
511
|
const value = this.activeFilters[filter.key]
|
|
488
512
|
if (!value) return []
|
|
@@ -491,7 +515,11 @@ export default {
|
|
|
491
515
|
return values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })
|
|
492
516
|
},
|
|
493
517
|
|
|
494
|
-
/**
|
|
518
|
+
/**
|
|
519
|
+
* Handle filter select change
|
|
520
|
+
* @param {string} key Filter key
|
|
521
|
+
* @param {Array} selected Selected values
|
|
522
|
+
*/
|
|
495
523
|
onFilterChange(key, selected) {
|
|
496
524
|
const values = selected ? selected.map((o) => o.id) : []
|
|
497
525
|
this.$emit('filter-change', { key, values })
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnInfoWidget — Renders label-value pairs in a responsive CSS grid.
|
|
3
|
+
|
|
4
|
+
Supports two modes:
|
|
5
|
+
1. Manual: provide `fields` prop with `[{ label, value }]` objects
|
|
6
|
+
2. Auto-generated: provide `object` + `schema` props to generate from JSON Schema properties
|
|
7
|
+
|
|
8
|
+
Used in dashboard and detail page grid layouts to display entity metadata.
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
class="cn-info-widget"
|
|
13
|
+
:style="gridStyle">
|
|
14
|
+
<div
|
|
15
|
+
v-for="(field, index) in displayFields"
|
|
16
|
+
:key="index"
|
|
17
|
+
class="cn-info-widget__field">
|
|
18
|
+
<dt class="cn-info-widget__label">
|
|
19
|
+
{{ field.label }}
|
|
20
|
+
</dt>
|
|
21
|
+
<dd class="cn-info-widget__value">
|
|
22
|
+
{{ field.value || '—' }}
|
|
23
|
+
</dd>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
/**
|
|
30
|
+
* CnInfoWidget — Renders label-value pairs in a responsive CSS grid.
|
|
31
|
+
*
|
|
32
|
+
* @example Manual fields
|
|
33
|
+
* <CnInfoWidget :fields="[
|
|
34
|
+
* { label: 'Email', value: 'test@example.com' },
|
|
35
|
+
* { label: 'Phone', value: '+31 6 12345678' },
|
|
36
|
+
* ]" :columns="2" />
|
|
37
|
+
*
|
|
38
|
+
* @example Auto-generated from schema
|
|
39
|
+
* <CnInfoWidget :object="myObject" :schema="mySchema" :columns="3" />
|
|
40
|
+
*/
|
|
41
|
+
export default {
|
|
42
|
+
name: 'CnInfoWidget',
|
|
43
|
+
|
|
44
|
+
props: {
|
|
45
|
+
/**
|
|
46
|
+
* Manual field definitions. Array of `{ label, value }` objects.
|
|
47
|
+
* Takes precedence over object+schema auto-generation.
|
|
48
|
+
*
|
|
49
|
+
* @type {{ label: string, value: string|number }[]}
|
|
50
|
+
*/
|
|
51
|
+
fields: {
|
|
52
|
+
type: Array,
|
|
53
|
+
default: null,
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* Object data for auto-generation mode. Properties are extracted
|
|
57
|
+
* based on the schema definition.
|
|
58
|
+
*
|
|
59
|
+
* @type {object}
|
|
60
|
+
*/
|
|
61
|
+
object: {
|
|
62
|
+
type: Object,
|
|
63
|
+
default: null,
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* JSON Schema for auto-generation mode. Each schema property
|
|
67
|
+
* generates a label:value pair using the property title as label.
|
|
68
|
+
*
|
|
69
|
+
* @type {object}
|
|
70
|
+
*/
|
|
71
|
+
schema: {
|
|
72
|
+
type: Object,
|
|
73
|
+
default: null,
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Number of columns for the grid layout.
|
|
77
|
+
*
|
|
78
|
+
* @type {number}
|
|
79
|
+
*/
|
|
80
|
+
columns: {
|
|
81
|
+
type: Number,
|
|
82
|
+
default: 2,
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Fields to include (by key). If provided, only these fields are shown.
|
|
86
|
+
* Only applies in auto-generation mode.
|
|
87
|
+
*
|
|
88
|
+
* @type {string[]}
|
|
89
|
+
*/
|
|
90
|
+
includeFields: {
|
|
91
|
+
type: Array,
|
|
92
|
+
default: null,
|
|
93
|
+
},
|
|
94
|
+
/**
|
|
95
|
+
* Fields to exclude (by key). Only applies in auto-generation mode.
|
|
96
|
+
*
|
|
97
|
+
* @type {string[]}
|
|
98
|
+
*/
|
|
99
|
+
excludeFields: {
|
|
100
|
+
type: Array,
|
|
101
|
+
default: () => [],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
computed: {
|
|
106
|
+
/**
|
|
107
|
+
* Resolved fields to display. Manual fields take precedence;
|
|
108
|
+
* otherwise generates from object + schema.
|
|
109
|
+
*
|
|
110
|
+
* @return {{ label: string, value: string|number }[]}
|
|
111
|
+
*/
|
|
112
|
+
displayFields() {
|
|
113
|
+
if (this.fields) {
|
|
114
|
+
return this.fields
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (this.object && this.schema && this.schema.properties) {
|
|
118
|
+
return this.generateFieldsFromSchema()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return []
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* CSS grid style based on column count.
|
|
126
|
+
*
|
|
127
|
+
* @return {object}
|
|
128
|
+
*/
|
|
129
|
+
gridStyle() {
|
|
130
|
+
return {
|
|
131
|
+
gridTemplateColumns: `repeat(${this.columns}, 1fr)`,
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
methods: {
|
|
137
|
+
/**
|
|
138
|
+
* Generate label-value field definitions from object + schema.
|
|
139
|
+
*
|
|
140
|
+
* @return {{ label: string, value: string|number }[]}
|
|
141
|
+
*/
|
|
142
|
+
generateFieldsFromSchema() {
|
|
143
|
+
const properties = this.schema.properties || {}
|
|
144
|
+
const keys = this.includeFields || Object.keys(properties)
|
|
145
|
+
|
|
146
|
+
return keys
|
|
147
|
+
.filter(key => !this.excludeFields.includes(key))
|
|
148
|
+
.filter(key => properties[key])
|
|
149
|
+
.map(key => ({
|
|
150
|
+
label: properties[key].title || key,
|
|
151
|
+
value: this.formatFieldValue(this.object[key], properties[key]),
|
|
152
|
+
}))
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format a field value for display based on its schema type.
|
|
157
|
+
*
|
|
158
|
+
* @param {*} value - The raw value.
|
|
159
|
+
* @param {object} schemaProp - The JSON Schema property definition.
|
|
160
|
+
* @return {string} Formatted display value.
|
|
161
|
+
*/
|
|
162
|
+
formatFieldValue(value) {
|
|
163
|
+
if (value === null || value === undefined) {
|
|
164
|
+
return ''
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
return value.join(', ')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (typeof value === 'object') {
|
|
172
|
+
return JSON.stringify(value)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof value === 'boolean') {
|
|
176
|
+
return value ? 'Yes' : 'No'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return String(value)
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<style scoped>
|
|
186
|
+
.cn-info-widget {
|
|
187
|
+
display: grid;
|
|
188
|
+
gap: 12px 24px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.cn-info-widget__field {
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
gap: 2px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.cn-info-widget__label {
|
|
198
|
+
font-size: 12px;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
color: var(--color-text-maxcontrast);
|
|
201
|
+
text-transform: uppercase;
|
|
202
|
+
letter-spacing: 0.5px;
|
|
203
|
+
margin: 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.cn-info-widget__value {
|
|
207
|
+
font-size: 14px;
|
|
208
|
+
color: var(--color-main-text);
|
|
209
|
+
margin: 0;
|
|
210
|
+
word-break: break-word;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Responsive: single column on small screens */
|
|
214
|
+
@media (max-width: 600px) {
|
|
215
|
+
.cn-info-widget {
|
|
216
|
+
grid-template-columns: 1fr !important;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnInfoWidget } from './CnInfoWidget.vue'
|