@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.11

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 (208) hide show
  1. package/README.md +226 -0
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +79416 -7715
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +3583 -504
  6. package/dist/nextcloud-vue.esm.js +79343 -7692
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/l10n/en.json +164 -0
  9. package/l10n/nl.json +164 -0
  10. package/package.json +104 -63
  11. package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
  12. package/src/components/CnActionsBar/index.js +1 -0
  13. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +570 -0
  14. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  15. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  16. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +422 -0
  17. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  18. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  19. package/src/components/CnCard/CnCard.vue +415 -0
  20. package/src/components/CnCard/index.js +1 -0
  21. package/src/components/CnCardGrid/CnCardGrid.vue +156 -152
  22. package/src/components/CnCardGrid/index.js +1 -1
  23. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  24. package/src/components/CnCellRenderer/index.js +1 -1
  25. package/src/components/CnChartWidget/CnChartWidget.vue +346 -0
  26. package/src/components/CnChartWidget/index.js +1 -0
  27. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  28. package/src/components/CnConfigurationCard/index.js +1 -1
  29. package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
  30. package/src/components/CnContextMenu/index.js +1 -0
  31. package/src/components/CnCopyDialog/CnCopyDialog.vue +266 -0
  32. package/src/components/CnCopyDialog/index.js +1 -0
  33. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
  34. package/src/components/CnDashboardGrid/index.js +1 -0
  35. package/src/components/CnDashboardPage/CnDashboardPage.vue +397 -0
  36. package/src/components/CnDashboardPage/index.js +1 -0
  37. package/src/components/CnDataTable/CnDataTable.vue +362 -354
  38. package/src/components/CnDataTable/index.js +1 -1
  39. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
  40. package/src/components/CnDeleteDialog/index.js +1 -0
  41. package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
  42. package/src/components/CnDetailCard/index.js +1 -0
  43. package/src/components/CnDetailGrid/CnDetailGrid.vue +256 -0
  44. package/src/components/CnDetailGrid/index.js +1 -0
  45. package/src/components/CnDetailPage/CnDetailPage.vue +432 -0
  46. package/src/components/CnDetailPage/index.js +1 -0
  47. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +234 -223
  48. package/src/components/CnFacetSidebar/index.js +1 -1
  49. package/src/components/CnFilterBar/CnFilterBar.vue +153 -152
  50. package/src/components/CnFilterBar/index.js +1 -1
  51. package/src/components/CnFormDialog/CnFormDialog.vue +1047 -0
  52. package/src/components/CnFormDialog/index.js +1 -0
  53. package/src/components/CnIcon/CnIcon.vue +89 -0
  54. package/src/components/CnIcon/index.js +1 -0
  55. package/src/components/CnIndexPage/CnIndexPage.vue +980 -682
  56. package/src/components/CnIndexPage/index.js +1 -1
  57. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +536 -0
  58. package/src/components/CnIndexSidebar/index.js +1 -0
  59. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  60. package/src/components/CnInfoWidget/index.js +1 -0
  61. package/src/components/CnItemCard/CnItemCard.vue +134 -0
  62. package/src/components/CnItemCard/index.js +1 -0
  63. package/src/components/CnJsonViewer/CnJsonViewer.vue +312 -0
  64. package/src/components/CnJsonViewer/index.js +1 -0
  65. package/src/components/CnKpiGrid/CnKpiGrid.vue +93 -89
  66. package/src/components/CnKpiGrid/index.js +1 -1
  67. package/src/components/CnMassActionBar/CnMassActionBar.vue +161 -160
  68. package/src/components/CnMassActionBar/index.js +1 -1
  69. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +327 -320
  70. package/src/components/CnMassCopyDialog/index.js +1 -1
  71. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +245 -238
  72. package/src/components/CnMassDeleteDialog/index.js +1 -1
  73. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +191 -190
  74. package/src/components/CnMassExportDialog/index.js +1 -1
  75. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +494 -491
  76. package/src/components/CnMassImportDialog/index.js +1 -1
  77. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  78. package/src/components/CnNoteCard/index.js +1 -0
  79. package/src/components/CnNotesCard/CnNotesCard.vue +416 -0
  80. package/src/components/CnNotesCard/index.js +1 -0
  81. package/src/components/CnObjectCard/CnObjectCard.vue +294 -292
  82. package/src/components/CnObjectCard/index.js +1 -1
  83. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +854 -0
  84. package/src/components/CnObjectDataWidget/index.js +1 -0
  85. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +289 -0
  86. package/src/components/CnObjectMetadataWidget/index.js +1 -0
  87. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +369 -0
  88. package/src/components/CnObjectSidebar/CnFilesTab.vue +287 -0
  89. package/src/components/CnObjectSidebar/CnNotesTab.vue +250 -0
  90. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +255 -0
  91. package/src/components/CnObjectSidebar/CnTagsTab.vue +259 -0
  92. package/src/components/CnObjectSidebar/CnTasksTab.vue +483 -0
  93. package/src/components/CnObjectSidebar/index.js +6 -0
  94. package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
  95. package/src/components/CnPageHeader/index.js +1 -0
  96. package/src/components/CnPagination/CnPagination.vue +253 -252
  97. package/src/components/CnPagination/index.js +1 -1
  98. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  99. package/src/components/CnProgressBar/index.js +1 -0
  100. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +793 -0
  101. package/src/components/CnRegisterMapping/index.js +1 -0
  102. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  103. package/src/components/CnRowActions/index.js +1 -1
  104. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  105. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +788 -0
  106. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  107. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  108. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  109. package/src/components/CnSchemaFormDialog/index.js +1 -0
  110. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  111. package/src/components/CnSettingsCard/index.js +1 -1
  112. package/src/components/CnSettingsSection/CnSettingsSection.vue +267 -266
  113. package/src/components/CnSettingsSection/index.js +1 -1
  114. package/src/components/CnStatsBlock/CnStatsBlock.vue +437 -366
  115. package/src/components/CnStatsBlock/index.js +1 -1
  116. package/src/components/CnStatsPanel/CnStatsPanel.vue +321 -0
  117. package/src/components/CnStatsPanel/index.js +1 -0
  118. package/src/components/CnStatusBadge/CnStatusBadge.vue +90 -77
  119. package/src/components/CnStatusBadge/index.js +1 -1
  120. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +545 -0
  121. package/src/components/CnTabbedFormDialog/index.js +1 -0
  122. package/src/components/CnTableWidget/CnTableWidget.vue +333 -0
  123. package/src/components/CnTableWidget/index.js +1 -0
  124. package/src/components/CnTasksCard/CnTasksCard.vue +374 -0
  125. package/src/components/CnTasksCard/index.js +1 -0
  126. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  127. package/src/components/CnTileWidget/index.js +1 -0
  128. package/src/components/CnTimelineStages/CnTimelineStages.vue +294 -0
  129. package/src/components/CnTimelineStages/index.js +1 -0
  130. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +436 -0
  131. package/src/components/CnUserActionMenu/index.js +1 -0
  132. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +313 -312
  133. package/src/components/CnVersionInfoCard/index.js +1 -1
  134. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  135. package/src/components/CnWidgetRenderer/index.js +1 -0
  136. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +248 -0
  137. package/src/components/CnWidgetWrapper/index.js +1 -0
  138. package/src/components/index.js +57 -25
  139. package/src/composables/index.js +5 -3
  140. package/src/composables/useContextMenu.js +126 -0
  141. package/src/composables/useDashboardView.js +286 -0
  142. package/src/composables/useDetailView.js +290 -132
  143. package/src/composables/useListView.js +364 -153
  144. package/src/composables/useSubResource.js +142 -142
  145. package/src/constants/metadata.js +30 -0
  146. package/src/css/CnSchemaFormDialog.css +546 -0
  147. package/src/css/__sample_nextcloud_tokens.css +110 -0
  148. package/src/css/actions-bar.css +54 -0
  149. package/src/css/badge.css +83 -51
  150. package/src/css/card.css +129 -128
  151. package/src/css/context-menu.css +20 -0
  152. package/src/css/dashboard.css +70 -0
  153. package/src/css/detail-page.css +235 -0
  154. package/src/css/detail.css +68 -68
  155. package/src/css/index-page.css +44 -0
  156. package/src/css/index-sidebar.css +193 -0
  157. package/src/css/index.css +17 -8
  158. package/src/css/layout.css +90 -90
  159. package/src/css/page-header.css +35 -0
  160. package/src/css/pagination.css +72 -72
  161. package/src/css/table.css +142 -143
  162. package/src/css/timeline-stages.css +220 -0
  163. package/src/css/utilities.css +46 -46
  164. package/src/index.js +95 -50
  165. package/src/l10n/index.js +12 -0
  166. package/src/mixins/gridLayout.js +118 -0
  167. package/src/store/createCrudStore.d.ts +350 -0
  168. package/src/store/createCrudStore.js +413 -0
  169. package/src/store/createSubResourcePlugin.js +125 -135
  170. package/src/store/index.js +4 -3
  171. package/src/store/pluginMerge.js +55 -0
  172. package/src/store/plugins/auditTrails.js +357 -17
  173. package/src/store/plugins/files.js +250 -186
  174. package/src/store/plugins/index.js +8 -4
  175. package/src/store/plugins/lifecycle.js +180 -180
  176. package/src/store/plugins/logs.d.ts +22 -0
  177. package/src/store/plugins/logs.js +172 -0
  178. package/src/store/plugins/registerMapping.js +195 -0
  179. package/src/store/plugins/relations.js +68 -68
  180. package/src/store/plugins/search.js +385 -0
  181. package/src/store/plugins/selection.js +104 -0
  182. package/src/store/useObjectStore.js +793 -625
  183. package/src/types/auditTrail.d.ts +32 -32
  184. package/src/types/file.d.ts +23 -23
  185. package/src/types/index.d.ts +67 -35
  186. package/src/types/notification.d.ts +36 -36
  187. package/src/types/object.d.ts +40 -40
  188. package/src/types/organisation.d.ts +41 -41
  189. package/src/types/register.d.ts +25 -25
  190. package/src/types/schema.d.ts +39 -39
  191. package/src/types/shared.d.ts +79 -79
  192. package/src/types/source.d.ts +14 -14
  193. package/src/types/task.d.ts +31 -31
  194. package/src/utils/errors.js +96 -96
  195. package/src/utils/getTheme.js +9 -0
  196. package/src/utils/headers.js +80 -44
  197. package/src/utils/id.js +13 -0
  198. package/src/utils/index.js +4 -3
  199. package/src/utils/schema.js +423 -287
  200. package/src/utils/widgetVisibility.js +162 -0
  201. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
  202. package/src/components/CnDetailViewLayout/index.js +0 -1
  203. package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
  204. package/src/components/CnEmptyState/index.js +0 -1
  205. package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
  206. package/src/components/CnListViewLayout/index.js +0 -1
  207. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
  208. package/src/components/CnViewModeToggle/index.js +0 -1
