@conduction/nextcloud-vue 0.1.0-beta.6 → 0.1.0-beta.8
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 +13575 -2374
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.css +1238 -270
- package/dist/nextcloud-vue.esm.js +13517 -2336
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +11 -7
- package/src/components/CnActionsBar/CnActionsBar.vue +20 -2
- 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 +63 -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,9 @@
|
|
|
28
28
|
:view-mode="currentViewMode"
|
|
29
29
|
:show-view-toggle="showViewToggle"
|
|
30
30
|
:refreshing="refreshing"
|
|
31
|
+
:refresh-disabled="refreshDisabled"
|
|
32
|
+
:add-disabled="addDisabled"
|
|
33
|
+
:show-add="showAdd"
|
|
31
34
|
@add="onAddClick"
|
|
32
35
|
@refresh="$emit('refresh')"
|
|
33
36
|
@show-import="showImportDialog = true"
|
|
@@ -52,6 +55,7 @@
|
|
|
52
55
|
ref="massDeleteDialog"
|
|
53
56
|
:items="selectedObjects"
|
|
54
57
|
:name-field="massActionNameField"
|
|
58
|
+
:name-formatter="nameFormatter"
|
|
55
59
|
@confirm="onMassDeleteConfirm"
|
|
56
60
|
@close="showMassDeleteDialog = false" />
|
|
57
61
|
|
|
@@ -61,6 +65,7 @@
|
|
|
61
65
|
ref="massCopyDialog"
|
|
62
66
|
:items="selectedObjects"
|
|
63
67
|
:name-field="massActionNameField"
|
|
68
|
+
:name-formatter="nameFormatter"
|
|
64
69
|
@confirm="onMassCopyConfirm"
|
|
65
70
|
@close="showMassCopyDialog = false" />
|
|
66
71
|
|
|
@@ -94,6 +99,7 @@
|
|
|
94
99
|
ref="singleDeleteDialog"
|
|
95
100
|
:item="actionTargetItem"
|
|
96
101
|
:name-field="massActionNameField"
|
|
102
|
+
:name-formatter="nameFormatter"
|
|
97
103
|
@confirm="onSingleDeleteConfirm"
|
|
98
104
|
@close="closeSingleDelete" />
|
|
99
105
|
</slot>
|
|
@@ -108,6 +114,7 @@
|
|
|
108
114
|
ref="singleCopyDialog"
|
|
109
115
|
:item="actionTargetItem"
|
|
110
116
|
:name-field="massActionNameField"
|
|
117
|
+
:name-formatter="nameFormatter"
|
|
111
118
|
@confirm="onSingleCopyConfirm"
|
|
112
119
|
@close="closeSingleCopy" />
|
|
113
120
|
</slot>
|
|
@@ -500,6 +507,11 @@ export default {
|
|
|
500
507
|
type: String,
|
|
501
508
|
default: 'title',
|
|
502
509
|
},
|
|
510
|
+
/** Optional function to format item names in dialogs. Receives the item, returns a string. Overrides massActionNameField when provided. */
|
|
511
|
+
nameFormatter: {
|
|
512
|
+
type: Function,
|
|
513
|
+
default: null,
|
|
514
|
+
},
|
|
503
515
|
/** Available export formats for the export dialog */
|
|
504
516
|
exportFormats: {
|
|
505
517
|
type: Array,
|
|
@@ -563,6 +575,21 @@ export default {
|
|
|
563
575
|
type: Boolean,
|
|
564
576
|
default: false,
|
|
565
577
|
},
|
|
578
|
+
/** Whether the refresh action is disabled (e.g. when required selections are missing) */
|
|
579
|
+
refreshDisabled: {
|
|
580
|
+
type: Boolean,
|
|
581
|
+
default: false,
|
|
582
|
+
},
|
|
583
|
+
/** Whether the Add button is disabled (e.g. when required selections are missing) */
|
|
584
|
+
addDisabled: {
|
|
585
|
+
type: Boolean,
|
|
586
|
+
default: false,
|
|
587
|
+
},
|
|
588
|
+
/** Whether to show the Add button in the actions bar */
|
|
589
|
+
showAdd: {
|
|
590
|
+
type: Boolean,
|
|
591
|
+
default: true,
|
|
592
|
+
},
|
|
566
593
|
/**
|
|
567
594
|
* Store instance for automatic save integration. When provided alongside
|
|
568
595
|
* objectType, the form dialog saves directly to the store instead of
|
|
@@ -751,28 +778,40 @@ export default {
|
|
|
751
778
|
this.$emit('mass-import', payload)
|
|
752
779
|
},
|
|
753
780
|
|
|
754
|
-
/**
|
|
781
|
+
/**
|
|
782
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
783
|
+
* @public
|
|
784
|
+
*/
|
|
755
785
|
setMassDeleteResult(resultData) {
|
|
756
786
|
if (this.$refs.massDeleteDialog) {
|
|
757
787
|
this.$refs.massDeleteDialog.setResult(resultData)
|
|
758
788
|
}
|
|
759
789
|
},
|
|
760
790
|
|
|
761
|
-
/**
|
|
791
|
+
/**
|
|
792
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
793
|
+
* @public
|
|
794
|
+
*/
|
|
762
795
|
setMassCopyResult(resultData) {
|
|
763
796
|
if (this.$refs.massCopyDialog) {
|
|
764
797
|
this.$refs.massCopyDialog.setResult(resultData)
|
|
765
798
|
}
|
|
766
799
|
},
|
|
767
800
|
|
|
768
|
-
/**
|
|
801
|
+
/**
|
|
802
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
803
|
+
* @public
|
|
804
|
+
*/
|
|
769
805
|
setExportResult(resultData) {
|
|
770
806
|
if (this.$refs.exportDialog) {
|
|
771
807
|
this.$refs.exportDialog.setResult(resultData)
|
|
772
808
|
}
|
|
773
809
|
},
|
|
774
810
|
|
|
775
|
-
/**
|
|
811
|
+
/**
|
|
812
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
813
|
+
* @public
|
|
814
|
+
*/
|
|
776
815
|
setImportResult(resultData) {
|
|
777
816
|
if (this.$refs.importDialog) {
|
|
778
817
|
this.$refs.importDialog.setResult(resultData)
|
|
@@ -780,11 +819,17 @@ export default {
|
|
|
780
819
|
},
|
|
781
820
|
|
|
782
821
|
// --- Backward-compatible aliases ---
|
|
783
|
-
/**
|
|
822
|
+
/**
|
|
823
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
824
|
+
* @public
|
|
825
|
+
*/
|
|
784
826
|
setDeleteResult(resultData) {
|
|
785
827
|
this.setMassDeleteResult(resultData)
|
|
786
828
|
},
|
|
787
|
-
/**
|
|
829
|
+
/**
|
|
830
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
831
|
+
* @public
|
|
832
|
+
*/
|
|
788
833
|
setCopyResult(resultData) {
|
|
789
834
|
this.setMassCopyResult(resultData)
|
|
790
835
|
},
|
|
@@ -837,21 +882,30 @@ export default {
|
|
|
837
882
|
this.editItem = null
|
|
838
883
|
},
|
|
839
884
|
|
|
840
|
-
/**
|
|
885
|
+
/**
|
|
886
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
887
|
+
* @public
|
|
888
|
+
*/
|
|
841
889
|
setSingleDeleteResult(resultData) {
|
|
842
890
|
if (this.$refs.singleDeleteDialog) {
|
|
843
891
|
this.$refs.singleDeleteDialog.setResult(resultData)
|
|
844
892
|
}
|
|
845
893
|
},
|
|
846
894
|
|
|
847
|
-
/**
|
|
895
|
+
/**
|
|
896
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
897
|
+
* @public
|
|
898
|
+
*/
|
|
848
899
|
setSingleCopyResult(resultData) {
|
|
849
900
|
if (this.$refs.singleCopyDialog) {
|
|
850
901
|
this.$refs.singleCopyDialog.setResult(resultData)
|
|
851
902
|
}
|
|
852
903
|
},
|
|
853
904
|
|
|
854
|
-
/**
|
|
905
|
+
/**
|
|
906
|
+
* @param {*} resultData Result data to pass to the dialog
|
|
907
|
+
* @public
|
|
908
|
+
*/
|
|
855
909
|
setFormResult(resultData) {
|
|
856
910
|
if (this.$refs.formDialog) {
|
|
857
911
|
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'
|