@conduction/nextcloud-vue 0.1.0-beta.4 → 0.1.0-beta.6

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 (71) hide show
  1. package/dist/nextcloud-vue.cjs +67614 -0
  2. package/dist/nextcloud-vue.cjs.js +9559 -8983
  3. package/dist/nextcloud-vue.cjs.js.map +1 -1
  4. package/dist/nextcloud-vue.cjs.map +1 -0
  5. package/dist/nextcloud-vue.css +1231 -1231
  6. package/dist/nextcloud-vue.esm.js +9559 -8983
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +14 -5
  9. package/src/components/CnActionsBar/CnActionsBar.vue +235 -235
  10. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -579
  11. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -217
  12. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -121
  13. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -418
  14. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -247
  15. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  16. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  17. package/src/components/CnChartWidget/CnChartWidget.vue +320 -320
  18. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  19. package/src/components/CnCopyDialog/CnCopyDialog.vue +250 -250
  20. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -225
  21. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -390
  22. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  23. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +170 -170
  24. package/src/components/CnDetailCard/CnDetailCard.vue +214 -214
  25. package/src/components/CnDetailPage/CnDetailPage.vue +285 -281
  26. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -231
  27. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  28. package/src/components/CnFormDialog/CnFormDialog.vue +302 -11
  29. package/src/components/CnIcon/CnIcon.vue +89 -89
  30. package/src/components/CnIndexPage/CnIndexPage.vue +884 -874
  31. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -503
  32. package/src/components/CnItemCard/CnItemCard.vue +132 -132
  33. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  34. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  35. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  36. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  37. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  38. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  39. package/src/components/CnNoteCard/CnNoteCard.vue +149 -149
  40. package/src/components/CnNotesCard/CnNotesCard.vue +413 -413
  41. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  42. package/src/components/CnObjectCard/eslint-setup.md +235 -0
  43. package/src/components/CnObjectCard/package.json-or.json +132 -0
  44. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -876
  45. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  46. package/src/components/CnPagination/CnPagination.vue +252 -252
  47. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -792
  48. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  49. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -226
  50. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -787
  51. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -305
  52. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -1398
  53. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -236
  54. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  55. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  56. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -420
  57. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  58. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -540
  59. package/src/components/CnTasksCard/CnTasksCard.vue +373 -373
  60. package/src/components/CnTileWidget/CnTileWidget.vue +159 -159
  61. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -292
  62. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -435
  63. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  64. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -180
  65. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -211
  66. package/src/index.js +1 -1
  67. package/src/types/notification.d.ts +13 -13
  68. package/src/types/organisation.d.ts +15 -15
  69. package/src/types/schema.d.ts +13 -13
  70. package/src/types/task.d.ts +6 -6
  71. package/src/utils/headers.js +5 -3
@@ -1,373 +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>
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>