@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.
Files changed (82) hide show
  1. package/dist/nextcloud-vue.cjs.js +13606 -1918
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +1238 -270
  4. package/dist/nextcloud-vue.esm.js +13548 -1880
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/package.json +9 -4
  7. package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
  8. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +1 -11
  9. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +5 -1
  10. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +1 -1
  11. package/src/components/CnCard/CnCard.vue +415 -0
  12. package/src/components/CnCard/index.js +1 -0
  13. package/src/components/CnCardGrid/CnCardGrid.vue +20 -20
  14. package/src/components/CnChartWidget/CnChartWidget.vue +3 -1
  15. package/src/components/CnCopyDialog/CnCopyDialog.vue +7 -1
  16. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +4 -0
  17. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -0
  18. package/src/components/CnDataTable/CnDataTable.vue +6 -2
  19. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +7 -1
  20. package/src/components/CnDetailCard/CnDetailCard.vue +12 -1
  21. package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
  22. package/src/components/CnDetailGrid/index.js +1 -0
  23. package/src/components/CnDetailPage/CnDetailPage.vue +157 -11
  24. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -1
  25. package/src/components/CnFormDialog/CnFormDialog.vue +934 -920
  26. package/src/components/CnIcon/CnIcon.vue +1 -1
  27. package/src/components/CnIndexPage/CnIndexPage.vue +51 -9
  28. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +37 -9
  29. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  30. package/src/components/CnInfoWidget/index.js +1 -0
  31. package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
  32. package/src/components/CnJsonViewer/index.js +1 -0
  33. package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
  34. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +7 -1
  35. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +7 -1
  36. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +1 -1
  37. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +1 -1
  38. package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
  39. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
  40. package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
  41. package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
  42. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +45 -668
  43. package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
  44. package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
  45. package/src/components/CnObjectSidebar/index.js +5 -0
  46. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  47. package/src/components/CnProgressBar/index.js +1 -0
  48. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +1 -1
  49. package/src/components/CnStatsBlock/CnStatsBlock.vue +27 -11
  50. package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
  51. package/src/components/CnStatsPanel/index.js +1 -0
  52. package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
  53. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +5 -1
  54. package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
  55. package/src/components/CnTableWidget/index.js +1 -0
  56. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +36 -1
  57. package/src/components/index.js +11 -0
  58. package/src/composables/useDashboardView.js +58 -12
  59. package/src/composables/useDetailView.js +3 -2
  60. package/src/composables/useListView.js +7 -6
  61. package/src/composables/useSubResource.js +3 -3
  62. package/src/css/badge.css +32 -0
  63. package/src/css/card.css +1 -0
  64. package/src/css/detail-page.css +74 -7
  65. package/src/index.js +16 -0
  66. package/src/mixins/gridLayout.js +118 -0
  67. package/src/store/createCrudStore.js +360 -0
  68. package/src/store/createSubResourcePlugin.js +5 -15
  69. package/src/store/index.js +1 -0
  70. package/src/store/plugins/auditTrails.js +346 -6
  71. package/src/store/plugins/lifecycle.js +4 -4
  72. package/src/store/plugins/registerMapping.js +18 -8
  73. package/src/store/plugins/relations.js +1 -1
  74. package/src/store/plugins/search.js +21 -8
  75. package/src/store/useObjectStore.js +30 -36
  76. package/src/utils/getTheme.js +9 -0
  77. package/src/utils/headers.js +13 -3
  78. package/src/utils/index.js +1 -0
  79. package/src/utils/schema.js +3 -3
  80. package/src/utils/widgetVisibility.js +162 -0
  81. package/src/components/CnObjectCard/eslint-setup.md +0 -235
  82. package/src/components/CnObjectCard/package.json-or.json +0 -132
