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

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 (68) hide show
  1. package/dist/nextcloud-vue.cjs +67614 -0
  2. package/dist/nextcloud-vue.cjs.js +9554 -8980
  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 +9554 -8980
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +11 -4
  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/CnObjectSidebar/CnObjectSidebar.vue +876 -876
  43. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  44. package/src/components/CnPagination/CnPagination.vue +252 -252
  45. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -792
  46. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  47. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -226
  48. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -787
  49. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -305
  50. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -1398
  51. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -236
  52. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  53. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  54. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -420
  55. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  56. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -540
  57. package/src/components/CnTasksCard/CnTasksCard.vue +373 -373
  58. package/src/components/CnTileWidget/CnTileWidget.vue +159 -159
  59. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -292
  60. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -435
  61. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  62. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -180
  63. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -211
  64. package/src/index.js +1 -1
  65. package/src/types/notification.d.ts +13 -13
  66. package/src/types/organisation.d.ts +15 -15
  67. package/src/types/schema.d.ts +13 -13
  68. package/src/types/task.d.ts +6 -6
@@ -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>