@@ -0,0 +1,333 @@
1
+ <!--
2
+ CnTableWidget — Data table widget with card wrapper and dual data sourcing.
3
+
4
+ Wraps CnDataTable in a card container with a title header, optional "View all"
5
+ footer link, and loading/empty states. Supports two data modes:
6
+ 1. External: `rows` prop provided (no API calls)
7
+ 2. Self-fetch: `register` + `schemaId` provided (fetches from OpenRegister API)
8
+
9
+ Used in dashboard and detail page grid layouts for displaying related data tables.
10
+ -->
11
+ <template>
12
+ <div class="cn-table-widget">
13
+ <!-- Header -->
14
+ <div v-if="title" class="cn-table-widget__header">
15
+ <h3 class="cn-table-widget__title">
16
+ {{ title }}
17
+ </h3>
18
+ <span v-if="totalCount > 0" class="cn-table-widget__count">
19
+ {{ totalCount }}
20
+ </span>
21
+ </div>
22
+
23
+ <!-- Loading state -->
24
+ <NcLoadingIcon v-if="isLoading" class="cn-table-widget__loading" :size="32" />
25
+
26
+ <!-- Empty state -->
27
+ <p v-else-if="displayRows.length === 0" class="cn-table-widget__empty">
28
+ {{ emptyText }}
29
+ </p>
30
+
31
+ <!-- Data table -->
32
+ <CnDataTable
33
+ v-else
34
+ :rows="displayRows"
35
+ :columns="columns"
36
+ :loading="false"
37
+ :selectable="false"
38
+ @row-click="onRowClick" />
39
+
40
+ <!-- Footer with view all link -->
41
+ <div
42
+ v-if="viewAllRoute && totalCount > limitedCount"
43
+ class="cn-table-widget__footer">
44
+ <a
45
+ class="cn-table-widget__view-all"
46
+ @click.prevent="$router.push(viewAllRoute)">
47
+ {{ viewAllLabel }}
48
+ </a>
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+ <script>
54
+ import { translate as t } from '@nextcloud/l10n'
55
+ import { NcLoadingIcon } from '@nextcloud/vue'
56
+ import CnDataTable from '../CnDataTable/CnDataTable.vue'
57
+
58
+ /**
59
+ * CnTableWidget — Data table widget with card wrapper and dual data sourcing.
60
+ *
61
+ * @example External data mode
62
+ * <CnTableWidget
63
+ * title="Related Skills"
64
+ * :rows="skillRows"
65
+ * :columns="skillColumns"
66
+ * :view-all-route="{ name: 'Skills' }" />
67
+ *
68
+ * @example Self-fetch mode
69
+ * <CnTableWidget
70
+ * title="Documents"
71
+ * register="9"
72
+ * schema-id="42"
73
+ * :limit="5" />
74
+ */
75
+ export default {
76
+ name: 'CnTableWidget',
77
+
78
+ components: {
79
+ NcLoadingIcon,
80
+ CnDataTable,
81
+ },
82
+
83
+ props: {
84
+ /** Widget title shown in the header. */
85
+ title: {
86
+ type: String,
87
+ default: '',
88
+ },
89
+ /**
90
+ * External row data. When provided, no API calls are made.
91
+ *
92
+ * @type {object[]}
93
+ */
94
+ rows: {
95
+ type: Array,
96
+ default: null,
97
+ },
98
+ /**
99
+ * Column definitions for CnDataTable.
100
+ *
101
+ * @type {{ key: string, label: string, sortable?: boolean }[]}
102
+ */
103
+ columns: {
104
+ type: Array,
105
+ default: () => [],
106
+ },
107
+ /**
108
+ * OpenRegister register ID for self-fetch mode.
109
+ *
110
+ * @type {string|number}
111
+ */
112
+ register: {
113
+ type: [String, Number],
114
+ default: null,
115
+ },
116
+ /**
117
+ * OpenRegister schema ID for self-fetch mode.
118
+ *
119
+ * @type {string|number}
120
+ */
121
+ schemaId: {
122
+ type: [String, Number],
123
+ default: null,
124
+ },
125
+ /**
126
+ * Maximum number of rows to display. When total exceeds this,
127
+ * a "View all" link appears.
128
+ *
129
+ * @type {number}
130
+ */
131
+ limit: {
132
+ type: Number,
133
+ default: 0,
134
+ },
135
+ /**
136
+ * Vue Router route for the "View all" link.
137
+ *
138
+ * @type {object}
139
+ */
140
+ viewAllRoute: {
141
+ type: Object,
142
+ default: null,
143
+ },
144
+ /**
145
+ * Function that returns a route object for row click navigation.
146
+ * Receives the row data as argument.
147
+ *
148
+ * @type {Function}
149
+ */
150
+ rowClickRoute: {
151
+ type: Function,
152
+ default: null,
153
+ },
154
+ /** Pre-translated "View all" label. */
155
+ viewAllLabel: {
156
+ type: String,
157
+ default: () => t('nextcloud-vue', 'View all'),
158
+ },
159
+ /** Pre-translated empty state text. */
160
+ emptyText: {
161
+ type: String,
162
+ default: () => t('nextcloud-vue', 'No data available'),
163
+ },
164
+ },
165
+
166
+ data() {
167
+ return {
168
+ fetchedRows: [],
169
+ loading: false,
170
+ }
171
+ },
172
+
173
+ computed: {
174
+ /**
175
+ * Whether data is currently loading.
176
+ *
177
+ * @return {boolean}
178
+ */
179
+ isLoading() {
180
+ return this.loading
181
+ },
182
+
183
+ /**
184
+ * All rows (external or fetched).
185
+ *
186
+ * @return {object[]}
187
+ */
188
+ allRows() {
189
+ return this.rows || this.fetchedRows
190
+ },
191
+
192
+ /**
193
+ * Rows limited to the configured limit.
194
+ *
195
+ * @return {object[]}
196
+ */
197
+ displayRows() {
198
+ if (this.limit > 0) {
199
+ return this.allRows.slice(0, this.limit)
200
+ }
201
+ return this.allRows
202
+ },
203
+
204
+ /**
205
+ * Total count of all rows (before limiting).
206
+ *
207
+ * @return {number}
208
+ */
209
+ totalCount() {
210
+ return this.allRows.length
211
+ },
212
+
213
+ /**
214
+ * Count of displayed rows (after limiting).
215
+ *
216
+ * @return {number}
217
+ */
218
+ limitedCount() {
219
+ return this.displayRows.length
220
+ },
221
+ },
222
+
223
+ mounted() {
224
+ if (this.rows === null && this.register && this.schemaId) {
225
+ this.fetchData()
226
+ }
227
+ },
228
+
229
+ methods: {
230
+ /**
231
+ * Fetch data from the OpenRegister API.
232
+ *
233
+ * @return {Promise<void>}
234
+ */
235
+ async fetchData() {
236
+ this.loading = true
237
+ try {
238
+ const url = `/index.php/apps/openregister/api/objects/${this.register}/${this.schemaId}`
239
+ const response = await fetch(url, {
240
+ headers: {
241
+ 'Content-Type': 'application/json',
242
+ 'OCS-APIREQUEST': 'true',
243
+ },
244
+ })
245
+ if (response.ok) {
246
+ const data = await response.json()
247
+ this.fetchedRows = data.results || data || []
248
+ }
249
+ } catch (error) {
250
+ console.error('CnTableWidget: Failed to fetch data', error)
251
+ } finally {
252
+ this.loading = false
253
+ }
254
+ },
255
+
256
+ /**
257
+ * Handle row click events. Navigates if rowClickRoute is configured.
258
+ *
259
+ * @param {object} row - The clicked row data.
260
+ */
261
+ onRowClick(row) {
262
+ if (this.rowClickRoute) {
263
+ const route = this.rowClickRoute(row)
264
+ if (route) {
265
+ this.$router.push(route)
266
+ }
267
+ }
268
+ },
269
+ },
270
+ }
271
+ </script>
272
+
273
+ <style scoped>
274
+ .cn-table-widget {
275
+ background: var(--color-main-background);
276
+ border: 1px solid var(--color-border);
277
+ border-radius: var(--border-radius-large, 16px);
278
+ overflow: hidden;
279
+ }
280
+
281
+ .cn-table-widget__header {
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: space-between;
285
+ padding: 12px 16px;
286
+ border-bottom: 1px solid var(--color-border);
287
+ }
288
+
289
+ .cn-table-widget__title {
290
+ margin: 0;
291
+ font-size: 14px;
292
+ font-weight: 600;
293
+ }
294
+
295
+ .cn-table-widget__count {
296
+ font-size: 12px;
297
+ color: var(--color-text-maxcontrast);
298
+ background: var(--color-background-dark);
299
+ padding: 2px 8px;
300
+ border-radius: 10px;
301
+ }
302
+
303
+ .cn-table-widget__loading {
304
+ padding: 32px 0;
305
+ display: flex;
306
+ justify-content: center;
307
+ }
308
+
309
+ .cn-table-widget__empty {
310
+ padding: 24px 16px;
311
+ text-align: center;
312
+ color: var(--color-text-maxcontrast);
313
+ font-size: 14px;
314
+ margin: 0;
315
+ }
316
+
317
+ .cn-table-widget__footer {
318
+ padding: 8px 16px;
319
+ border-top: 1px solid var(--color-border);
320
+ text-align: center;
321
+ }
322
+
323
+ .cn-table-widget__view-all {
324
+ font-size: 13px;
325
+ color: var(--color-primary-element);
326
+ cursor: pointer;
327
+ text-decoration: none;
328
+ }
329
+
330
+ .cn-table-widget__view-all:hover {
331
+ text-decoration: underline;
332
+ }
333
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnTableWidget } from './CnTableWidget.vue'
@@ -0,0 +1,374 @@
1
+ <!--
2
+ CnTasksCard — Inline tasks card for detail pages.
3
+
4
+ Displays up to 5 tasks with status indicators, assignee, and due date.
5
+ Integrates CnUserActionMenu on assignee names. Highlights overdue tasks.
6
+ Wraps CnDetailCard for consistent styling.
7
+ -->
8
+ <template>
9
+ <CnDetailCard :title="titleLabel" :icon="CheckboxMarkedOutline" :collapsible="collapsible">
10
+ <div class="cn-tasks-card">
11
+ <!-- Loading state -->
12
+ <NcLoadingIcon v-if="loading" />
13
+
14
+ <!-- Empty state -->
15
+ <div v-else-if="allTasks.length === 0" class="cn-tasks-card__empty">
16
+ {{ noTasksLabel }}
17
+ </div>
18
+
19
+ <!-- Tasks list -->
20
+ <div v-else class="cn-tasks-card__list">
21
+ <div
22
+ v-for="task in displayedTasks"
23
+ :key="task.id"
24
+ class="cn-tasks-card__task">
25
+ <!-- Status icon -->
26
+ <div class="cn-tasks-card__status-icon">
27
+ <CheckboxMarkedOutline
28
+ v-if="task.status === 'completed'"
29
+ :size="20"
30
+ class="cn-tasks-card__icon--completed" />
31
+ <ProgressClock
32
+ v-else-if="task.status === 'active' || task.status === 'in-process'"
33
+ :size="20"
34
+ class="cn-tasks-card__icon--active" />
35
+ <CloseCircleOutline
36
+ v-else-if="task.status === 'terminated'"
37
+ :size="20"
38
+ class="cn-tasks-card__icon--terminated" />
39
+ <CheckboxBlankOutline
40
+ v-else
41
+ :size="20"
42
+ class="cn-tasks-card__icon--available" />
43
+ </div>
44
+
45
+ <!-- Task content -->
46
+ <div class="cn-tasks-card__content">
47
+ <span class="cn-tasks-card__title">{{ task.title || task.name }}</span>
48
+ <div class="cn-tasks-card__meta">
49
+ <!-- Assignee -->
50
+ <span v-if="hasAssignee(task)" class="cn-tasks-card__assignee">
51
+ <CnUserActionMenu
52
+ v-if="!isCurrentUser(task.assignee)"
53
+ :user-id="task.assignee"
54
+ :display-name="task.assignee">
55
+ <span class="cn-tasks-card__assignee-name">{{ task.assignee }}</span>
56
+ </CnUserActionMenu>
57
+ <span v-else class="cn-tasks-card__assignee-name cn-tasks-card__assignee-name--self">
58
+ {{ task.assignee }}
59
+ </span>
60
+ </span>
61
+ <span v-else class="cn-tasks-card__unassigned">
62
+ {{ unassignedLabel }}
63
+ </span>
64
+
65
+ <!-- Due date -->
66
+ <span
67
+ v-if="task.dueDate"
68
+ class="cn-tasks-card__due-date"
69
+ :class="{ 'cn-tasks-card__due-date--overdue': isOverdue(task) }">
70
+ {{ formatDate(task.dueDate) }}
71
+ </span>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Footer: "Show all" link -->
79
+ <template v-if="allTasks.length > maxDisplay" #footer>
80
+ <button
81
+ class="cn-tasks-card__show-all"
82
+ @click="$emit('show-all')">
83
+ {{ showAllLabel }} ({{ allTasks.length }})
84
+ </button>
85
+ </template>
86
+ </CnDetailCard>
87
+ </template>
88
+
89
+ <script>
90
+ import { translate as t } from '@nextcloud/l10n'
91
+ import { NcLoadingIcon } from '@nextcloud/vue'
92
+ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'
93
+ import CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'
94
+ import ProgressClock from 'vue-material-design-icons/ProgressClock.vue'
95
+ import CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'
96
+
97
+ import CnDetailCard from '../CnDetailCard/CnDetailCard.vue'
98
+ import CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'
99
+ import { buildHeaders } from '../../utils/index.js'
100
+
101
+ /**
102
+ * CnTasksCard — Inline tasks widget for detail pages.
103
+ *
104
+ * Shows up to 5 tasks sorted by due date with status indicators.
105
+ * Highlights overdue tasks and integrates CnUserActionMenu on assignees.
106
+ *
107
+ * @example Basic usage
108
+ * <CnTasksCard
109
+ * register-id="uuid-register"
110
+ * schema-id="uuid-schema"
111
+ * object-id="uuid-object" />
112
+ *
113
+ * @example With sidebar sync
114
+ * <CnTasksCard
115
+ * register-id="reg"
116
+ * schema-id="schema"
117
+ * object-id="obj"
118
+ * @show-all="openSidebarTasksTab" />
119
+ */
120
+ export default {
121
+ name: 'CnTasksCard',
122
+
123
+ components: {
124
+ CnDetailCard,
125
+ CnUserActionMenu,
126
+ NcLoadingIcon,
127
+ CheckboxMarkedOutline,
128
+ CheckboxBlankOutline,
129
+ ProgressClock,
130
+ CloseCircleOutline,
131
+ },
132
+
133
+ props: {
134
+ /** OpenRegister register ID */
135
+ registerId: {
136
+ type: String,
137
+ required: true,
138
+ },
139
+ /** OpenRegister schema ID */
140
+ schemaId: {
141
+ type: String,
142
+ required: true,
143
+ },
144
+ /** Object UUID */
145
+ objectId: {
146
+ type: String,
147
+ required: true,
148
+ },
149
+ /** Base API URL for OpenRegister */
150
+ apiBase: {
151
+ type: String,
152
+ default: '/apps/openregister/api',
153
+ },
154
+ /** Maximum number of tasks to display */
155
+ maxDisplay: {
156
+ type: Number,
157
+ default: 5,
158
+ },
159
+ /** Whether the card is collapsible */
160
+ collapsible: {
161
+ type: Boolean,
162
+ default: false,
163
+ },
164
+
165
+ // --- Pre-translated labels ---
166
+ titleLabel: { type: String, default: () => t('nextcloud-vue', 'Tasks') },
167
+ noTasksLabel: { type: String, default: () => t('nextcloud-vue', 'No tasks') },
168
+ showAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },
169
+ unassignedLabel: { type: String, default: () => t('nextcloud-vue', 'Unassigned') },
170
+ },
171
+
172
+ emits: ['show-all'],
173
+
174
+ data() {
175
+ return {
176
+ CheckboxMarkedOutline,
177
+ allTasks: [],
178
+ loading: false,
179
+ }
180
+ },
181
+
182
+ computed: {
183
+ displayedTasks() {
184
+ // Sort by due date (soonest first), then limit
185
+ const sorted = [...this.allTasks].sort((a, b) => {
186
+ const dateA = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31')
187
+ const dateB = b.dueDate ? new Date(b.dueDate) : new Date('9999-12-31')
188
+ return dateA - dateB
189
+ })
190
+ return sorted.slice(0, this.maxDisplay)
191
+ },
192
+ },
193
+
194
+ watch: {
195
+ objectId: {
196
+ immediate: true,
197
+ handler(newId) {
198
+ if (newId && this.registerId && this.schemaId) {
199
+ this.fetchTasks()
200
+ }
201
+ },
202
+ },
203
+ },
204
+
205
+ methods: {
206
+ hasAssignee(task) {
207
+ return task.assignee && task.assignee.trim() !== ''
208
+ },
209
+
210
+ isCurrentUser(userId) {
211
+ const currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null
212
+ return userId === currentUser
213
+ },
214
+
215
+ isOverdue(task) {
216
+ if (!task.dueDate || task.status === 'completed') return false
217
+ try {
218
+ return new Date(task.dueDate) < new Date()
219
+ } catch {
220
+ return false
221
+ }
222
+ },
223
+
224
+ async fetchTasks() {
225
+ if (!this.registerId || !this.schemaId || !this.objectId) return
226
+ this.loading = true
227
+ try {
228
+ const url = `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/tasks`
229
+ const response = await fetch(url, { headers: buildHeaders() })
230
+ if (response.ok) {
231
+ const data = await response.json()
232
+ this.allTasks = data.results || data || []
233
+ }
234
+ } catch (err) {
235
+ console.error('CnTasksCard: Failed to fetch tasks', err)
236
+ } finally {
237
+ this.loading = false
238
+ }
239
+ },
240
+
241
+ formatDate(dateStr) {
242
+ if (!dateStr) return ''
243
+ try {
244
+ return new Date(dateStr).toLocaleDateString(undefined, {
245
+ year: 'numeric',
246
+ month: 'short',
247
+ day: 'numeric',
248
+ })
249
+ } catch {
250
+ return dateStr
251
+ }
252
+ },
253
+ },
254
+ }
255
+ </script>
256
+
257
+ <style scoped>
258
+ .cn-tasks-card__empty {
259
+ text-align: center;
260
+ padding: 16px 12px;
261
+ color: var(--color-text-maxcontrast);
262
+ font-size: 13px;
263
+ }
264
+
265
+ .cn-tasks-card__list {
266
+ display: flex;
267
+ flex-direction: column;
268
+ }
269
+
270
+ .cn-tasks-card__task {
271
+ display: flex;
272
+ align-items: flex-start;
273
+ gap: 10px;
274
+ padding: 8px 0;
275
+ border-bottom: 1px solid var(--color-border);
276
+ }
277
+
278
+ .cn-tasks-card__task:last-child {
279
+ border-bottom: none;
280
+ }
281
+
282
+ .cn-tasks-card__status-icon {
283
+ flex-shrink: 0;
284
+ padding-top: 1px;
285
+ }
286
+
287
+ .cn-tasks-card__icon--completed {
288
+ color: var(--color-success);
289
+ }
290
+
291
+ .cn-tasks-card__icon--active {
292
+ color: var(--color-primary-element);
293
+ }
294
+
295
+ .cn-tasks-card__icon--terminated {
296
+ color: var(--color-error);
297
+ }
298
+
299
+ .cn-tasks-card__icon--available {
300
+ color: var(--color-text-maxcontrast);
301
+ }
302
+
303
+ .cn-tasks-card__content {
304
+ flex: 1;
305
+ min-width: 0;
306
+ }
307
+
308
+ .cn-tasks-card__title {
309
+ display: block;
310
+ font-size: 13px;
311
+ font-weight: 500;
312
+ white-space: nowrap;
313
+ overflow: hidden;
314
+ text-overflow: ellipsis;
315
+ }
316
+
317
+ .cn-tasks-card__meta {
318
+ display: flex;
319
+ align-items: center;
320
+ gap: 8px;
321
+ margin-top: 2px;
322
+ font-size: 12px;
323
+ color: var(--color-text-maxcontrast);
324
+ }
325
+
326
+ .cn-tasks-card__assignee-name {
327
+ color: var(--color-primary-element);
328
+ font-weight: 500;
329
+ cursor: pointer;
330
+ }
331
+
332
+ .cn-tasks-card__assignee-name:hover {
333
+ text-decoration: underline;
334
+ }
335
+
336
+ .cn-tasks-card__assignee-name--self {
337
+ color: var(--color-text-maxcontrast);
338
+ cursor: default;
339
+ }
340
+
341
+ .cn-tasks-card__assignee-name--self:hover {
342
+ text-decoration: none;
343
+ }
344
+
345
+ .cn-tasks-card__unassigned {
346
+ font-style: italic;
347
+ color: var(--color-text-maxcontrast);
348
+ }
349
+
350
+ .cn-tasks-card__due-date {
351
+ white-space: nowrap;
352
+ }
353
+
354
+ .cn-tasks-card__due-date--overdue {
355
+ color: var(--color-error);
356
+ font-weight: 500;
357
+ }
358
+
359
+ .cn-tasks-card__show-all {
360
+ background: none;
361
+ border: none;
362
+ color: var(--color-primary-element);
363
+ font-size: 13px;
364
+ font-weight: 500;
365
+ cursor: pointer;
366
+ padding: 0;
367
+ width: 100%;
368
+ text-align: center;
369
+ }
370
+
371
+ .cn-tasks-card__show-all:hover {
372
+ text-decoration: underline;
373
+ }
374
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnTasksCard } from './CnTasksCard.vue'