@stacksjs/defaults 0.75.230 → 0.75.233

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 (51) hide show
  1. package/ai/skills/stacks-dashboard/SKILL.md +15 -0
  2. package/ai/skills/stacks-models/SKILL.md +6 -1
  3. package/ai/skills/stacks-native/CATALOG.md +6 -6
  4. package/ai/skills/stacks-storage/SKILL.md +43 -0
  5. package/ai/skills/stacks-testing/SKILL.md +38 -10
  6. package/app/Actions/Dashboard/Models/GetModels.ts +19 -14
  7. package/app/Actions/Dashboard/Models/GetSubscriberCount.ts +5 -2
  8. package/app/Actions/Dashboard/Models/GetUserCount.ts +5 -2
  9. package/app/Actions/Dashboard/Models/ModelShowAction.ts +3 -11
  10. package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +6 -3
  11. package/app/Actions/Dashboard/Models/model-write.ts +2 -3
  12. package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +6 -2
  13. package/functions/dashboard-form.test.ts +41 -0
  14. package/functions/dashboard-form.ts +30 -0
  15. package/functions/dashboard-list.test.ts +228 -0
  16. package/functions/dashboard-list.ts +257 -0
  17. package/ide/vscode/package.json +1 -1
  18. package/package.json +2 -2
  19. package/project/storage/framework/types/browser-composables.d.ts +56 -0
  20. package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +2 -7
  21. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +2 -4
  22. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +2 -4
  23. package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +2 -7
  24. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +2 -4
  25. package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +2 -7
  26. package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +2 -4
  27. package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +2 -7
  28. package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +2 -4
  29. package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +2 -4
  30. package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +2 -7
  31. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +2 -4
  32. package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +2 -7
  33. package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +2 -4
  34. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +2 -4
  35. package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +2 -4
  36. package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +2 -4
  37. package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +45 -92
  38. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +46 -92
  39. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +46 -92
  40. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +44 -90
  41. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +45 -91
  42. package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +2 -4
  43. package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +2 -7
  44. package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +2 -4
  45. package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +2 -4
  46. package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +2 -9
  47. package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +2 -9
  48. package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +2 -9
  49. package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +2 -9
  50. package/resources/functions/dashboard/model-access.ts +15 -0
  51. package/resources/plugins/preloader.ts +9 -1
