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

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