@stacksjs/defaults 0.75.231 → 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.
- package/ai/skills/stacks-native/CATALOG.md +4 -4
- package/ai/skills/stacks-storage/SKILL.md +43 -0
- package/ai/skills/stacks-testing/SKILL.md +38 -10
- package/functions/dashboard-form.test.ts +41 -0
- package/functions/dashboard-form.ts +30 -0
- package/functions/dashboard-list.test.ts +228 -0
- package/functions/dashboard-list.ts +257 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/project/storage/framework/types/browser-composables.d.ts +56 -0
- package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +45 -92
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +46 -92
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +46 -92
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +44 -90
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +45 -91
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +2 -7
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +2 -4
- package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +2 -9
- package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +2 -9
- package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +2 -9
- package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +2 -9
- package/resources/plugins/preloader.ts +9 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
+
remove(current)
|
|
214
174
|
deleting.set(null)
|
|
215
|
-
|
|
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
|
})
|
|
@@ -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 LicenseKeyStatus = 'active' | 'inactive' | 'unassigned'
|
|
@@ -42,21 +43,48 @@ interface LicenseKeyPayload {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const emptyOptions: LicenseKeyOptions = { customers: [], products: [], orders: [] }
|
|
45
|
-
|
|
46
|
+
function matchesQuery(records: LicenseKeyRecord[], query: string): LicenseKeyRecord[] {
|
|
47
|
+
return records.filter(record => [
|
|
48
|
+
record.key,
|
|
49
|
+
record.template,
|
|
50
|
+
record.status,
|
|
51
|
+
record.customer?.label,
|
|
52
|
+
record.customer?.detail,
|
|
53
|
+
record.product?.label,
|
|
54
|
+
record.order?.label,
|
|
55
|
+
].some(value => String(value || '').toLowerCase().includes(query)))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
records,
|
|
60
|
+
loading,
|
|
61
|
+
loadError,
|
|
62
|
+
search,
|
|
63
|
+
page,
|
|
64
|
+
perPage,
|
|
65
|
+
editorOpen,
|
|
66
|
+
editorMode,
|
|
67
|
+
editing,
|
|
68
|
+
saving,
|
|
69
|
+
editorError,
|
|
70
|
+
deleting,
|
|
71
|
+
deleteBusy,
|
|
72
|
+
deleteError,
|
|
73
|
+
filteredRecords,
|
|
74
|
+
paginatedRecords,
|
|
75
|
+
goToPage,
|
|
76
|
+
handleSearch,
|
|
77
|
+
openCreate,
|
|
78
|
+
openEdit,
|
|
79
|
+
closeEditor,
|
|
80
|
+
requestDelete,
|
|
81
|
+
closeDelete,
|
|
82
|
+
upsert,
|
|
83
|
+
remove,
|
|
84
|
+
clampPage,
|
|
85
|
+
} = useRecordsDashboard<LicenseKeyRecord>({ filter: matchesQuery })
|
|
86
|
+
|
|
46
87
|
const options = state<LicenseKeyOptions>(emptyOptions)
|
|
47
|
-
const loading = state(true)
|
|
48
|
-
const loadError = state('')
|
|
49
|
-
const search = state('')
|
|
50
|
-
const page = state(1)
|
|
51
|
-
const perPage = state(10)
|
|
52
|
-
const editorOpen = state(false)
|
|
53
|
-
const editorMode = state<'create' | 'edit'>('create')
|
|
54
|
-
const editing = state<LicenseKeyRecord | null>(null)
|
|
55
|
-
const saving = state(false)
|
|
56
|
-
const editorError = state('')
|
|
57
|
-
const deleting = state<LicenseKeyRecord | null>(null)
|
|
58
|
-
const deleteBusy = state(false)
|
|
59
|
-
const deleteError = state('')
|
|
60
88
|
|
|
61
89
|
function optionFor(items: RelatedSummary[], id?: number | null): RelatedSummary | null {
|
|
62
90
|
return id ? items.find(item => item.id === id) || null : null
|
|
@@ -78,26 +106,6 @@ function dateTime(value: string): number {
|
|
|
78
106
|
return new Date(value.replace(' ', 'T')).getTime()
|
|
79
107
|
}
|
|
80
108
|
|
|
81
|
-
function filteredRecords(): LicenseKeyRecord[] {
|
|
82
|
-
const query = search().trim().toLowerCase()
|
|
83
|
-
if (!query)
|
|
84
|
-
return records()
|
|
85
|
-
return records().filter(record => [
|
|
86
|
-
record.key,
|
|
87
|
-
record.template,
|
|
88
|
-
record.status,
|
|
89
|
-
record.customer?.label,
|
|
90
|
-
record.customer?.detail,
|
|
91
|
-
record.product?.label,
|
|
92
|
-
record.order?.label,
|
|
93
|
-
].some(value => String(value || '').toLowerCase().includes(query)))
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function paginatedRecords(): LicenseKeyRecord[] {
|
|
97
|
-
const start = (page() - 1) * perPage()
|
|
98
|
-
return filteredRecords().slice(start, start + perPage())
|
|
99
|
-
}
|
|
100
|
-
|
|
101
109
|
function metricCards(): Array<{ label: string, value: string, detail: string }> {
|
|
102
110
|
const expiryCutoff = Date.now() + (30 * 24 * 60 * 60 * 1000)
|
|
103
111
|
const expiring = records().filter((record) => {
|
|
@@ -124,9 +132,7 @@ async function loadKeys(): Promise<void> {
|
|
|
124
132
|
throw new TypeError('License key resources were not returned in the expected shape.')
|
|
125
133
|
options.set(optionData)
|
|
126
134
|
records.set(keyData.map(withRelationships))
|
|
127
|
-
|
|
128
|
-
if (page() > lastPage)
|
|
129
|
-
page.set(lastPage)
|
|
135
|
+
clampPage()
|
|
130
136
|
}
|
|
131
137
|
catch (error) {
|
|
132
138
|
loadError.set(error instanceof Error ? error.message : String(error))
|
|
@@ -137,33 +143,6 @@ async function loadKeys(): Promise<void> {
|
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
145
|
|
|
140
|
-
function handleSearch(value: string): void {
|
|
141
|
-
search.set(value)
|
|
142
|
-
page.set(1)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function openCreate(): void {
|
|
146
|
-
editing.set(null)
|
|
147
|
-
editorMode.set('create')
|
|
148
|
-
editorError.set('')
|
|
149
|
-
editorOpen.set(true)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function openEdit(record: LicenseKeyRecord): void {
|
|
153
|
-
editing.set(record)
|
|
154
|
-
editorMode.set('edit')
|
|
155
|
-
editorError.set('')
|
|
156
|
-
editorOpen.set(true)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function closeEditor(): void {
|
|
160
|
-
if (saving())
|
|
161
|
-
return
|
|
162
|
-
editorOpen.set(false)
|
|
163
|
-
editing.set(null)
|
|
164
|
-
editorError.set('')
|
|
165
|
-
}
|
|
166
|
-
|
|
167
146
|
function setEditorError(message: string): void {
|
|
168
147
|
editorError.set(message)
|
|
169
148
|
}
|
|
@@ -183,14 +162,7 @@ async function saveKey(payload: LicenseKeyPayload): Promise<void> {
|
|
|
183
162
|
},
|
|
184
163
|
)
|
|
185
164
|
const hydrated = withRelationships(updated)
|
|
186
|
-
|
|
187
|
-
records.set(records().map(record => record.id === current.id ? hydrated : record))
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
const nextRecords = [...records(), hydrated]
|
|
191
|
-
records.set(nextRecords)
|
|
192
|
-
page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
|
|
193
|
-
}
|
|
165
|
+
upsert(hydrated, current)
|
|
194
166
|
editorOpen.set(false)
|
|
195
167
|
editing.set(null)
|
|
196
168
|
pushToast('success', current ? 'License key updated' : 'License key added', { detail: hydrated.key })
|
|
@@ -203,18 +175,6 @@ async function saveKey(payload: LicenseKeyPayload): Promise<void> {
|
|
|
203
175
|
}
|
|
204
176
|
}
|
|
205
177
|
|
|
206
|
-
function requestDelete(record: LicenseKeyRecord): void {
|
|
207
|
-
deleting.set(record)
|
|
208
|
-
deleteError.set('')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function closeDelete(): void {
|
|
212
|
-
if (deleteBusy())
|
|
213
|
-
return
|
|
214
|
-
deleting.set(null)
|
|
215
|
-
deleteError.set('')
|
|
216
|
-
}
|
|
217
|
-
|
|
218
178
|
async function confirmDelete(): Promise<void> {
|
|
219
179
|
const current = deleting()
|
|
220
180
|
if (!current)
|
|
@@ -225,11 +185,9 @@ async function confirmDelete(): Promise<void> {
|
|
|
225
185
|
await dashboardApi(`/api/dashboard/commerce/license-keys/${current.id}`, {
|
|
226
186
|
method: 'DELETE',
|
|
227
187
|
})
|
|
228
|
-
|
|
188
|
+
remove(current)
|
|
229
189
|
deleting.set(null)
|
|
230
|
-
|
|
231
|
-
if (page() > lastPage)
|
|
232
|
-
page.set(lastPage)
|
|
190
|
+
clampPage()
|
|
233
191
|
pushToast('success', 'License key deleted', { detail: current.key })
|
|
234
192
|
}
|
|
235
193
|
catch (error) {
|
|
@@ -240,10 +198,6 @@ async function confirmDelete(): Promise<void> {
|
|
|
240
198
|
}
|
|
241
199
|
}
|
|
242
200
|
|
|
243
|
-
function goToPage(value: number): void {
|
|
244
|
-
page.set(value)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
201
|
onMount(() => {
|
|
248
202
|
void loadKeys()
|
|
249
203
|
})
|