@@ -6,6 +6,7 @@ import type {
6
6
  import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
7
7
  import { pushToast } from '../../../../functions/toasts'
8
8
  import { formatCurrency, minorToInput } from '@stacksjs/commerce/money'
9
+ import { pageControls } from '../../../../functions/dashboard-list'
9
10
 
10
11
  interface CustomerIndexResponse {
11
12
  records: CommerceCustomerRecord[]
@@ -243,13 +244,7 @@ async function deleteCustomer(): Promise<void> {
243
244
  }
244
245
  }
245
246
 
246
- function previousPage(): void {
247
- page.set(Math.max(1, page() - 1))
248
- }
249
-
250
- function nextPage(): void {
251
- page.set(Math.min(pageCount(), page() + 1))
252
- }
247
+ const { previousPage, nextPage } = pageControls(page, pageCount)
253
248
 
254
249
  onMount(() => {
255
250
  void loadCustomers()
@@ -1,6 +1,7 @@
1
1
  <script client>
2
2
  import type { GiftCardRecord, GiftCardStatus } from '../../../../app/Actions/Dashboard/Commerce/gift-card-records'
3
3
  import { minorToInput, moneyInputError, moneyInputStep, parseMoneyInput } from '@stacksjs/commerce/money'
4
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
4
5
 
5
6
  const open = useReactiveProp('open', false)
6
7
  const mode = useReactiveProp('mode', 'add')
@@ -148,10 +149,7 @@ function submit(): void {
148
149
  }
149
150
 
150
151
  function errorMessages(): string[] {
151
- return Object.entries(errors()).map(([field, message]) => {
152
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
153
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
154
- })
152
+ return formErrorMessages(errors())
155
153
  }
156
154
  </script>
157
155
 
@@ -7,6 +7,7 @@ import type {
7
7
  import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
8
8
  import { pushToast } from '../../../../functions/toasts'
9
9
  import { minorToInput } from '@stacksjs/commerce/money'
10
+ import { pageControls } from '../../../../functions/dashboard-list'
10
11
 
11
12
  interface GiftCardIndexResponse {
12
13
  records: GiftCardRecord[]
@@ -257,13 +258,7 @@ async function deleteGiftCard(): Promise<void> {
257
258
  }
258
259
  }
259
260
 
260
- function previousPage(): void {
261
- page.set(Math.max(1, page() - 1))
262
- }
263
-
264
- function nextPage(): void {
265
- page.set(Math.min(pageCount(), page() + 1))
266
- }
261
+ const { previousPage, nextPage } = pageControls(page, pageCount)
267
262
 
268
263
  onMount(() => {
269
264
  void loadGiftCards()
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import type { ManufacturerRecord } from '../../../../app/Actions/Dashboard/Commerce/manufacturer-records'
3
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
3
4
 
4
5
  const open = useReactiveProp('open', false)
5
6
  const mode = useReactiveProp('mode', 'add')
@@ -41,10 +42,7 @@ function submit(): void {
41
42
  }
42
43
 
43
44
  function errorMessages(): string[] {
44
- return Object.entries(errors()).map(([field, message]) => {
45
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
46
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
47
- })
45
+ return formErrorMessages(errors())
48
46
  }
49
47
  </script>
50
48
 
@@ -4,6 +4,7 @@ import type {
4
4
  OrderCustomerOption,
5
5
  } from '../../../../app/Actions/Dashboard/Commerce/commerce-order-records'
6
6
  import { minorToInput, moneyInputError, moneyInputStep, parseMoneyInput } from '@stacksjs/commerce/money'
7
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
7
8
 
8
9
  const open = useReactiveProp('open', false)
9
10
  const mode = useReactiveProp('mode', 'add')
@@ -108,10 +109,7 @@ function submit(): void {
108
109
  }
109
110
 
110
111
  function errorMessages(): string[] {
111
- return Object.entries(errors()).map(([field, message]) => {
112
- const fieldLabel = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
113
- return `${fieldLabel.charAt(0).toUpperCase()}${fieldLabel.slice(1)}: ${message}`
114
- })
112
+ return formErrorMessages(errors())
115
113
  }
116
114
  </script>
117
115
 
@@ -7,6 +7,7 @@ import type {
7
7
  import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
8
8
  import { pushToast } from '../../../../functions/toasts'
9
9
  import { formatCurrency, minorToInput } from '@stacksjs/commerce/money'
10
+ import { pageControls } from '../../../../functions/dashboard-list'
10
11
 
11
12
  interface OrderIndexResponse {
12
13
  records: CommerceOrderRecord[]
@@ -284,13 +285,7 @@ async function deleteOrder(): Promise<void> {
284
285
  }
285
286
  }
286
287
 
287
- function previousPage(): void {
288
- page.set(Math.max(1, page() - 1))
289
- }
290
-
291
- function nextPage(): void {
292
- page.set(Math.min(pageCount(), page() + 1))
293
- }
288
+ const { previousPage, nextPage } = pageControls(page, pageCount)
294
289
 
295
290
  onMount(() => {
296
291
  void loadOrders()
@@ -1,4 +1,5 @@
1
1
  <script client>
2
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
2
3
  import type {
3
4
  CommerceProductRecord,
4
5
  ProductOption,
@@ -108,10 +109,7 @@ function submit(): void {
108
109
  }
109
110
 
110
111
  function errorMessages(): string[] {
111
- return Object.entries(errors()).map(([field, message]) => {
112
- const fieldLabel = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
113
- return `${fieldLabel.charAt(0).toUpperCase()}${fieldLabel.slice(1)}: ${message}`
114
- })
112
+ return formErrorMessages(errors())
115
113
  }
116
114
  </script>
117
115
 
@@ -8,6 +8,7 @@ import type {
8
8
  import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
9
9
  import { pushToast } from '../../../../functions/toasts'
10
10
  import { minorToInput } from '@stacksjs/commerce/money'
11
+ import { pageControls } from '../../../../functions/dashboard-list'
11
12
 
12
13
  interface ProductIndexResponse {
13
14
  records: CommerceProductRecord[]
@@ -259,13 +260,7 @@ async function deleteProduct(): Promise<void> {
259
260
  }
260
261
  }
261
262
 
262
- function previousPage(): void {
263
- page.set(Math.max(1, page() - 1))
264
- }
265
-
266
- function nextPage(): void {
267
- page.set(Math.min(pageCount(), page() + 1))
268
- }
263
+ const { previousPage, nextPage } = pageControls(page, pageCount)
269
264
 
270
265
  onMount(() => {
271
266
  void loadProducts()
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import type { ReviewRecord } from '../../../../app/Actions/Dashboard/Commerce/review-records'
3
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
3
4
 
4
5
  const open = useReactiveProp('open', false)
5
6
  const review = useReactiveProp('review', null as ReviewRecord | null)
@@ -42,10 +43,7 @@ function submit(): void {
42
43
  }
43
44
 
44
45
  function errorMessages(): string[] {
45
- return Object.entries(errors()).map(([field, message]) => {
46
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
47
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
48
- })
46
+ return formErrorMessages(errors())
49
47
  }
50
48
  </script>
51
49
 
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import type { TaxRateRecord } from '../../../../app/Actions/Dashboard/Commerce/tax-rate-records'
3
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
3
4
 
4
5
  const open = useReactiveProp('open', false)
5
6
  const mode = useReactiveProp('mode', 'add')
@@ -59,10 +60,7 @@ function submit(): void {
59
60
  }
60
61
 
61
62
  function errorMessages(): string[] {
62
- return Object.entries(errors()).map(([field, message]) => {
63
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
64
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
65
- })
63
+ return formErrorMessages(errors())
66
64
  }
67
65
  </script>
68
66
 
@@ -1,4 +1,5 @@
1
1
  <script client>
2
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
2
3
  import type {
3
4
  ProductUnitOption,
4
5
  ProductUnitRecord,
@@ -52,10 +53,7 @@ function submit(): void {
52
53
  }
53
54
 
54
55
  function errorMessages(): string[] {
55
- return Object.entries(errors()).map(([field, message]) => {
56
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
57
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
58
- })
56
+ return formErrorMessages(errors())
59
57
  }
60
58
  </script>
61
59
 
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import type { ProductVariantOption, ProductVariantRecord } from '../../../../app/Actions/Dashboard/Commerce/product-variant-records'
3
+ import { formErrorMessages } from '../../../../functions/dashboard-form'
3
4
 
4
5
  const open = useReactiveProp('open', false)
5
6
  const mode = useReactiveProp('mode', 'add')
@@ -49,10 +50,7 @@ function submit(): void {
49
50
  }
50
51
 
51
52
  function errorMessages(): string[] {
52
- return Object.entries(errors()).map(([field, message]) => {
53
- const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
54
- return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
55
- })
53
+ return formErrorMessages(errors())
56
54
  }
57
55
  </script>
58
56
 
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import { dashboardApi } from '../../../../../functions/dashboard-api'
3
+ import { useRecordsDashboard } from '../../../../../functions/dashboard-list'
3
4
  import { pushToast } from '../../../../../functions/toasts'
4
5
 
5
6
  type CourierStatus = 'active' | 'on_delivery' | 'on_break'
@@ -34,21 +35,48 @@ interface CourierPayload {
34
35
  userId: number | null
35
36
  }
36
37
 
37
- const records = state<CourierRecord[]>([])
38
+ function matchesQuery(records: CourierRecord[], query: string): CourierRecord[] {
39
+ return records.filter(record => [
40
+ record.name,
41
+ record.phone,
42
+ record.vehicle_number,
43
+ record.license,
44
+ record.status,
45
+ record.user?.name,
46
+ record.user?.email,
47
+ ].some(value => String(value || '').toLowerCase().includes(query)))
48
+ }
49
+
50
+ const {
51
+ records,
52
+ loading,
53
+ loadError,
54
+ search,
55
+ page,
56
+ perPage,
57
+ editorOpen,
58
+ editorMode,
59
+ editing,
60
+ saving,
61
+ editorError,
62
+ deleting,
63
+ deleteBusy,
64
+ deleteError,
65
+ filteredRecords,
66
+ paginatedRecords,
67
+ goToPage,
68
+ handleSearch,
69
+ openCreate,
70
+ openEdit,
71
+ closeEditor,
72
+ requestDelete,
73
+ closeDelete,
74
+ upsert,
75
+ remove,
76
+ clampPage,
77
+ } = useRecordsDashboard<CourierRecord>({ filter: matchesQuery })
78
+
38
79
  const users = state<UserOption[]>([])
39
- const loading = state(true)
40
- const loadError = state('')
41
- const search = state('')
42
- const page = state(1)
43
- const perPage = state(10)
44
- const editorOpen = state(false)
45
- const editorMode = state<'create' | 'edit'>('create')
46
- const editing = state<CourierRecord | null>(null)
47
- const saving = state(false)
48
- const editorError = state('')
49
- const deleting = state<CourierRecord | null>(null)
50
- const deleteBusy = state(false)
51
- const deleteError = state('')
52
80
 
53
81
  function userFor(id?: number | null): UserOption | undefined {
54
82
  return id ? users().find(user => user.id === id) : undefined
@@ -61,26 +89,6 @@ function withUser(record: CourierRecord): CourierRecord {
61
89
  }
62
90
  }
63
91
 
64
- function filteredRecords(): CourierRecord[] {
65
- const query = search().trim().toLowerCase()
66
- if (!query)
67
- return records()
68
- return records().filter(record => [
69
- record.name,
70
- record.phone,
71
- record.vehicle_number,
72
- record.license,
73
- record.status,
74
- record.user?.name,
75
- record.user?.email,
76
- ].some(value => String(value || '').toLowerCase().includes(query)))
77
- }
78
-
79
- function paginatedRecords(): CourierRecord[] {
80
- const start = (page() - 1) * perPage()
81
- return filteredRecords().slice(start, start + perPage())
82
- }
83
-
84
92
  function statusCount(status: CourierStatus): number {
85
93
  return records().filter(record => record.status === status).length
86
94
  }
@@ -106,9 +114,7 @@ async function loadCouriers(): Promise<void> {
106
114
  throw new TypeError('Courier resources were not returned in the expected shape.')
107
115
  users.set(userData.users)
108
116
  records.set(courierData.map(withUser))
109
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
110
- if (page() > lastPage)
111
- page.set(lastPage)
117
+ clampPage()
112
118
  }
113
119
  catch (error) {
114
120
  loadError.set(error instanceof Error ? error.message : String(error))
@@ -119,33 +125,6 @@ async function loadCouriers(): Promise<void> {
119
125
  }
120
126
  }
121
127
 
122
- function handleSearch(value: string): void {
123
- search.set(value)
124
- page.set(1)
125
- }
126
-
127
- function openCreate(): void {
128
- editing.set(null)
129
- editorMode.set('create')
130
- editorError.set('')
131
- editorOpen.set(true)
132
- }
133
-
134
- function openEdit(record: CourierRecord): void {
135
- editing.set(record)
136
- editorMode.set('edit')
137
- editorError.set('')
138
- editorOpen.set(true)
139
- }
140
-
141
- function closeEditor(): void {
142
- if (saving())
143
- return
144
- editorOpen.set(false)
145
- editing.set(null)
146
- editorError.set('')
147
- }
148
-
149
128
  function setEditorError(message: string): void {
150
129
  editorError.set(message)
151
130
  }
@@ -165,14 +144,7 @@ async function saveCourier(payload: CourierPayload): Promise<void> {
165
144
  },
166
145
  )
167
146
  const hydrated = withUser(updated)
168
- if (current) {
169
- records.set(records().map(record => record.id === current.id ? hydrated : record))
170
- }
171
- else {
172
- const nextRecords = [...records(), hydrated]
173
- records.set(nextRecords)
174
- page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
175
- }
147
+ upsert(hydrated, current)
176
148
  editorOpen.set(false)
177
149
  editing.set(null)
178
150
  pushToast('success', current ? 'Courier updated' : 'Courier added', { detail: hydrated.name })
@@ -185,18 +157,6 @@ async function saveCourier(payload: CourierPayload): Promise<void> {
185
157
  }
186
158
  }
187
159
 
188
- function requestDelete(record: CourierRecord): void {
189
- deleting.set(record)
190
- deleteError.set('')
191
- }
192
-
193
- function closeDelete(): void {
194
- if (deleteBusy())
195
- return
196
- deleting.set(null)
197
- deleteError.set('')
198
- }
199
-
200
160
  async function confirmDelete(): Promise<void> {
201
161
  const current = deleting()
202
162
  if (!current)
@@ -207,11 +167,8 @@ async function confirmDelete(): Promise<void> {
207
167
  await dashboardApi(`/api/dashboard/commerce/couriers/${current.id}`, {
208
168
  method: 'DELETE',
209
169
  })
210
- records.set(records().filter(record => record.id !== current.id))
170
+ remove(current)
211
171
  deleting.set(null)
212
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
213
- if (page() > lastPage)
214
- page.set(lastPage)
215
172
  pushToast('success', 'Courier deleted', { detail: current.name })
216
173
  }
217
174
  catch (error) {
@@ -222,10 +179,6 @@ async function confirmDelete(): Promise<void> {
222
179
  }
223
180
  }
224
181
 
225
- function goToPage(value: number): void {
226
- page.set(value)
227
- }
228
-
229
182
  onMount(() => {
230
183
  void loadCouriers()
231
184
  })
@@ -1,5 +1,6 @@
1
1
  <script client>
2
2
  import { dashboardApi } from '../../../../../functions/dashboard-api'
3
+ import { useRecordsDashboard } from '../../../../../functions/dashboard-list'
3
4
  import { pushToast } from '../../../../../functions/toasts'
4
5
 
5
6
  interface CourierRecord {
@@ -31,21 +32,48 @@ interface DeliveryRoutePayload {
31
32
  lastActive: number
32
33
  }
33
34
 
34
- const records = state<DeliveryRouteRecord[]>([])
35
+ function matchesQuery(records: DeliveryRouteRecord[], query: string): DeliveryRouteRecord[] {
36
+ return records.filter(record => [
37
+ record.id,
38
+ record.courier,
39
+ record.courier_record?.name,
40
+ record.vehicle,
41
+ record.stops,
42
+ record.delivery_time,
43
+ record.total_distance,
44
+ ].some(value => String(value || '').toLowerCase().includes(query)))
45
+ }
46
+
47
+ const {
48
+ records,
49
+ loading,
50
+ loadError,
51
+ search,
52
+ page,
53
+ perPage,
54
+ editorOpen,
55
+ editorMode,
56
+ editing,
57
+ saving,
58
+ editorError,
59
+ deleting,
60
+ deleteBusy,
61
+ deleteError,
62
+ filteredRecords,
63
+ paginatedRecords,
64
+ goToPage,
65
+ handleSearch,
66
+ openCreate,
67
+ openEdit,
68
+ closeEditor,
69
+ requestDelete,
70
+ closeDelete,
71
+ upsert,
72
+ remove,
73
+ clampPage,
74
+ } = useRecordsDashboard<DeliveryRouteRecord>({ filter: matchesQuery })
75
+
35
76
  const couriers = state<CourierRecord[]>([])
36
- const loading = state(true)
37
- const loadError = state('')
38
- const search = state('')
39
- const page = state(1)
40
- const perPage = state(10)
41
- const editorOpen = state(false)
42
- const editorMode = state<'create' | 'edit'>('create')
43
- const editing = state<DeliveryRouteRecord | null>(null)
44
- const saving = state(false)
45
- const editorError = state('')
46
- const deleting = state<DeliveryRouteRecord | null>(null)
47
- const deleteBusy = state(false)
48
- const deleteError = state('')
49
77
 
50
78
  function courierFor(id?: number | null): CourierRecord | undefined {
51
79
  return id ? couriers().find(courier => courier.id === id) : undefined
@@ -64,26 +92,6 @@ function routeTime(value: string | number): number {
64
92
  return new Date(String(value).replace(' ', 'T')).getTime()
65
93
  }
66
94
 
67
- function filteredRecords(): DeliveryRouteRecord[] {
68
- const query = search().trim().toLowerCase()
69
- if (!query)
70
- return records()
71
- return records().filter(record => [
72
- record.id,
73
- record.courier,
74
- record.courier_record?.name,
75
- record.vehicle,
76
- record.stops,
77
- record.delivery_time,
78
- record.total_distance,
79
- ].some(value => String(value || '').toLowerCase().includes(query)))
80
- }
81
-
82
- function paginatedRecords(): DeliveryRouteRecord[] {
83
- const start = (page() - 1) * perPage()
84
- return filteredRecords().slice(start, start + perPage())
85
- }
86
-
87
95
  function metricCards(): Array<{ label: string, value: string, detail: string }> {
88
96
  const totalStops = records().reduce((sum, record) => sum + record.stops, 0)
89
97
  const totalDistance = records().reduce((sum, record) => sum + record.total_distance, 0)
@@ -109,9 +117,7 @@ async function loadRoutes(): Promise<void> {
109
117
  throw new TypeError('Delivery route resources were not returned in the expected shape.')
110
118
  couriers.set(courierData)
111
119
  records.set(routeData.map(withCourier))
112
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
113
- if (page() > lastPage)
114
- page.set(lastPage)
120
+ clampPage()
115
121
  }
116
122
  catch (error) {
117
123
  loadError.set(error instanceof Error ? error.message : String(error))
@@ -122,33 +128,6 @@ async function loadRoutes(): Promise<void> {
122
128
  }
123
129
  }
124
130
 
125
- function handleSearch(value: string): void {
126
- search.set(value)
127
- page.set(1)
128
- }
129
-
130
- function openCreate(): void {
131
- editing.set(null)
132
- editorMode.set('create')
133
- editorError.set('')
134
- editorOpen.set(true)
135
- }
136
-
137
- function openEdit(record: DeliveryRouteRecord): void {
138
- editing.set(record)
139
- editorMode.set('edit')
140
- editorError.set('')
141
- editorOpen.set(true)
142
- }
143
-
144
- function closeEditor(): void {
145
- if (saving())
146
- return
147
- editorOpen.set(false)
148
- editing.set(null)
149
- editorError.set('')
150
- }
151
-
152
131
  function setEditorError(message: string): void {
153
132
  editorError.set(message)
154
133
  }
@@ -168,14 +147,7 @@ async function saveRoute(payload: DeliveryRoutePayload): Promise<void> {
168
147
  },
169
148
  )
170
149
  const hydrated = withCourier(updated)
171
- if (current) {
172
- records.set(records().map(record => record.id === current.id ? hydrated : record))
173
- }
174
- else {
175
- const nextRecords = [...records(), hydrated]
176
- records.set(nextRecords)
177
- page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
178
- }
150
+ upsert(hydrated, current)
179
151
  editorOpen.set(false)
180
152
  editing.set(null)
181
153
  pushToast('success', current ? 'Delivery route updated' : 'Delivery route added', { detail: `Route #${hydrated.id}` })
@@ -188,18 +160,6 @@ async function saveRoute(payload: DeliveryRoutePayload): Promise<void> {
188
160
  }
189
161
  }
190
162
 
191
- function requestDelete(record: DeliveryRouteRecord): void {
192
- deleting.set(record)
193
- deleteError.set('')
194
- }
195
-
196
- function closeDelete(): void {
197
- if (deleteBusy())
198
- return
199
- deleting.set(null)
200
- deleteError.set('')
201
- }
202
-
203
163
  async function confirmDelete(): Promise<void> {
204
164
  const current = deleting()
205
165
  if (!current)
@@ -210,11 +170,9 @@ async function confirmDelete(): Promise<void> {
210
170
  await dashboardApi(`/api/dashboard/commerce/delivery-routes/${current.id}`, {
211
171
  method: 'DELETE',
212
172
  })
213
- records.set(records().filter(record => record.id !== current.id))
173
+ remove(current)
214
174
  deleting.set(null)
215
- const lastPage = Math.max(1, Math.ceil(filteredRecords().length / perPage()))
216
- if (page() > lastPage)
217
- page.set(lastPage)
175
+ clampPage()
218
176
  pushToast('success', 'Delivery route deleted', { detail: `Route #${current.id}` })
219
177
  }
220
178
  catch (error) {
@@ -225,10 +183,6 @@ async function confirmDelete(): Promise<void> {
225
183
  }
226
184
  }
227
185
 
228
- function goToPage(value: number): void {
229
- page.set(value)
230
- }
231
-
232
186
  onMount(() => {
233
187
  void loadRoutes()
234
188
  })