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

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 (153) hide show
  1. package/README.md +226 -226
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +60455 -8755
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +2062 -528
  6. package/dist/nextcloud-vue.esm.js +60411 -8731
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +75 -61
  9. package/src/components/CnActionsBar/CnActionsBar.vue +235 -225
  10. package/src/components/CnActionsBar/index.js +1 -1
  11. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
  12. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  13. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  14. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
  15. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  16. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  17. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  18. package/src/components/CnCardGrid/index.js +1 -1
  19. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  20. package/src/components/CnCellRenderer/index.js +1 -1
  21. package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
  22. package/src/components/CnChartWidget/index.js +1 -0
  23. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  24. package/src/components/CnConfigurationCard/index.js +1 -1
  25. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
  26. package/src/components/CnDashboardGrid/index.js +1 -0
  27. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
  28. package/src/components/CnDashboardPage/index.js +1 -0
  29. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  30. package/src/components/CnDataTable/index.js +1 -1
  31. package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
  32. package/src/components/CnDetailCard/index.js +1 -0
  33. package/src/components/CnDetailPage/CnDetailPage.vue +281 -0
  34. package/src/components/CnDetailPage/index.js +1 -0
  35. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -223
  36. package/src/components/CnFacetSidebar/index.js +1 -1
  37. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  38. package/src/components/CnFilterBar/index.js +1 -1
  39. package/src/components/CnIcon/CnIcon.vue +89 -89
  40. package/src/components/CnIcon/index.js +1 -1
  41. package/src/components/CnIndexPage/CnIndexPage.vue +874 -816
  42. package/src/components/CnIndexPage/index.js +1 -1
  43. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -484
  44. package/src/components/CnIndexSidebar/index.js +1 -1
  45. package/src/components/CnItemCard/CnItemCard.vue +132 -0
  46. package/src/components/CnItemCard/index.js +1 -0
  47. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  48. package/src/components/CnKpiGrid/index.js +1 -1
  49. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  50. package/src/components/CnMassActionBar/index.js +1 -1
  51. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  52. package/src/components/CnMassCopyDialog/index.js +1 -1
  53. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  54. package/src/components/CnMassDeleteDialog/index.js +1 -1
  55. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  56. package/src/components/CnMassExportDialog/index.js +1 -1
  57. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  58. package/src/components/CnMassImportDialog/index.js +1 -1
  59. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  60. package/src/components/CnNoteCard/index.js +1 -0
  61. package/src/components/CnNotesCard/CnNotesCard.vue +413 -0
  62. package/src/components/CnNotesCard/index.js +1 -0
  63. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  64. package/src/components/CnObjectCard/index.js +1 -1
  65. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
  66. package/src/components/CnObjectSidebar/index.js +1 -0
  67. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  68. package/src/components/CnPageHeader/index.js +1 -1
  69. package/src/components/CnPagination/CnPagination.vue +252 -252
  70. package/src/components/CnPagination/index.js +1 -1
  71. package/src/components/CnRowActions/CnRowActions.vue +73 -73
  72. package/src/components/CnRowActions/index.js +1 -1
  73. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  74. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
  75. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  76. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  77. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  78. package/src/components/CnSchemaFormDialog/index.js +1 -0
  79. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  80. package/src/components/CnSettingsCard/index.js +1 -1
  81. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  82. package/src/components/CnSettingsSection/index.js +1 -1
  83. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -366
  84. package/src/components/CnStatsBlock/index.js +1 -1
  85. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  86. package/src/components/CnStatusBadge/index.js +1 -1
  87. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
  88. package/src/components/CnTabbedFormDialog/index.js +1 -0
  89. package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
  90. package/src/components/CnTasksCard/index.js +1 -0
  91. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  92. package/src/components/CnTileWidget/index.js +1 -0
  93. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
  94. package/src/components/CnTimelineStages/index.js +1 -0
  95. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
  96. package/src/components/CnUserActionMenu/index.js +1 -0
  97. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  98. package/src/components/CnVersionInfoCard/index.js +1 -1
  99. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  100. package/src/components/CnWidgetRenderer/index.js +1 -0
  101. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -0
  102. package/src/components/CnWidgetWrapper/index.js +1 -0
  103. package/src/components/index.js +43 -29
  104. package/src/composables/index.js +4 -3
  105. package/src/composables/useDashboardView.js +240 -0
  106. package/src/composables/useDetailView.js +289 -132
  107. package/src/composables/useListView.js +363 -153
  108. package/src/composables/useSubResource.js +142 -142
  109. package/src/constants/metadata.js +30 -30
  110. package/src/css/CnSchemaFormDialog.css +546 -0
  111. package/src/css/__sample_nextcloud_tokens.css +110 -0
  112. package/src/css/actions-bar.css +48 -48
  113. package/src/css/badge.css +51 -51
  114. package/src/css/card.css +128 -128
  115. package/src/css/dashboard.css +70 -0
  116. package/src/css/detail-page.css +168 -0
  117. package/src/css/detail.css +68 -68
  118. package/src/css/index-page.css +44 -32
  119. package/src/css/index-sidebar.css +193 -187
  120. package/src/css/index.css +16 -12
  121. package/src/css/layout.css +90 -90
  122. package/src/css/page-header.css +33 -33
  123. package/src/css/pagination.css +72 -72
  124. package/src/css/table.css +142 -142
  125. package/src/css/timeline-stages.css +218 -0
  126. package/src/css/utilities.css +46 -46
  127. package/src/index.js +72 -53
  128. package/src/store/createSubResourcePlugin.js +135 -135
  129. package/src/store/index.js +3 -3
  130. package/src/store/plugins/auditTrails.js +17 -17
  131. package/src/store/plugins/files.js +250 -186
  132. package/src/store/plugins/index.js +7 -5
  133. package/src/store/plugins/lifecycle.js +180 -180
  134. package/src/store/plugins/relations.js +68 -68
  135. package/src/store/plugins/search.js +372 -0
  136. package/src/store/plugins/selection.js +104 -0
  137. package/src/store/useObjectStore.js +829 -686
  138. package/src/types/auditTrail.d.ts +32 -32
  139. package/src/types/file.d.ts +23 -23
  140. package/src/types/index.d.ts +35 -35
  141. package/src/types/notification.d.ts +36 -36
  142. package/src/types/object.d.ts +40 -40
  143. package/src/types/organisation.d.ts +41 -41
  144. package/src/types/register.d.ts +25 -25
  145. package/src/types/schema.d.ts +39 -39
  146. package/src/types/shared.d.ts +79 -79
  147. package/src/types/source.d.ts +14 -14
  148. package/src/types/task.d.ts +31 -31
  149. package/src/utils/errors.js +96 -96
  150. package/src/utils/headers.js +68 -50
  151. package/src/utils/id.js +13 -0
  152. package/src/utils/index.js +3 -3
  153. package/src/utils/schema.js +422 -419
