@conduction/nextcloud-vue 0.1.0-beta.1
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 +10710 -0
- package/dist/nextcloud-vue.cjs.js.map +1 -0
- package/dist/nextcloud-vue.css +803 -0
- package/dist/nextcloud-vue.esm.js +10665 -0
- package/dist/nextcloud-vue.esm.js.map +1 -0
- package/package.json +63 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +152 -0
- package/src/components/CnCardGrid/index.js +1 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -0
- package/src/components/CnCellRenderer/index.js +1 -0
- package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -0
- package/src/components/CnConfigurationCard/index.js +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +354 -0
- package/src/components/CnDataTable/index.js +1 -0
- package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +88 -0
- package/src/components/CnDetailViewLayout/index.js +1 -0
- package/src/components/CnEmptyState/CnEmptyState.vue +78 -0
- package/src/components/CnEmptyState/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +223 -0
- package/src/components/CnFacetSidebar/index.js +1 -0
- package/src/components/CnFilterBar/CnFilterBar.vue +152 -0
- package/src/components/CnFilterBar/index.js +1 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +682 -0
- package/src/components/CnIndexPage/index.js +1 -0
- package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -0
- package/src/components/CnKpiGrid/index.js +1 -0
- package/src/components/CnListViewLayout/CnListViewLayout.vue +80 -0
- package/src/components/CnListViewLayout/index.js +1 -0
- package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -0
- package/src/components/CnMassActionBar/index.js +1 -0
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -0
- package/src/components/CnMassCopyDialog/index.js +1 -0
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -0
- package/src/components/CnMassDeleteDialog/index.js +1 -0
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -0
- package/src/components/CnMassExportDialog/index.js +1 -0
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -0
- package/src/components/CnMassImportDialog/index.js +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +292 -0
- package/src/components/CnObjectCard/index.js +1 -0
- package/src/components/CnPagination/CnPagination.vue +252 -0
- package/src/components/CnPagination/index.js +1 -0
- package/src/components/CnRowActions/CnRowActions.vue +73 -0
- package/src/components/CnRowActions/index.js +1 -0
- package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -0
- package/src/components/CnSettingsCard/index.js +1 -0
- package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -0
- package/src/components/CnSettingsSection/index.js +1 -0
- package/src/components/CnStatsBlock/CnStatsBlock.vue +366 -0
- package/src/components/CnStatsBlock/index.js +1 -0
- package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -0
- package/src/components/CnStatusBadge/index.js +1 -0
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -0
- package/src/components/CnVersionInfoCard/index.js +1 -0
- package/src/components/CnViewModeToggle/CnViewModeToggle.vue +77 -0
- package/src/components/CnViewModeToggle/index.js +1 -0
- package/src/components/index.js +25 -0
- package/src/composables/index.js +3 -0
- package/src/composables/useDetailView.js +132 -0
- package/src/composables/useListView.js +153 -0
- package/src/composables/useSubResource.js +142 -0
- package/src/css/badge.css +51 -0
- package/src/css/card.css +128 -0
- package/src/css/detail.css +68 -0
- package/src/css/index.css +8 -0
- package/src/css/layout.css +90 -0
- package/src/css/pagination.css +72 -0
- package/src/css/table.css +143 -0
- package/src/css/utilities.css +46 -0
- package/src/index.js +50 -0
- package/src/store/createSubResourcePlugin.js +135 -0
- package/src/store/index.js +3 -0
- package/src/store/plugins/auditTrails.js +17 -0
- package/src/store/plugins/files.js +186 -0
- package/src/store/plugins/index.js +4 -0
- package/src/store/plugins/lifecycle.js +180 -0
- package/src/store/plugins/relations.js +68 -0
- package/src/store/useObjectStore.js +625 -0
- package/src/types/auditTrail.d.ts +32 -0
- package/src/types/file.d.ts +23 -0
- package/src/types/index.d.ts +35 -0
- package/src/types/notification.d.ts +36 -0
- package/src/types/object.d.ts +40 -0
- package/src/types/organisation.d.ts +41 -0
- package/src/types/register.d.ts +25 -0
- package/src/types/schema.d.ts +39 -0
- package/src/types/shared.d.ts +79 -0
- package/src/types/source.d.ts +14 -0
- package/src/types/task.d.ts +31 -0
- package/src/utils/errors.js +96 -0
- package/src/utils/headers.js +44 -0
- package/src/utils/index.js +3 -0
- package/src/utils/schema.js +287 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cn-table-container" :class="{ 'cn-table-container--scrollable': scrollable }">
|
|
3
|
+
<!-- Loading State -->
|
|
4
|
+
<div v-if="loading" class="cn-table-loading">
|
|
5
|
+
<NcLoadingIcon :size="32" />
|
|
6
|
+
<p>{{ loadingText }}</p>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- Table -->
|
|
10
|
+
<table v-else class="cn-data-table">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<!-- Checkbox column -->
|
|
14
|
+
<th v-if="selectable" class="cn-table-col--checkbox">
|
|
15
|
+
<NcCheckboxRadioSwitch
|
|
16
|
+
:checked="allSelected"
|
|
17
|
+
:indeterminate="someSelected && !allSelected"
|
|
18
|
+
@update:checked="toggleSelectAll" />
|
|
19
|
+
</th>
|
|
20
|
+
|
|
21
|
+
<!-- Data columns -->
|
|
22
|
+
<th
|
|
23
|
+
v-for="col in effectiveColumns"
|
|
24
|
+
:key="col.key"
|
|
25
|
+
:class="[
|
|
26
|
+
col.sortable ? 'cn-table-header--sortable' : '',
|
|
27
|
+
col.class || '',
|
|
28
|
+
]"
|
|
29
|
+
:style="col.width ? { width: col.width } : {}"
|
|
30
|
+
@click="col.sortable ? onSort(col.key) : null">
|
|
31
|
+
{{ col.label }}
|
|
32
|
+
<span
|
|
33
|
+
v-if="col.sortable && sortKey === col.key"
|
|
34
|
+
class="cn-table-sort-indicator">
|
|
35
|
+
{{ sortOrder === 'asc' ? '▲' : '▼' }}
|
|
36
|
+
</span>
|
|
37
|
+
</th>
|
|
38
|
+
|
|
39
|
+
<!-- Actions column -->
|
|
40
|
+
<th v-if="$scopedSlots['row-actions']" class="cn-table-col--actions">
|
|
41
|
+
<!-- Actions header intentionally empty -->
|
|
42
|
+
</th>
|
|
43
|
+
</tr>
|
|
44
|
+
</thead>
|
|
45
|
+
|
|
46
|
+
<tbody>
|
|
47
|
+
<!-- Empty state -->
|
|
48
|
+
<tr v-if="rows.length === 0" class="cn-table-empty">
|
|
49
|
+
<td :colspan="totalColumns">
|
|
50
|
+
<slot name="empty">
|
|
51
|
+
{{ emptyText }}
|
|
52
|
+
</slot>
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
|
|
56
|
+
<!-- Data rows -->
|
|
57
|
+
<tr
|
|
58
|
+
v-for="row in rows"
|
|
59
|
+
v-else
|
|
60
|
+
:key="row[rowKey]"
|
|
61
|
+
class="cn-table-row"
|
|
62
|
+
:class="[
|
|
63
|
+
isSelected(row) ? 'cn-table-row--selected' : '',
|
|
64
|
+
rowClass ? rowClass(row) : '',
|
|
65
|
+
]"
|
|
66
|
+
@click="$emit('row-click', row)">
|
|
67
|
+
<!-- Checkbox -->
|
|
68
|
+
<td v-if="selectable" class="cn-table-col--checkbox" @click.stop>
|
|
69
|
+
<NcCheckboxRadioSwitch
|
|
70
|
+
:checked="isSelected(row)"
|
|
71
|
+
@update:checked="toggleSelect(row)" />
|
|
72
|
+
</td>
|
|
73
|
+
|
|
74
|
+
<!-- Data cells -->
|
|
75
|
+
<td
|
|
76
|
+
v-for="col in effectiveColumns"
|
|
77
|
+
:key="col.key"
|
|
78
|
+
:class="col.cellClass || ''"
|
|
79
|
+
:style="col.width ? { maxWidth: col.width } : {}">
|
|
80
|
+
<slot :name="'column-' + col.key" :row="row" :value="getCellValue(row, col.key)">
|
|
81
|
+
<!-- Schema-driven: use CnCellRenderer -->
|
|
82
|
+
<CnCellRenderer
|
|
83
|
+
v-if="isSchemaColumn(col)"
|
|
84
|
+
:value="getCellValue(row, col.key)"
|
|
85
|
+
:property="getSchemaProperty(col.key)" />
|
|
86
|
+
<!-- Manual: plain text -->
|
|
87
|
+
<template v-else>
|
|
88
|
+
{{ getCellValue(row, col.key) }}
|
|
89
|
+
</template>
|
|
90
|
+
</slot>
|
|
91
|
+
</td>
|
|
92
|
+
|
|
93
|
+
<!-- Row actions -->
|
|
94
|
+
<td v-if="$scopedSlots['row-actions']" class="cn-table-col--actions" @click.stop>
|
|
95
|
+
<slot name="row-actions" :row="row" />
|
|
96
|
+
</td>
|
|
97
|
+
</tr>
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</div>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script>
|
|
104
|
+
import { NcLoadingIcon, NcCheckboxRadioSwitch } from '@nextcloud/vue'
|
|
105
|
+
import { CnCellRenderer } from '../CnCellRenderer/index.js'
|
|
106
|
+
import { columnsFromSchema } from '../../utils/schema.js'
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* CnDataTable — Generic sortable data table for list views.
|
|
110
|
+
*
|
|
111
|
+
* Replaces the copy-pasted `<table class="viewTable">` HTML pattern found in
|
|
112
|
+
* every list view across OpenRegister, Pipelinq, and Procest. Supports sorting,
|
|
113
|
+
* row selection, custom cell rendering via scoped slots, loading states,
|
|
114
|
+
* and empty states.
|
|
115
|
+
*
|
|
116
|
+
* When a `schema` prop is provided, columns are auto-generated from schema
|
|
117
|
+
* properties and cells render through CnCellRenderer for type-aware formatting
|
|
118
|
+
* (dates, booleans, UUIDs, enums, etc.). Scoped slots still override individual
|
|
119
|
+
* columns when needed.
|
|
120
|
+
*
|
|
121
|
+
* NL Design tokens used:
|
|
122
|
+
* - --nldesign-component-table-header-background-color
|
|
123
|
+
* - --nldesign-component-table-row-hover-background-color
|
|
124
|
+
* - --nldesign-component-table-border-color
|
|
125
|
+
*
|
|
126
|
+
* @example Manual columns (backwards compatible)
|
|
127
|
+
* <CnDataTable
|
|
128
|
+
* :columns="[
|
|
129
|
+
* { key: 'name', label: 'Name', sortable: true },
|
|
130
|
+
* { key: 'email', label: 'Email' },
|
|
131
|
+
* ]"
|
|
132
|
+
* :rows="clients"
|
|
133
|
+
* @row-click="openClient" />
|
|
134
|
+
*
|
|
135
|
+
* @example Schema-driven (auto columns)
|
|
136
|
+
* <CnDataTable :schema="schema" :rows="objects" />
|
|
137
|
+
*
|
|
138
|
+
* @example Schema with overrides and custom cell
|
|
139
|
+
* <CnDataTable
|
|
140
|
+
* :schema="schema"
|
|
141
|
+
* :exclude-columns="['description']"
|
|
142
|
+
* :column-overrides="{ status: { width: '200px' } }"
|
|
143
|
+
* :rows="objects">
|
|
144
|
+
* <template #column-status="{ row, value }">
|
|
145
|
+
* <QuickStatusDropdown :case-obj="row" />
|
|
146
|
+
* </template>
|
|
147
|
+
* </CnDataTable>
|
|
148
|
+
*/
|
|
149
|
+
export default {
|
|
150
|
+
name: 'CnDataTable',
|
|
151
|
+
|
|
152
|
+
components: {
|
|
153
|
+
NcLoadingIcon,
|
|
154
|
+
NcCheckboxRadioSwitch,
|
|
155
|
+
CnCellRenderer,
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
props: {
|
|
159
|
+
/**
|
|
160
|
+
* Column definitions (manual mode).
|
|
161
|
+
* Not required when `schema` is provided.
|
|
162
|
+
* @type {Array<{key: string, label: string, sortable?: boolean, width?: string, class?: string, cellClass?: string}>}
|
|
163
|
+
*/
|
|
164
|
+
columns: {
|
|
165
|
+
type: Array,
|
|
166
|
+
default: () => [],
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Schema object with `properties` field (schema-driven mode).
|
|
170
|
+
* When provided, columns are auto-generated from schema properties.
|
|
171
|
+
*/
|
|
172
|
+
schema: {
|
|
173
|
+
type: Object,
|
|
174
|
+
default: null,
|
|
175
|
+
},
|
|
176
|
+
/** Per-column overrides when using schema mode: { key: { width, label, sortable, ... } } */
|
|
177
|
+
columnOverrides: {
|
|
178
|
+
type: Object,
|
|
179
|
+
default: () => ({}),
|
|
180
|
+
},
|
|
181
|
+
/** Column keys to exclude when using schema mode */
|
|
182
|
+
excludeColumns: {
|
|
183
|
+
type: Array,
|
|
184
|
+
default: () => [],
|
|
185
|
+
},
|
|
186
|
+
/** Column keys to include when using schema mode (whitelist) */
|
|
187
|
+
includeColumns: {
|
|
188
|
+
type: Array,
|
|
189
|
+
default: null,
|
|
190
|
+
},
|
|
191
|
+
/** Row data array. Each row should have a unique identifier (see rowKey). */
|
|
192
|
+
rows: {
|
|
193
|
+
type: Array,
|
|
194
|
+
default: () => [],
|
|
195
|
+
},
|
|
196
|
+
/** Whether data is loading (shows loading spinner) */
|
|
197
|
+
loading: {
|
|
198
|
+
type: Boolean,
|
|
199
|
+
default: false,
|
|
200
|
+
},
|
|
201
|
+
/** Current sort column key */
|
|
202
|
+
sortKey: {
|
|
203
|
+
type: String,
|
|
204
|
+
default: null,
|
|
205
|
+
},
|
|
206
|
+
/** Current sort order: 'asc' or 'desc' */
|
|
207
|
+
sortOrder: {
|
|
208
|
+
type: String,
|
|
209
|
+
default: 'asc',
|
|
210
|
+
validator: (v) => ['asc', 'desc'].includes(v),
|
|
211
|
+
},
|
|
212
|
+
/** Whether rows can be selected with checkboxes */
|
|
213
|
+
selectable: {
|
|
214
|
+
type: Boolean,
|
|
215
|
+
default: false,
|
|
216
|
+
},
|
|
217
|
+
/** Array of currently selected row IDs */
|
|
218
|
+
selectedIds: {
|
|
219
|
+
type: Array,
|
|
220
|
+
default: () => [],
|
|
221
|
+
},
|
|
222
|
+
/** Property name used as unique row identifier */
|
|
223
|
+
rowKey: {
|
|
224
|
+
type: String,
|
|
225
|
+
default: 'id',
|
|
226
|
+
},
|
|
227
|
+
/** Text shown when there are no rows */
|
|
228
|
+
emptyText: {
|
|
229
|
+
type: String,
|
|
230
|
+
default: 'No items found',
|
|
231
|
+
},
|
|
232
|
+
/** Function returning CSS class(es) for a row: (row) => string|object */
|
|
233
|
+
rowClass: {
|
|
234
|
+
type: Function,
|
|
235
|
+
default: null,
|
|
236
|
+
},
|
|
237
|
+
/** Whether to constrain table height and make it scrollable */
|
|
238
|
+
scrollable: {
|
|
239
|
+
type: Boolean,
|
|
240
|
+
default: false,
|
|
241
|
+
},
|
|
242
|
+
/** Text shown while loading */
|
|
243
|
+
loadingText: {
|
|
244
|
+
type: String,
|
|
245
|
+
default: 'Loading...',
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
computed: {
|
|
250
|
+
/**
|
|
251
|
+
* Effective columns: schema-generated or manually provided.
|
|
252
|
+
* Schema columns take precedence when schema is provided and no manual columns given.
|
|
253
|
+
*/
|
|
254
|
+
effectiveColumns() {
|
|
255
|
+
if (this.schema && this.columns.length === 0) {
|
|
256
|
+
return columnsFromSchema(this.schema, {
|
|
257
|
+
exclude: this.excludeColumns,
|
|
258
|
+
include: this.includeColumns,
|
|
259
|
+
overrides: this.columnOverrides,
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
return this.columns
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
totalColumns() {
|
|
266
|
+
let count = this.effectiveColumns.length
|
|
267
|
+
if (this.selectable) count++
|
|
268
|
+
if (this.$scopedSlots['row-actions']) count++
|
|
269
|
+
return count
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
allSelected() {
|
|
273
|
+
return this.rows.length > 0
|
|
274
|
+
&& this.rows.every((row) => this.selectedIds.includes(row[this.rowKey]))
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
someSelected() {
|
|
278
|
+
return this.rows.some((row) => this.selectedIds.includes(row[this.rowKey]))
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
methods: {
|
|
283
|
+
/**
|
|
284
|
+
* Get a cell value from a row using dot-notation key.
|
|
285
|
+
* @param {object} row The row data
|
|
286
|
+
* @param {string} key The column key (supports dot notation: 'address.city')
|
|
287
|
+
* @return {*} The cell value
|
|
288
|
+
*/
|
|
289
|
+
getCellValue(row, key) {
|
|
290
|
+
if (key.includes('.')) {
|
|
291
|
+
return key.split('.').reduce((obj, k) => obj?.[k], row)
|
|
292
|
+
}
|
|
293
|
+
return row[key]
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Check if a column was generated from schema (has type info).
|
|
298
|
+
* @param {object} col Column definition
|
|
299
|
+
* @return {boolean}
|
|
300
|
+
*/
|
|
301
|
+
isSchemaColumn(col) {
|
|
302
|
+
return !!(this.schema && col.type)
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get the schema property definition for a column key.
|
|
307
|
+
* @param {string} key Column key
|
|
308
|
+
* @return {object} Property definition
|
|
309
|
+
*/
|
|
310
|
+
getSchemaProperty(key) {
|
|
311
|
+
return this.schema?.properties?.[key] || {}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
isSelected(row) {
|
|
315
|
+
return this.selectedIds.includes(row[this.rowKey])
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Handle sort column click.
|
|
320
|
+
* @param {string} key Column key
|
|
321
|
+
*/
|
|
322
|
+
onSort(key) {
|
|
323
|
+
let order = 'asc'
|
|
324
|
+
if (this.sortKey === key) {
|
|
325
|
+
order = this.sortOrder === 'asc' ? 'desc' : 'asc'
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* @event sort Emitted when a sortable column header is clicked.
|
|
329
|
+
* @type {{ key: string, order: 'asc'|'desc' }}
|
|
330
|
+
*/
|
|
331
|
+
this.$emit('sort', { key, order })
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
toggleSelect(row) {
|
|
335
|
+
const id = row[this.rowKey]
|
|
336
|
+
const newIds = this.isSelected(row)
|
|
337
|
+
? this.selectedIds.filter((i) => i !== id)
|
|
338
|
+
: [...this.selectedIds, id]
|
|
339
|
+
/** @event select Emitted when row selection changes. Payload: array of selected IDs. */
|
|
340
|
+
this.$emit('select', newIds)
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
toggleSelectAll() {
|
|
344
|
+
if (this.allSelected) {
|
|
345
|
+
this.$emit('select', [])
|
|
346
|
+
} else {
|
|
347
|
+
this.$emit('select', this.rows.map((row) => row[this.rowKey]))
|
|
348
|
+
}
|
|
349
|
+
/** @event select-all Emitted when select-all checkbox is toggled. */
|
|
350
|
+
this.$emit('select-all', !this.allSelected)
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnDataTable } from './CnDataTable.vue'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cn-detail-layout">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="cn-detail-layout__header">
|
|
5
|
+
<NcButton @click="$emit('back')">
|
|
6
|
+
<template #icon>
|
|
7
|
+
<ArrowLeft :size="20" />
|
|
8
|
+
</template>
|
|
9
|
+
{{ backLabel }}
|
|
10
|
+
</NcButton>
|
|
11
|
+
|
|
12
|
+
<h2 class="cn-detail-layout__title">
|
|
13
|
+
<slot name="title">{{ title }}</slot>
|
|
14
|
+
</h2>
|
|
15
|
+
|
|
16
|
+
<div class="cn-detail-layout__actions">
|
|
17
|
+
<slot name="actions" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Loading state -->
|
|
22
|
+
<div v-if="loading" class="cn-loading-container">
|
|
23
|
+
<NcLoadingIcon :size="32" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- Main content -->
|
|
27
|
+
<div v-else class="cn-detail-layout__content">
|
|
28
|
+
<slot />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Delete confirmation dialog -->
|
|
32
|
+
<slot name="dialogs" />
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
|
|
38
|
+
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* CnDetailViewLayout — Detail page layout with back button, title, actions, and content.
|
|
42
|
+
*
|
|
43
|
+
* Provides the standard structure for detail/edit views: back navigation,
|
|
44
|
+
* page title, action buttons, and a content area. Supports loading state
|
|
45
|
+
* and a dialogs slot for modals.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* <CnDetailViewLayout
|
|
49
|
+
* title="Client: Acme Corp"
|
|
50
|
+
* :loading="isLoading"
|
|
51
|
+
* @back="goBack">
|
|
52
|
+
* <template #actions>
|
|
53
|
+
* <NcButton @click="edit">Edit</NcButton>
|
|
54
|
+
* <NcButton type="error" @click="confirmDelete">Delete</NcButton>
|
|
55
|
+
* </template>
|
|
56
|
+
* <div class="cn-detail-grid">
|
|
57
|
+
* <div class="cn-detail-item">...</div>
|
|
58
|
+
* </div>
|
|
59
|
+
* </CnDetailViewLayout>
|
|
60
|
+
*/
|
|
61
|
+
export default {
|
|
62
|
+
name: 'CnDetailViewLayout',
|
|
63
|
+
|
|
64
|
+
components: {
|
|
65
|
+
NcButton,
|
|
66
|
+
NcLoadingIcon,
|
|
67
|
+
ArrowLeft,
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
props: {
|
|
71
|
+
/** Page title */
|
|
72
|
+
title: {
|
|
73
|
+
type: String,
|
|
74
|
+
default: '',
|
|
75
|
+
},
|
|
76
|
+
/** Whether data is loading */
|
|
77
|
+
loading: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
81
|
+
/** Back button label */
|
|
82
|
+
backLabel: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: 'Back',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnDetailViewLayout } from './CnDetailViewLayout.vue'
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NcEmptyContent :name="title" :description="description">
|
|
3
|
+
<template #icon>
|
|
4
|
+
<slot name="icon">
|
|
5
|
+
<component :is="icon" v-if="icon" :size="64" />
|
|
6
|
+
</slot>
|
|
7
|
+
</template>
|
|
8
|
+
<template v-if="actionLabel" #action>
|
|
9
|
+
<slot name="action">
|
|
10
|
+
<NcButton :type="actionType" @click="$emit('action')">
|
|
11
|
+
{{ actionLabel }}
|
|
12
|
+
</NcButton>
|
|
13
|
+
</slot>
|
|
14
|
+
</template>
|
|
15
|
+
</NcEmptyContent>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { NcEmptyContent, NcButton } from '@nextcloud/vue'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* CnEmptyState — Consistent empty state display wrapping NcEmptyContent.
|
|
23
|
+
*
|
|
24
|
+
* Provides a unified empty state pattern with icon, title, description,
|
|
25
|
+
* and optional action button. Used across all list views.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <CnEmptyState
|
|
29
|
+
* title="No clients yet"
|
|
30
|
+
* description="Create your first client to get started"
|
|
31
|
+
* action-label="New Client"
|
|
32
|
+
* @action="createClient" />
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <!-- With custom icon -->
|
|
36
|
+
* <CnEmptyState title="No results">
|
|
37
|
+
* <template #icon>
|
|
38
|
+
* <Magnify :size="64" />
|
|
39
|
+
* </template>
|
|
40
|
+
* </CnEmptyState>
|
|
41
|
+
*/
|
|
42
|
+
export default {
|
|
43
|
+
name: 'CnEmptyState',
|
|
44
|
+
|
|
45
|
+
components: {
|
|
46
|
+
NcEmptyContent,
|
|
47
|
+
NcButton,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
props: {
|
|
51
|
+
/** Main title text */
|
|
52
|
+
title: {
|
|
53
|
+
type: String,
|
|
54
|
+
required: true,
|
|
55
|
+
},
|
|
56
|
+
/** Description text below the title */
|
|
57
|
+
description: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: '',
|
|
60
|
+
},
|
|
61
|
+
/** Vue component for the icon (e.g., imported material design icon) */
|
|
62
|
+
icon: {
|
|
63
|
+
type: [Object, null],
|
|
64
|
+
default: null,
|
|
65
|
+
},
|
|
66
|
+
/** Action button label. If empty, no button is shown. */
|
|
67
|
+
actionLabel: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: '',
|
|
70
|
+
},
|
|
71
|
+
/** NcButton type for the action button */
|
|
72
|
+
actionType: {
|
|
73
|
+
type: String,
|
|
74
|
+
default: 'primary',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnEmptyState } from './CnEmptyState.vue'
|