@@ -0,0 +1,482 @@
1
+ <template>
2
+ <div class="cn-sidebar-tab">
3
+ <!-- Add / Edit task -->
4
+ <div class="cn-sidebar-tab__section">
5
+ <div class="cn-sidebar-tab__action--row">
6
+ <NcTextField
7
+ v-model="newTaskSummary"
8
+ :label="editingTaskId ? editLabel : addTaskPlaceholder"
9
+ @keyup.enter="editingTaskId ? saveEdit() : addTask()" />
10
+ <NcButton
11
+ v-if="editingTaskId"
12
+ type="tertiary"
13
+ @click="cancelEdit">
14
+ <template #icon>
15
+ <Close :size="20" />
16
+ </template>
17
+ </NcButton>
18
+ <NcButton
19
+ type="primary"
20
+ :disabled="!newTaskSummary.trim() || saving"
21
+ @click="editingTaskId ? saveEdit() : addTask()">
22
+ <template #icon>
23
+ <Plus v-if="!editingTaskId" :size="20" />
24
+ <ContentSave v-else :size="20" />
25
+ </template>
26
+ </NcButton>
27
+ </div>
28
+ <div class="cn-sidebar-tab__grid">
29
+ <NcDateTimePickerNative
30
+ id="task-deadline"
31
+ v-model="newTaskDue"
32
+ :label="deadlineLabel"
33
+ type="date" />
34
+ <NcSelect
35
+ v-model="newTaskAssignee"
36
+ :options="userList"
37
+ :placeholder="assigneeLabel"
38
+ :input-label="assigneeLabel"
39
+ label="displayName"
40
+ track-by="userId"
41
+ :clearable="true" />
42
+ </div>
43
+ </div>
44
+
45
+ <!-- Filters -->
46
+ <div v-if="tasks.length > 0 || filterStatus || filterAssignee" class="cn-sidebar-tab__section cn-sidebar-tab__section--filters">
47
+ <div class="cn-sidebar-tab__grid">
48
+ <NcSelect
49
+ v-model="filterStatus"
50
+ :options="statusOptions"
51
+ :placeholder="statusFilterLabel"
52
+ :input-label="statusFilterLabel"
53
+ :clearable="true" />
54
+ <NcSelect
55
+ v-model="filterAssignee"
56
+ :options="assigneeOptions"
57
+ :placeholder="assigneeFilterLabel"
58
+ :input-label="assigneeFilterLabel"
59
+ :clearable="true" />
60
+ </div>
61
+ </div>
62
+
63
+ <!-- Tasks list -->
64
+ <NcLoadingIcon v-if="loading" />
65
+ <div v-else-if="filteredTasks.length === 0" class="cn-sidebar-tab__empty">
66
+ {{ noTasksLabel }}
67
+ </div>
68
+ <div v-else class="cn-sidebar-tab__list">
69
+ <NcListItem
70
+ v-for="task in filteredTasks"
71
+ :key="task.id"
72
+ :name="task.summary || task.title || task.name"
73
+ :bold="false"
74
+ :force-display-actions="true"
75
+ :class="{ 'cn-sidebar-tab__task--overdue': isOverdue(task) }">
76
+ <template #icon>
77
+ <button class="cn-sidebar-tab__task-checkbox" @click.stop="toggleTask(task)">
78
+ <CheckboxMarkedOutline v-if="task.status === 'completed'" :size="32" class="cn-sidebar-tab__task-done" />
79
+ <CheckboxBlankOutline v-else :size="32" :class="{ 'cn-sidebar-tab__task-overdue-icon': isOverdue(task) }" />
80
+ </button>
81
+ </template>
82
+ <template #subname>
83
+ {{ extractAssignee(task) }}
84
+ </template>
85
+ <template v-if="task.due" #details>
86
+ <span :class="{ 'cn-sidebar-tab__task-overdue-date': isOverdue(task) }">
87
+ {{ formatShortDate(task.due) }}
88
+ </span>
89
+ </template>
90
+ <template #actions>
91
+ <NcActionButton @click="startEdit(task)">
92
+ <template #icon>
93
+ <Pencil :size="20" />
94
+ </template>
95
+ {{ editLabel }}
96
+ </NcActionButton>
97
+ <NcActionButton v-if="task.status !== 'completed'" @click="completeTask(task)">
98
+ <template #icon>
99
+ <CheckboxMarkedOutline :size="20" />
100
+ </template>
101
+ {{ completeLabel }}
102
+ </NcActionButton>
103
+ <NcActionButton @click="deleteTask(task)">
104
+ <template #icon>
105
+ <Delete :size="20" />
106
+ </template>
107
+ {{ deleteLabel }}
108
+ </NcActionButton>
109
+ </template>
110
+ </NcListItem>
111
+ </div>
112
+ <NcButton
113
+ v-if="tasks.length < total"
114
+ type="tertiary"
115
+ :wide="true"
116
+ :disabled="loadingMore"
117
+ class="cn-sidebar-tab__load-more"
118
+ @click="loadMore">
119
+ <template v-if="loadingMore" #icon>
120
+ <NcLoadingIcon :size="20" />
121
+ </template>
122
+ {{ loadingMore ? '' : loadMoreLabel }}
123
+ </NcButton>
124
+ </div>
125
+ </template>
126
+
127
+ <script>
128
+ import { NcButton, NcTextField, NcListItem, NcActionButton, NcLoadingIcon, NcDateTimePickerNative, NcSelect } from '@nextcloud/vue'
129
+ import Plus from 'vue-material-design-icons/Plus.vue'
130
+ import Delete from 'vue-material-design-icons/Delete.vue'
131
+ import Pencil from 'vue-material-design-icons/Pencil.vue'
132
+ import Close from 'vue-material-design-icons/Close.vue'
133
+ import ContentSave from 'vue-material-design-icons/ContentSave.vue'
134
+ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'
135
+ import CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'
136
+ import { buildHeaders } from '../../utils/index.js'
137
+
138
+ export default {
139
+ name: 'CnTasksTab',
140
+
141
+ components: {
142
+ NcButton,
143
+ NcTextField,
144
+ NcListItem,
145
+ NcActionButton,
146
+ NcLoadingIcon,
147
+ NcDateTimePickerNative,
148
+ NcSelect,
149
+ Plus,
150
+ Delete,
151
+ Pencil,
152
+ Close,
153
+ ContentSave,
154
+ CheckboxMarkedOutline,
155
+ CheckboxBlankOutline,
156
+ },
157
+
158
+ props: {
159
+ objectId: { type: String, required: true },
160
+ register: { type: String, default: '' },
161
+ schema: { type: String, default: '' },
162
+ apiBase: { type: String, default: '/apps/openregister/api' },
163
+ addTaskPlaceholder: { type: String, default: 'Add task...' },
164
+ deadlineLabel: { type: String, default: 'Deadline' },
165
+ assigneeLabel: { type: String, default: 'Assignee' },
166
+ completeLabel: { type: String, default: 'Complete' },
167
+ editLabel: { type: String, default: 'Edit' },
168
+ deleteLabel: { type: String, default: 'Delete' },
169
+ noTasksLabel: { type: String, default: 'No linked tasks' },
170
+ loadMoreLabel: { type: String, default: 'Load more' },
171
+ statusFilterLabel: { type: String, default: 'Status' },
172
+ assigneeFilterLabel: { type: String, default: 'Assignee' },
173
+ },
174
+
175
+ data() {
176
+ return {
177
+ tasks: [],
178
+ loading: false,
179
+ loadingMore: false,
180
+ newTaskSummary: '',
181
+ newTaskDue: null,
182
+ newTaskAssignee: null,
183
+ saving: false,
184
+ editingTaskId: null,
185
+ userList: [],
186
+ page: 1,
187
+ total: 0,
188
+ limit: 20,
189
+ filterStatus: null,
190
+ filterAssignee: null,
191
+ }
192
+ },
193
+
194
+ computed: {
195
+ statusOptions() {
196
+ return [...new Set(this.tasks.map(t => t.status).filter(Boolean))]
197
+ },
198
+ assigneeOptions() {
199
+ return [...new Set(this.tasks.map(t => this.extractAssignee(t)).filter(Boolean))]
200
+ },
201
+ filteredTasks() {
202
+ let result = this.tasks
203
+ if (this.filterStatus) {
204
+ result = result.filter(t => t.status === this.filterStatus)
205
+ }
206
+ if (this.filterAssignee) {
207
+ result = result.filter(t => this.extractAssignee(t) === this.filterAssignee)
208
+ }
209
+ return result
210
+ },
211
+ },
212
+
213
+ watch: {
214
+ objectId: {
215
+ immediate: true,
216
+ handler(id) {
217
+ if (id) {
218
+ this.fetchTasks()
219
+ this.fetchUsers()
220
+ }
221
+ },
222
+ },
223
+ },
224
+
225
+ methods: {
226
+ async fetchTasks(append = false) {
227
+ if (!this.register || !this.schema) return
228
+ if (append) { this.loadingMore = true } else { this.loading = true }
229
+ try {
230
+ const params = new URLSearchParams({ limit: this.limit, _page: this.page })
231
+ const response = await fetch(
232
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tasks?${params}`,
233
+ { headers: buildHeaders() },
234
+ )
235
+ if (response.ok) {
236
+ const data = await response.json()
237
+ const results = data.results || data || []
238
+ this.tasks = append ? [...this.tasks, ...results] : results
239
+ this.total = data.total || this.tasks.length
240
+ }
241
+ } catch (err) {
242
+ console.error('CnTasksTab: Failed to fetch tasks', err)
243
+ } finally {
244
+ this.loading = false
245
+ this.loadingMore = false
246
+ }
247
+ },
248
+
249
+ loadMore() {
250
+ this.page++
251
+ this.fetchTasks(true)
252
+ },
253
+
254
+ isOverdue(task) {
255
+ if (!task.due || task.status === 'completed') return false
256
+ return new Date(task.due) < new Date()
257
+ },
258
+
259
+ async fetchUsers() {
260
+ try {
261
+ const response = await fetch('/ocs/v2.php/cloud/users/details?format=json&limit=50', {
262
+ headers: buildHeaders(),
263
+ })
264
+ if (response.ok) {
265
+ const data = await response.json()
266
+ const users = data.ocs?.data?.users || {}
267
+ this.userList = Object.entries(users).map(([id, user]) => ({
268
+ userId: id,
269
+ displayName: user.displayname || id,
270
+ }))
271
+ }
272
+ } catch (err) {
273
+ console.error('CnTasksTab: Failed to fetch users', err)
274
+ }
275
+ },
276
+
277
+ async addTask() {
278
+ if (!this.newTaskSummary.trim() || !this.register || !this.schema) return
279
+ this.saving = true
280
+ try {
281
+ const taskData = { summary: this.newTaskSummary.trim() }
282
+ if (this.newTaskDue) {
283
+ taskData.due = new Date(this.newTaskDue).toISOString()
284
+ }
285
+ if (this.newTaskAssignee) {
286
+ taskData.description = 'Assigned to: ' + this.newTaskAssignee.displayName
287
+ }
288
+ await fetch(
289
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tasks`,
290
+ {
291
+ method: 'POST',
292
+ headers: buildHeaders(),
293
+ body: JSON.stringify(taskData),
294
+ },
295
+ )
296
+ this.clearForm()
297
+ await this.fetchTasks()
298
+ } catch (err) {
299
+ console.error('CnTasksTab: Failed to add task', err)
300
+ } finally {
301
+ this.saving = false
302
+ }
303
+ },
304
+
305
+ startEdit(task) {
306
+ this.editingTaskId = task.id
307
+ this.newTaskSummary = task.summary || task.title || task.name || ''
308
+ this.newTaskDue = task.due ? new Date(task.due).toISOString().split('T')[0] : null
309
+ const assigneeName = this.extractAssignee(task)
310
+ this.newTaskAssignee = this.userList.find(u => u.displayName === assigneeName) || null
311
+ },
312
+
313
+ cancelEdit() {
314
+ this.editingTaskId = null
315
+ this.clearForm()
316
+ },
317
+
318
+ async saveEdit() {
319
+ if (!this.newTaskSummary.trim() || !this.editingTaskId) return
320
+ this.saving = true
321
+ try {
322
+ const taskData = { summary: this.newTaskSummary.trim() }
323
+ if (this.newTaskDue) {
324
+ taskData.due = new Date(this.newTaskDue).toISOString()
325
+ } else {
326
+ taskData.due = ''
327
+ }
328
+ if (this.newTaskAssignee) {
329
+ taskData.description = 'Assigned to: ' + this.newTaskAssignee.displayName
330
+ } else {
331
+ taskData.description = ''
332
+ }
333
+ await fetch(
334
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tasks/${encodeURIComponent(this.editingTaskId)}`,
335
+ {
336
+ method: 'PUT',
337
+ headers: buildHeaders(),
338
+ body: JSON.stringify(taskData),
339
+ },
340
+ )
341
+ this.editingTaskId = null
342
+ this.clearForm()
343
+ await this.fetchTasks()
344
+ } catch (err) {
345
+ console.error('CnTasksTab: Failed to update task', err)
346
+ } finally {
347
+ this.saving = false
348
+ }
349
+ },
350
+
351
+ clearForm() {
352
+ this.newTaskSummary = ''
353
+ this.newTaskDue = null
354
+ this.newTaskAssignee = null
355
+ },
356
+
357
+ async toggleTask(task) {
358
+ const newStatus = task.status === 'completed' ? 'NEEDS-ACTION' : 'COMPLETED'
359
+ try {
360
+ await fetch(
361
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tasks/${encodeURIComponent(task.id)}`,
362
+ {
363
+ method: 'PUT',
364
+ headers: buildHeaders(),
365
+ body: JSON.stringify({ status: newStatus }),
366
+ },
367
+ )
368
+ await this.fetchTasks()
369
+ } catch (err) {
370
+ console.error('CnTasksTab: Failed to toggle task', err)
371
+ }
372
+ },
373
+
374
+ async completeTask(task) {
375
+ try {
376
+ await fetch(
377
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tasks/${encodeURIComponent(task.id)}`,
378
+ {
379
+ method: 'PUT',
380
+ headers: buildHeaders(),
381
+ body: JSON.stringify({ status: 'COMPLETED' }),
382
+ },
383
+ )
384
+ await this.fetchTasks()
385
+ } catch (err) {
386
+ console.error('CnTasksTab: Failed to complete task', err)
387
+ }
388
+ },
389
+
390
+ async deleteTask(task) {
391
+ try {
392
+ await fetch(
393
+ `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/tasks/${encodeURIComponent(task.id)}`,
394
+ { method: 'DELETE', headers: buildHeaders() },
395
+ )
396
+ this.tasks = this.tasks.filter(t => t.id !== task.id)
397
+ } catch (err) {
398
+ console.error('CnTasksTab: Failed to delete task', err)
399
+ }
400
+ },
401
+
402
+ extractAssignee(task) {
403
+ if (task.description?.startsWith('Assigned to: ')) {
404
+ return task.description.replace('Assigned to: ', '')
405
+ }
406
+ return task.description || ''
407
+ },
408
+
409
+ formatShortDate(dateStr) {
410
+ if (!dateStr) return ''
411
+ try {
412
+ return new Date(dateStr).toLocaleDateString(undefined, {
413
+ day: 'numeric', month: 'short',
414
+ })
415
+ } catch { return dateStr }
416
+ },
417
+ },
418
+ }
419
+ </script>
420
+
421
+ <style scoped>
422
+ .cn-sidebar-tab {
423
+ padding: 12px;
424
+ overflow-x: hidden;
425
+ }
426
+
427
+ .cn-sidebar-tab__section {
428
+ margin-bottom: 12px;
429
+ }
430
+
431
+ .cn-sidebar-tab__section--filters {
432
+ padding-top: 12px;
433
+ border-top: 1px solid var(--color-border);
434
+ }
435
+
436
+ .cn-sidebar-tab__action--row {
437
+ display: flex;
438
+ gap: 8px;
439
+ align-items: flex-end;
440
+ margin-bottom: 8px;
441
+ }
442
+
443
+ .cn-sidebar-tab__grid {
444
+ display: grid;
445
+ grid-template-columns: 1fr 1fr;
446
+ gap: 8px;
447
+ }
448
+
449
+ .cn-sidebar-tab__grid > * {
450
+ min-width: 0;
451
+ }
452
+
453
+ .cn-sidebar-tab__empty {
454
+ text-align: center;
455
+ padding: 24px 12px;
456
+ color: var(--color-text-maxcontrast);
457
+ font-size: 13px;
458
+ }
459
+
460
+ .cn-sidebar-tab__list {
461
+ display: flex;
462
+ flex-direction: column;
463
+ gap: 2px;
464
+ }
465
+
466
+ .cn-sidebar-tab__load-more { margin-top: 8px; }
467
+
468
+ .cn-sidebar-tab__task-checkbox {
469
+ display: flex;
470
+ align-items: center;
471
+ justify-content: center;
472
+ background: none;
473
+ border: none;
474
+ padding: 0;
475
+ cursor: pointer;
476
+ color: inherit;
477
+ }
478
+
479
+ .cn-sidebar-tab__task-done { color: var(--color-success); }
480
+ .cn-sidebar-tab__task-overdue-icon { color: var(--color-error, #e53935); }
481
+ .cn-sidebar-tab__task-overdue-date { color: var(--color-error, #e53935); font-weight: 500; }
482
+ </style>
@@ -1 +1,6 @@
1
1
  export { default as CnObjectSidebar } from './CnObjectSidebar.vue'
2
+ export { default as CnFilesTab } from './CnFilesTab.vue'
3
+ export { default as CnNotesTab } from './CnNotesTab.vue'
4
+ export { default as CnTagsTab } from './CnTagsTab.vue'
5
+ export { default as CnTasksTab } from './CnTasksTab.vue'
6
+ export { default as CnAuditTrailTab } from './CnAuditTrailTab.vue'