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