@@ -1,153 +1,363 @@
1
- import { ref, onBeforeUnmount } from 'vue'
2
-
3
- /**
4
- * Composable for managing list view state: search, filters, sorting, pagination.
5
- *
6
- * Extracts the search-debounce + filter + sort + pagination pattern
7
- * found in every list view 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 Function to call: (type, params) => Promise<Array>
12
- * @param {number} [options.debounceMs=300] Search debounce in milliseconds
13
- * @param {number} [options.pageSize=20] Default page size
14
- * @param {object} [options.defaultSort] Default sort: { key: string, order: 'asc'|'desc' }
15
- * @return {object} Reactive state and methods
16
- *
17
- * @example
18
- * import { useListView } from '@conduction/nextcloud-vue'
19
- *
20
- * const { searchTerm, sortKey, sortOrder, currentPage, onSearchInput, toggleSort, fetch } = useListView({
21
- * objectType: 'client',
22
- * fetchFn: (type, params) => objectStore.fetchCollection(type, params),
23
- * defaultSort: { key: 'name', order: 'asc' },
24
- * })
25
- */
26
- export function useListView(options) {
27
- const searchTerm = ref('')
28
- const filters = ref({})
29
- const sortKey = ref(options.defaultSort?.key || null)
30
- const sortOrder = ref(options.defaultSort?.order || 'asc')
31
- const currentPage = ref(1)
32
- const pageSize = ref(options.pageSize || 20)
33
-
34
- let searchTimeout = null
35
-
36
- /**
37
- * Build fetch parameters from current state.
38
- * @return {object} Parameters for the fetch function
39
- */
40
- function buildFetchParams() {
41
- const params = {
42
- _limit: pageSize.value,
43
- _page: currentPage.value,
44
- }
45
-
46
- if (searchTerm.value) {
47
- params._search = searchTerm.value
48
- }
49
-
50
- if (sortKey.value) {
51
- params._order = { [sortKey.value]: sortOrder.value }
52
- }
53
-
54
- // Merge active filters
55
- for (const [key, value] of Object.entries(filters.value)) {
56
- if (value !== null && value !== '' && value !== false) {
57
- params[key] = value
58
- }
59
- }
60
-
61
- return params
62
- }
63
-
64
- /**
65
- * Execute a fetch with current state.
66
- * @param {number} [page] Optional page override
67
- * @return {Promise<Array>} Fetched results
68
- */
69
- async function fetchData(page) {
70
- if (page !== undefined) {
71
- currentPage.value = page
72
- }
73
- const params = buildFetchParams()
74
- return options.fetchFn(options.objectType, params)
75
- }
76
-
77
- /**
78
- * Handle search input with debouncing.
79
- * @param {string} value New search value
80
- */
81
- function onSearchInput(value) {
82
- searchTerm.value = value
83
- clearTimeout(searchTimeout)
84
- searchTimeout = setTimeout(() => fetchData(1), options.debounceMs || 300)
85
- }
86
-
87
- /**
88
- * Toggle sort on a column. Cycles: asc -> desc -> null.
89
- * @param {string} key Column key
90
- */
91
- function toggleSort(key) {
92
- if (sortKey.value === key) {
93
- if (sortOrder.value === 'asc') {
94
- sortOrder.value = 'desc'
95
- } else {
96
- sortKey.value = null
97
- sortOrder.value = 'asc'
98
- }
99
- } else {
100
- sortKey.value = key
101
- sortOrder.value = 'asc'
102
- }
103
- fetchData(1)
104
- }
105
-
106
- /**
107
- * Set a filter value and re-fetch.
108
- * @param {string} key Filter key
109
- * @param {*} value Filter value
110
- */
111
- function setFilter(key, value) {
112
- filters.value = { ...filters.value, [key]: value }
113
- fetchData(1)
114
- }
115
-
116
- /**
117
- * Clear all filters and search, then re-fetch.
118
- */
119
- function clearAllFilters() {
120
- searchTerm.value = ''
121
- filters.value = {}
122
- sortKey.value = options.defaultSort?.key || null
123
- sortOrder.value = options.defaultSort?.order || 'asc'
124
- fetchData(1)
125
- }
126
-
127
- /**
128
- * Navigate to a specific page.
129
- * @param {number} page Page number
130
- */
131
- function goToPage(page) {
132
- currentPage.value = page
133
- fetchData()
134
- }
135
-
136
- onBeforeUnmount(() => clearTimeout(searchTimeout))
137
-
138
- return {
139
- searchTerm,
140
- filters,
141
- sortKey,
142
- sortOrder,
143
- currentPage,
144
- pageSize,
145
- onSearchInput,
146
- toggleSort,
147
- setFilter,
148
- clearAllFilters,
149
- goToPage,
150
- fetch: fetchData,
151
- buildFetchParams,
152
- }
153
- }
1
+ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
2
+ import { useObjectStore } from '../store/index.js'
3
+
4
+ /**
5
+ * Composable for managing list view state with full objectStore integration.
6
+ *
7
+ * When called with an `objectType` string, connects to the objectStore and handles
8
+ * schema loading, collection fetching, sidebar wiring, and all event handlers
9
+ * automatically. Everything a `CnIndexPage`-based list view needs is returned
10
+ * directly no additional computed properties or methods required in the component.
11
+ *
12
+ * Backward-compatible: existing `useListView(options)` and `useListView()` calls
13
+ * continue to work without modification.
14
+ *
15
+ * @param {string|object} [objectTypeOrOptions] Object type slug (new API) or legacy options object
16
+ * @param {object} [options] Options (new API only)
17
+ * @param {object|null} [options.sidebarState] Sidebar state object from `inject('sidebarState')`. When provided, the composable wires and unwires the sidebar automatically on mount/unmount.
18
+ * @param {number} [options.defaultPageSize=20] Default `_limit` sent to the API
19
+ * @param {number} [options.debounceMs=300] Search debounce in milliseconds
20
+ * @param {object} [options.defaultSort] Default sort applied on mount e.g. `{ key: 'createdAt', order: 'desc' }`
21
+ * @return {object} Reactive state and event handlers
22
+ *
23
+ * @example
24
+ * // New API — minimal
25
+ * const { schema, objects, loading, pagination,
26
+ * onSearch, onSort, onFilterChange, onPageChange, refresh } = useListView('client')
27
+ *
28
+ * @example
29
+ * // New API — with sidebar wiring
30
+ * const list = useListView('client', {
31
+ * sidebarState: inject('sidebarState', null),
32
+ * })
33
+ *
34
+ * @example
35
+ * // Legacy API — still works
36
+ * const { searchTerm, filters, onSearchInput, toggleSort } = useListView({
37
+ * objectType: 'client',
38
+ * fetchFn: (type, params) => objectStore.fetchCollection(type, params),
39
+ * })
40
+ */
41
+ export function useListView(objectTypeOrOptions, options) {
42
+ // Backward compat: if first arg is an object or absent, delegate to legacy implementation
43
+ if (!objectTypeOrOptions || typeof objectTypeOrOptions === 'object') {
44
+ return useLegacyListView(objectTypeOrOptions || {})
45
+ }
46
+
47
+ // ── New API ──────────────────────────────────────────────────────────
48
+ const objectType = objectTypeOrOptions
49
+ const opts = options || {}
50
+ const sidebarState = opts.sidebarState || null
51
+
52
+ const objectStore = useObjectStore()
53
+
54
+ // ── State refs ───────────────────────────────────────────────────────
55
+ const schema = ref(null)
56
+ const searchTerm = ref('')
57
+ const sortKey = ref(opts.defaultSort?.key || null)
58
+ const sortOrder = ref(opts.defaultSort?.order || 'asc')
59
+ const activeFilters = ref({})
60
+ const visibleColumns = ref(null)
61
+ const pageSize = ref(opts.defaultPageSize || 20)
62
+
63
+ // ── Computed refs from the store ─────────────────────────────────────
64
+ const objects = computed(() => objectStore.collections[objectType] || [])
65
+ const loading = computed(() => objectStore.loading[objectType] || false)
66
+ const pagination = computed(
67
+ () => objectStore.pagination[objectType] || { total: 0, page: 1, pages: 1, limit: 20 },
68
+ )
69
+
70
+ let searchTimeout = null
71
+
72
+ // ── Param construction ───────────────────────────────────────────────
73
+
74
+ /**
75
+ * Build API fetch params from current reactive state.
76
+ *
77
+ * @param {number} page Page number to request
78
+ * @return {object} Params object ready to pass to fetchCollection
79
+ */
80
+ function buildParams(page) {
81
+ const params = { _limit: pageSize.value, _page: page }
82
+
83
+ if (searchTerm.value) {
84
+ params._search = searchTerm.value
85
+ }
86
+
87
+ if (sortKey.value) {
88
+ params._order = { [sortKey.value]: sortOrder.value }
89
+ }
90
+
91
+ for (const [key, values] of Object.entries(activeFilters.value)) {
92
+ if (values && values.length > 0) {
93
+ // Single-value arrays are unwrapped to scalar params
94
+ params[key] = values.length === 1 ? values[0] : values
95
+ }
96
+ }
97
+
98
+ return params
99
+ }
100
+
101
+ // ── Fetch ────────────────────────────────────────────────────────────
102
+
103
+ /**
104
+ * Fetch the collection using current state params and update sidebar facet data.
105
+ *
106
+ * @param {number} [page=1] Page to fetch
107
+ * @return {Promise<void>}
108
+ */
109
+ async function refresh(page = 1) {
110
+ await objectStore.fetchCollection(objectType, buildParams(page))
111
+ }
112
+
113
+ // ── Event handlers ───────────────────────────────────────────────────
114
+
115
+ /**
116
+ * Handle search input. Debounced by `options.debounceMs` (default 300 ms).
117
+ *
118
+ * @param {string} value New search string
119
+ */
120
+ function onSearch(value) {
121
+ searchTerm.value = value
122
+ clearTimeout(searchTimeout)
123
+ searchTimeout = setTimeout(() => refresh(1), opts.debounceMs || 300)
124
+ }
125
+
126
+ /**
127
+ * Handle sort change. Updates sort state and triggers refresh.
128
+ *
129
+ * @param {{key: string, order: string}} sort New sort definition
130
+ */
131
+ function onSort({ key, order }) {
132
+ sortKey.value = key
133
+ sortOrder.value = order || 'asc'
134
+ refresh(1)
135
+ }
136
+
137
+ /**
138
+ * Handle filter change for a single key. Empty arrays remove the key.
139
+ *
140
+ * @param {string} key Filter key (maps to API param name)
141
+ * @param {Array} values Selected filter values
142
+ */
143
+ function onFilterChange(key, values) {
144
+ if (!values || values.length === 0) {
145
+ const updated = { ...activeFilters.value }
146
+ delete updated[key]
147
+ activeFilters.value = updated
148
+ } else {
149
+ activeFilters.value = { ...activeFilters.value, [key]: values }
150
+ }
151
+ refresh(1)
152
+ }
153
+
154
+ /**
155
+ * Handle page navigation.
156
+ *
157
+ * @param {number} page Page number to navigate to
158
+ */
159
+ function onPageChange(page) {
160
+ refresh(page)
161
+ }
162
+
163
+ /**
164
+ * Handle page-size change. Resets to page 1.
165
+ *
166
+ * @param {number} size New page size
167
+ */
168
+ function onPageSizeChange(size) {
169
+ pageSize.value = size
170
+ refresh(1)
171
+ }
172
+
173
+ // ── Sidebar wiring ───────────────────────────────────────────────────
174
+
175
+ function setupSidebar() {
176
+ if (!sidebarState) return
177
+ sidebarState.active = true
178
+ sidebarState.schema = schema.value
179
+ sidebarState.searchValue = searchTerm.value
180
+ sidebarState.activeFilters = {}
181
+ sidebarState.onSearch = onSearch
182
+ sidebarState.onColumnsChange = (cols) => {
183
+ visibleColumns.value = cols
184
+ }
185
+ sidebarState.onFilterChange = ({ key, values }) => onFilterChange(key, values)
186
+ }
187
+
188
+ function teardownSidebar() {
189
+ if (!sidebarState) return
190
+ sidebarState.active = false
191
+ sidebarState.schema = null
192
+ sidebarState.activeFilters = {}
193
+ sidebarState.facetData = {}
194
+ sidebarState.onSearch = null
195
+ sidebarState.onColumnsChange = null
196
+ sidebarState.onFilterChange = null
197
+ }
198
+
199
+ // Push facet data to sidebar after each store update
200
+ if (sidebarState) {
201
+ watch(
202
+ () => objectStore.facets[objectType],
203
+ (facets) => {
204
+ sidebarState.facetData = facets || {}
205
+ },
206
+ )
207
+ }
208
+
209
+ // ── Lifecycle ────────────────────────────────────────────────────────
210
+
211
+ onMounted(async () => {
212
+ schema.value = await objectStore.fetchSchema(objectType)
213
+ if (sidebarState) {
214
+ setupSidebar()
215
+ }
216
+ await refresh(1)
217
+ })
218
+
219
+ onBeforeUnmount(() => {
220
+ clearTimeout(searchTimeout)
221
+ teardownSidebar()
222
+ })
223
+
224
+ // ── Return value ─────────────────────────────────────────────────────
225
+
226
+ return {
227
+ // Store-derived
228
+ schema,
229
+ objects,
230
+ loading,
231
+ pagination,
232
+ // Local state
233
+ searchTerm,
234
+ sortKey,
235
+ sortOrder,
236
+ activeFilters,
237
+ visibleColumns,
238
+ pageSize,
239
+ // Event handlers
240
+ onSearch,
241
+ onSort,
242
+ onFilterChange,
243
+ onPageChange,
244
+ onPageSizeChange,
245
+ // Explicit fetch
246
+ refresh,
247
+ }
248
+ }
249
+
250
+ // ── Legacy implementation ─────────────────────────────────────────────────────
251
+
252
+ /**
253
+ * Legacy `useListView(options)` implementation.
254
+ * Preserved verbatim for backward compatibility.
255
+ *
256
+ * @param {object} options Legacy options object
257
+ * @param {string} [options.objectType] The registered object type slug
258
+ * @param {Function} [options.fetchFn] Function to call: (type, params) => Promise<Array>
259
+ * @param {number} [options.debounceMs=300] Search debounce in milliseconds
260
+ * @param {number} [options.pageSize=20] Default page size
261
+ * @param {object} [options.defaultSort] Default sort: { key: string, order: 'asc'|'desc' }
262
+ * @return {object} Reactive state and methods
263
+ */
264
+ function useLegacyListView(options) {
265
+ const searchTerm = ref('')
266
+ const filters = ref({})
267
+ const sortKey = ref(options.defaultSort?.key || null)
268
+ const sortOrder = ref(options.defaultSort?.order || 'asc')
269
+ const currentPage = ref(1)
270
+ const pageSize = ref(options.pageSize || 20)
271
+
272
+ let searchTimeout = null
273
+
274
+ function buildFetchParams() {
275
+ const params = {
276
+ _limit: pageSize.value,
277
+ _page: currentPage.value,
278
+ }
279
+
280
+ if (searchTerm.value) {
281
+ params._search = searchTerm.value
282
+ }
283
+
284
+ if (sortKey.value) {
285
+ params._order = { [sortKey.value]: sortOrder.value }
286
+ }
287
+
288
+ for (const [key, value] of Object.entries(filters.value)) {
289
+ if (value !== null && value !== '' && value !== false) {
290
+ params[key] = value
291
+ }
292
+ }
293
+
294
+ return params
295
+ }
296
+
297
+ async function fetchData(page) {
298
+ if (page !== undefined) {
299
+ currentPage.value = page
300
+ }
301
+ const params = buildFetchParams()
302
+ if (options.fetchFn) {
303
+ return options.fetchFn(options.objectType, params)
304
+ }
305
+ }
306
+
307
+ function onSearchInput(value) {
308
+ searchTerm.value = value
309
+ clearTimeout(searchTimeout)
310
+ searchTimeout = setTimeout(() => fetchData(1), options.debounceMs || 300)
311
+ }
312
+
313
+ function toggleSort(key) {
314
+ if (sortKey.value === key) {
315
+ if (sortOrder.value === 'asc') {
316
+ sortOrder.value = 'desc'
317
+ } else {
318
+ sortKey.value = null
319
+ sortOrder.value = 'asc'
320
+ }
321
+ } else {
322
+ sortKey.value = key
323
+ sortOrder.value = 'asc'
324
+ }
325
+ fetchData(1)
326
+ }
327
+
328
+ function setFilter(key, value) {
329
+ filters.value = { ...filters.value, [key]: value }
330
+ fetchData(1)
331
+ }
332
+
333
+ function clearAllFilters() {
334
+ searchTerm.value = ''
335
+ filters.value = {}
336
+ sortKey.value = options.defaultSort?.key || null
337
+ sortOrder.value = options.defaultSort?.order || 'asc'
338
+ fetchData(1)
339
+ }
340
+
341
+ function goToPage(page) {
342
+ currentPage.value = page
343
+ fetchData()
344
+ }
345
+
346
+ onBeforeUnmount(() => clearTimeout(searchTimeout))
347
+
348
+ return {
349
+ searchTerm,
350
+ filters,
351
+ sortKey,
352
+ sortOrder,
353
+ currentPage,
354
+ pageSize,
355
+ onSearchInput,
356
+ toggleSort,
357
+ setFilter,
358
+ clearAllFilters,
359
+ goToPage,
360
+ fetch: fetchData,
361
+ buildFetchParams,
362
+ }
363
+ }