@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
@@ -1,132 +1,290 @@
1
- import { ref } from 'vue'
2
-
3
- /**
4
- * Composable for managing detail view state: loading, editing, deleting.
5
- *
6
- * Extracts the load/edit/delete pattern found in every detail view
7
- * across Pipelinq and Procest.
8
- *
9
- * @param {object} options Configuration options
10
- * @param {string} options.objectType The registered object type slug
11
- * @param {Function} options.fetchFn (type, id) => Promise<object>
12
- * @param {Function} options.saveFn (type, data) => Promise<object>
13
- * @param {Function} options.deleteFn (type, id) => Promise<boolean>
14
- * @param {Function} [options.onSaved] Callback after successful save
15
- * @param {Function} [options.onDeleted] Callback after successful delete
16
- * @return {object} Reactive state and methods
17
- *
18
- * @example
19
- * import { useDetailView } from '@conduction/nextcloud-vue'
20
- *
21
- * const { objectData, editing, loading, showDeleteDialog, load, save, confirmDelete, executeDelete } = useDetailView({
22
- * objectType: 'client',
23
- * fetchFn: (type, id) => objectStore.fetchObject(type, id),
24
- * saveFn: (type, data) => objectStore.saveObject(type, data),
25
- * deleteFn: (type, id) => objectStore.deleteObject(type, id),
26
- * onSaved: (result) => router.push(`/clients/${result.id}`),
27
- * onDeleted: () => router.push('/clients'),
28
- * })
29
- */
30
- export function useDetailView(options) {
31
- const objectData = ref({})
32
- const editing = ref(false)
33
- const loading = ref(false)
34
- const saving = ref(false)
35
- const showDeleteDialog = ref(false)
36
- const error = ref(null)
37
-
38
- /**
39
- * Load an object by ID.
40
- * @param {string} id Object ID
41
- * @return {Promise<object|null>}
42
- */
43
- async function load(id) {
44
- loading.value = true
45
- error.value = null
46
- try {
47
- const result = await options.fetchFn(options.objectType, id)
48
- if (result) {
49
- objectData.value = { ...result }
50
- }
51
- return result
52
- } catch (err) {
53
- error.value = err.message || 'Failed to load'
54
- return null
55
- } finally {
56
- loading.value = false
57
- }
58
- }
59
-
60
- /**
61
- * Save the current object data (create or update).
62
- * @param {object} [formData] Optional form data override (defaults to objectData)
63
- * @return {Promise<object|null>}
64
- */
65
- async function save(formData) {
66
- saving.value = true
67
- error.value = null
68
- try {
69
- const data = formData || objectData.value
70
- const result = await options.saveFn(options.objectType, data)
71
- if (result) {
72
- objectData.value = { ...result }
73
- editing.value = false
74
- if (options.onSaved) {
75
- options.onSaved(result)
76
- }
77
- }
78
- return result
79
- } catch (err) {
80
- error.value = err.message || 'Failed to save'
81
- return null
82
- } finally {
83
- saving.value = false
84
- }
85
- }
86
-
87
- /**
88
- * Show the delete confirmation dialog.
89
- */
90
- function confirmDelete() {
91
- showDeleteDialog.value = true
92
- }
93
-
94
- /**
95
- * Execute the delete operation.
96
- * @param {string} [id] Object ID (defaults to objectData.id)
97
- * @return {Promise<boolean>}
98
- */
99
- async function executeDelete(id) {
100
- const deleteId = id || objectData.value.id
101
- loading.value = true
102
- error.value = null
103
- try {
104
- const success = await options.deleteFn(options.objectType, deleteId)
105
- if (success) {
106
- showDeleteDialog.value = false
107
- if (options.onDeleted) {
108
- options.onDeleted()
109
- }
110
- }
111
- return success
112
- } catch (err) {
113
- error.value = err.message || 'Failed to delete'
114
- return false
115
- } finally {
116
- loading.value = false
117
- }
118
- }
119
-
120
- return {
121
- objectData,
122
- editing,
123
- loading,
124
- saving,
125
- showDeleteDialog,
126
- error,
127
- load,
128
- save,
129
- confirmDelete,
130
- executeDelete,
131
- }
132
- }
1
+ import { ref, computed, isRef, watch, onMounted } from 'vue'
2
+ import { useObjectStore } from '../store/index.js'
3
+
4
+ /**
5
+ * Composable for managing detail view state with full objectStore integration.
6
+ *
7
+ * When called with `objectType` and `id`, connects to the objectStore and handles
8
+ * fetching on mount, re-fetching on `id` changes, save/delete operations, and
9
+ * optional router navigation eliminating boilerplate from every detail-view component.
10
+ *
11
+ * Backward-compatible: existing `useDetailView(options)` and `useDetailView()` calls
12
+ * continue to work without modification.
13
+ *
14
+ * @param {string|object} [objectTypeOrOptions] Object type slug (new API) or legacy options object
15
+ * @param {string|import('vue').Ref<string>} [id] Object ID or `'new'` for a new object
16
+ * @param {object} [options] Options (new API only)
17
+ * @param {Function} [options.objectStore] Custom object store instance (from createObjectStore)
18
+ * @param {object|null} [options.router] Vue Router instance — enables post-save/delete navigation
19
+ * @param {string|null} [options.listRouteName] Route name to navigate to after successful delete
20
+ * @param {string|null} [options.detailRouteName] Route name to navigate to after successful create
21
+ * @param {string} [options.nameField] Field shown in error messages
22
+ * @return {object} Reactive state and operation functions
23
+ *
24
+ * @example
25
+ * // New API
26
+ * const { object, isNew, loading, saving, editing,
27
+ * onSave, confirmDelete, showDeleteDialog } = useDetailView('client', props.id, {
28
+ * router: useRouter(),
29
+ * listRouteName: 'ClientList',
30
+ * detailRouteName: 'ClientDetail',
31
+ * })
32
+ *
33
+ * @example
34
+ * // Legacy API — still works
35
+ * const { objectData, editing, load, save, confirmDelete } = useDetailView({
36
+ * objectType: 'client',
37
+ * fetchFn: (type, id) => objectStore.fetchObject(type, id),
38
+ * saveFn: (type, data) => objectStore.saveObject(type, data),
39
+ * deleteFn: (type, id) => objectStore.deleteObject(type, id),
40
+ * })
41
+ */
42
+ export function useDetailView(objectTypeOrOptions, id, options) {
43
+ // Backward compat: if first arg is an object or absent, delegate to legacy implementation
44
+ if (!objectTypeOrOptions || typeof objectTypeOrOptions === 'object') {
45
+ return useLegacyDetailView(objectTypeOrOptions || {})
46
+ }
47
+
48
+ // ── New API ──────────────────────────────────────────────────────────
49
+ const objectType = objectTypeOrOptions
50
+ const opts = options || {}
51
+ const router = opts.router || null
52
+ const listRouteName = opts.listRouteName || null
53
+ const detailRouteName = opts.detailRouteName || null
54
+
55
+ // Normalise `id` to a ref so we can watch it
56
+ const idRef = isRef(id) ? id : ref(id)
57
+
58
+ const objectStore = opts.objectStore ? opts.objectStore() : useObjectStore()
59
+
60
+ // ── State refs ───────────────────────────────────────────────────────
61
+ const editing = ref(false)
62
+ const saving = ref(false)
63
+ const showDeleteDialog = ref(false)
64
+ const error = ref(null)
65
+ const validationErrors = ref(null)
66
+
67
+ // ── Computed refs from the store ─────────────────────────────────────
68
+
69
+ const isNew = computed(() => !idRef.value || idRef.value === 'new')
70
+
71
+ const object = computed(() => {
72
+ if (isNew.value) return {}
73
+ return objectStore.getObject(objectType, idRef.value) || {}
74
+ })
75
+
76
+ const loading = computed(() => objectStore.loading[objectType] || false)
77
+
78
+ // ── Operations ───────────────────────────────────────────────────────
79
+
80
+ /**
81
+ * Save the object. Handles create (POST) and update (PUT) branches.
82
+ * On 422 validation error the `validationErrors` ref is populated.
83
+ * On successful create with `detailRouteName` set, the router navigates to the detail route.
84
+ * On successful update, `editing` is set to false and the object is re-fetched.
85
+ *
86
+ * @param {object} formData Data to save
87
+ * @return {Promise<object|null>} Saved object or null on error
88
+ */
89
+ async function onSave(formData) {
90
+ saving.value = true
91
+ error.value = null
92
+ validationErrors.value = null
93
+
94
+ try {
95
+ const dataToSave = isNew.value ? formData : { ...formData, id: idRef.value }
96
+ const result = await objectStore.saveObject(objectType, dataToSave)
97
+
98
+ if (!result) {
99
+ // Store already set error; surface it
100
+ error.value = objectStore.errors[objectType]?.message || 'Failed to save'
101
+ return null
102
+ }
103
+
104
+ if (isNew.value && router && detailRouteName) {
105
+ router.push({ name: detailRouteName, params: { id: result.id } })
106
+ } else {
107
+ editing.value = false
108
+ await objectStore.fetchObject(objectType, idRef.value)
109
+ }
110
+
111
+ return result
112
+ } catch (err) {
113
+ if (err?.response?.status === 422 || err?.status === 422) {
114
+ const data = err?.response?.data || err?.data || {}
115
+ validationErrors.value = data.errors || data
116
+ } else {
117
+ error.value = err.message || 'Failed to save'
118
+ }
119
+ return null
120
+ } finally {
121
+ saving.value = false
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Delete the current object. On success, navigate to `listRouteName` if configured.
127
+ * On failure, `error` ref is set.
128
+ *
129
+ * @return {Promise<boolean>} True if deleted successfully
130
+ */
131
+ async function confirmDelete() {
132
+ error.value = null
133
+ try {
134
+ const success = await objectStore.deleteObject(objectType, idRef.value)
135
+ if (success) {
136
+ showDeleteDialog.value = false
137
+ if (router && listRouteName) {
138
+ router.push({ name: listRouteName })
139
+ }
140
+ } else {
141
+ error.value = objectStore.errors[objectType]?.message || 'Failed to delete'
142
+ }
143
+ return success
144
+ } catch (err) {
145
+ error.value = err.message || 'Failed to delete'
146
+ return false
147
+ }
148
+ }
149
+
150
+ // ── Lifecycle ────────────────────────────────────────────────────────
151
+
152
+ async function fetchIfNeeded(currentId) {
153
+ if (!currentId || currentId === 'new') return
154
+ await objectStore.fetchObject(objectType, currentId)
155
+ }
156
+
157
+ onMounted(() => {
158
+ fetchIfNeeded(idRef.value)
159
+ })
160
+
161
+ watch(idRef, (newId) => {
162
+ fetchIfNeeded(newId)
163
+ })
164
+
165
+ // ── Return value ─────────────────────────────────────────────────────
166
+
167
+ return {
168
+ // Store-derived
169
+ object,
170
+ loading,
171
+ // Computed state
172
+ isNew,
173
+ // Local state
174
+ editing,
175
+ saving,
176
+ showDeleteDialog,
177
+ error,
178
+ validationErrors,
179
+ // Operations
180
+ onSave,
181
+ confirmDelete,
182
+ }
183
+ }
184
+
185
+ // ── Legacy implementation ─────────────────────────────────────────────────────
186
+
187
+ /**
188
+ * Legacy `useDetailView(options)` implementation.
189
+ * Preserved verbatim for backward compatibility.
190
+ *
191
+ * @param {object} options Legacy options object
192
+ * @param {string} [options.objectType] The registered object type slug
193
+ * @param {Function} [options.fetchFn] (type, id) => Promise<object>
194
+ * @param {Function} [options.saveFn] (type, data) => Promise<object>
195
+ * @param {Function} [options.deleteFn] (type, id) => Promise<boolean>
196
+ * @param {Function} [options.onSaved] Callback after successful save
197
+ * @param {Function} [options.onDeleted] Callback after successful delete
198
+ * @return {object} Reactive state and methods
199
+ */
200
+ function useLegacyDetailView(options) {
201
+ const objectData = ref({})
202
+ const editing = ref(false)
203
+ const loading = ref(false)
204
+ const saving = ref(false)
205
+ const showDeleteDialog = ref(false)
206
+ const error = ref(null)
207
+
208
+ async function load(id) {
209
+ loading.value = true
210
+ error.value = null
211
+ try {
212
+ const result = options.fetchFn
213
+ ? await options.fetchFn(options.objectType, id)
214
+ : null
215
+ if (result) {
216
+ objectData.value = { ...result }
217
+ }
218
+ return result
219
+ } catch (err) {
220
+ error.value = err.message || 'Failed to load'
221
+ return null
222
+ } finally {
223
+ loading.value = false
224
+ }
225
+ }
226
+
227
+ async function save(formData) {
228
+ saving.value = true
229
+ error.value = null
230
+ try {
231
+ const data = formData || objectData.value
232
+ const result = options.saveFn
233
+ ? await options.saveFn(options.objectType, data)
234
+ : null
235
+ if (result) {
236
+ objectData.value = { ...result }
237
+ editing.value = false
238
+ if (options.onSaved) {
239
+ options.onSaved(result)
240
+ }
241
+ }
242
+ return result
243
+ } catch (err) {
244
+ error.value = err.message || 'Failed to save'
245
+ return null
246
+ } finally {
247
+ saving.value = false
248
+ }
249
+ }
250
+
251
+ function confirmDelete() {
252
+ showDeleteDialog.value = true
253
+ }
254
+
255
+ async function executeDelete(id) {
256
+ const deleteId = id || objectData.value.id
257
+ loading.value = true
258
+ error.value = null
259
+ try {
260
+ const success = options.deleteFn
261
+ ? await options.deleteFn(options.objectType, deleteId)
262
+ : false
263
+ if (success) {
264
+ showDeleteDialog.value = false
265
+ if (options.onDeleted) {
266
+ options.onDeleted()
267
+ }
268
+ }
269
+ return success
270
+ } catch (err) {
271
+ error.value = err.message || 'Failed to delete'
272
+ return false
273
+ } finally {
274
+ loading.value = false
275
+ }
276
+ }
277
+
278
+ return {
279
+ objectData,
280
+ editing,
281
+ loading,
282
+ saving,
283
+ showDeleteDialog,
284
+ error,
285
+ load,
286
+ save,
287
+ confirmDelete,
288
+ executeDelete,
289
+ }
290
+ }