@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.
- package/ai/skills/stacks-dashboard/SKILL.md +15 -0
- package/ai/skills/stacks-models/SKILL.md +6 -1
- package/ai/skills/stacks-native/CATALOG.md +6 -6
- package/ai/skills/stacks-storage/SKILL.md +43 -0
- package/ai/skills/stacks-testing/SKILL.md +38 -10
- package/app/Actions/Dashboard/Models/GetModels.ts +19 -14
- package/app/Actions/Dashboard/Models/GetSubscriberCount.ts +5 -2
- package/app/Actions/Dashboard/Models/GetUserCount.ts +5 -2
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +3 -11
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +6 -3
- package/app/Actions/Dashboard/Models/model-write.ts +2 -3
- package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +6 -2
- 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/functions/dashboard/model-access.ts +15 -0
- package/resources/plugins/preloader.ts +9 -1
|
@@ -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
|
})
|
|
@@ -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
|
import { formatCurrency, minorToInput } from '@stacksjs/commerce/money'
|
|
5
6
|
|
|
@@ -28,22 +29,47 @@ interface ShippingRatePayload {
|
|
|
28
29
|
rate: number
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
function matchesQuery(records: ShippingRateRecord[], query: string): ShippingRateRecord[] {
|
|
33
|
+
return records.filter(record => [
|
|
34
|
+
record.shipping_method?.name,
|
|
35
|
+
record.shipping_zone?.name,
|
|
36
|
+
record.weight_from,
|
|
37
|
+
record.weight_to,
|
|
38
|
+
minorToInput(record.rate, 'USD'),
|
|
39
|
+
].some(value => String(value ?? '').toLowerCase().includes(query)))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
records,
|
|
44
|
+
loading,
|
|
45
|
+
loadError,
|
|
46
|
+
search,
|
|
47
|
+
page,
|
|
48
|
+
perPage,
|
|
49
|
+
editorOpen,
|
|
50
|
+
editorMode,
|
|
51
|
+
editing,
|
|
52
|
+
saving,
|
|
53
|
+
editorError,
|
|
54
|
+
deleting,
|
|
55
|
+
deleteBusy,
|
|
56
|
+
deleteError,
|
|
57
|
+
filteredRecords,
|
|
58
|
+
paginatedRecords,
|
|
59
|
+
goToPage,
|
|
60
|
+
handleSearch,
|
|
61
|
+
openCreate,
|
|
62
|
+
openEdit,
|
|
63
|
+
closeEditor,
|
|
64
|
+
requestDelete,
|
|
65
|
+
closeDelete,
|
|
66
|
+
upsert,
|
|
67
|
+
remove,
|
|
68
|
+
clampPage,
|
|
69
|
+
} = useRecordsDashboard<ShippingRateRecord>({ filter: matchesQuery })
|
|
70
|
+
|
|
32
71
|
const methods = state<ShippingResourceOption[]>([])
|
|
33
72
|
const zones = state<ShippingResourceOption[]>([])
|
|
34
|
-
const loading = state(true)
|
|
35
|
-
const loadError = state('')
|
|
36
|
-
const search = state('')
|
|
37
|
-
const page = state(1)
|
|
38
|
-
const perPage = state(10)
|
|
39
|
-
const editorOpen = state(false)
|
|
40
|
-
const editorMode = state<'create' | 'edit'>('create')
|
|
41
|
-
const editing = state<ShippingRateRecord | null>(null)
|
|
42
|
-
const saving = state(false)
|
|
43
|
-
const editorError = state('')
|
|
44
|
-
const deleting = state<ShippingRateRecord | null>(null)
|
|
45
|
-
const deleteBusy = state(false)
|
|
46
|
-
const deleteError = state('')
|
|
47
73
|
|
|
48
74
|
function methodFor(id: number): ShippingResourceOption | undefined {
|
|
49
75
|
return methods().find(method => method.id === id)
|
|
@@ -61,24 +87,6 @@ function withRelations(record: ShippingRateRecord): ShippingRateRecord {
|
|
|
61
87
|
}
|
|
62
88
|
}
|
|
63
89
|
|
|
64
|
-
function filteredRecords(): ShippingRateRecord[] {
|
|
65
|
-
const query = search().trim().toLowerCase()
|
|
66
|
-
if (!query)
|
|
67
|
-
return records()
|
|
68
|
-
return records().filter(record => [
|
|
69
|
-
record.shipping_method?.name,
|
|
70
|
-
record.shipping_zone?.name,
|
|
71
|
-
record.weight_from,
|
|
72
|
-
record.weight_to,
|
|
73
|
-
minorToInput(record.rate, 'USD'),
|
|
74
|
-
].some(value => String(value ?? '').toLowerCase().includes(query)))
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function paginatedRecords(): ShippingRateRecord[] {
|
|
78
|
-
const start = (page() - 1) * perPage()
|
|
79
|
-
return filteredRecords().slice(start, start + perPage())
|
|
80
|
-
}
|
|
81
|
-
|
|
82
90
|
function uniqueZoneCount(): number {
|
|
83
91
|
return new Set(records().map(record => record.shipping_zone_id)).size
|
|
84
92
|
}
|
|
@@ -114,9 +122,7 @@ async function loadRates(): Promise<void> {
|
|
|
114
122
|
methods.set(methodData)
|
|
115
123
|
zones.set(zoneData)
|
|
116
124
|
records.set(rateData.map(withRelations))
|
|
117
|
-
|
|
118
|
-
if (page() > lastPage)
|
|
119
|
-
page.set(lastPage)
|
|
125
|
+
clampPage()
|
|
120
126
|
}
|
|
121
127
|
catch (error) {
|
|
122
128
|
loadError.set(error instanceof Error ? error.message : String(error))
|
|
@@ -127,33 +133,6 @@ async function loadRates(): Promise<void> {
|
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
function handleSearch(value: string): void {
|
|
131
|
-
search.set(value)
|
|
132
|
-
page.set(1)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function openCreate(): void {
|
|
136
|
-
editing.set(null)
|
|
137
|
-
editorMode.set('create')
|
|
138
|
-
editorError.set('')
|
|
139
|
-
editorOpen.set(true)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function openEdit(record: ShippingRateRecord): void {
|
|
143
|
-
editing.set(record)
|
|
144
|
-
editorMode.set('edit')
|
|
145
|
-
editorError.set('')
|
|
146
|
-
editorOpen.set(true)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function closeEditor(): void {
|
|
150
|
-
if (saving())
|
|
151
|
-
return
|
|
152
|
-
editorOpen.set(false)
|
|
153
|
-
editing.set(null)
|
|
154
|
-
editorError.set('')
|
|
155
|
-
}
|
|
156
|
-
|
|
157
136
|
function setEditorError(message: string): void {
|
|
158
137
|
editorError.set(message)
|
|
159
138
|
}
|
|
@@ -173,14 +152,7 @@ async function saveRate(payload: ShippingRatePayload): Promise<void> {
|
|
|
173
152
|
},
|
|
174
153
|
)
|
|
175
154
|
const hydrated = withRelations(updated)
|
|
176
|
-
|
|
177
|
-
records.set(records().map(record => record.id === current.id ? hydrated : record))
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
const nextRecords = [...records(), hydrated]
|
|
181
|
-
records.set(nextRecords)
|
|
182
|
-
page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
|
|
183
|
-
}
|
|
155
|
+
upsert(hydrated, current)
|
|
184
156
|
editorOpen.set(false)
|
|
185
157
|
editing.set(null)
|
|
186
158
|
pushToast('success', current ? 'Shipping rate updated' : 'Shipping rate added', {
|
|
@@ -195,18 +167,6 @@ async function saveRate(payload: ShippingRatePayload): Promise<void> {
|
|
|
195
167
|
}
|
|
196
168
|
}
|
|
197
169
|
|
|
198
|
-
function requestDelete(record: ShippingRateRecord): void {
|
|
199
|
-
deleting.set(record)
|
|
200
|
-
deleteError.set('')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function closeDelete(): void {
|
|
204
|
-
if (deleteBusy())
|
|
205
|
-
return
|
|
206
|
-
deleting.set(null)
|
|
207
|
-
deleteError.set('')
|
|
208
|
-
}
|
|
209
|
-
|
|
210
170
|
async function confirmDelete(): Promise<void> {
|
|
211
171
|
const current = deleting()
|
|
212
172
|
if (!current)
|
|
@@ -217,11 +177,9 @@ async function confirmDelete(): Promise<void> {
|
|
|
217
177
|
await dashboardApi(`/api/dashboard/commerce/shipping-rates/${current.id}`, {
|
|
218
178
|
method: 'DELETE',
|
|
219
179
|
})
|
|
220
|
-
|
|
180
|
+
remove(current)
|
|
221
181
|
deleting.set(null)
|
|
222
|
-
|
|
223
|
-
if (page() > lastPage)
|
|
224
|
-
page.set(lastPage)
|
|
182
|
+
clampPage()
|
|
225
183
|
pushToast('success', 'Shipping rate deleted', {
|
|
226
184
|
detail: `${current.shipping_method?.name || 'Method'} for ${current.shipping_zone?.name || 'zone'}`,
|
|
227
185
|
})
|
|
@@ -234,10 +192,6 @@ async function confirmDelete(): Promise<void> {
|
|
|
234
192
|
}
|
|
235
193
|
}
|
|
236
194
|
|
|
237
|
-
function goToPage(value: number): void {
|
|
238
|
-
page.set(value)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
195
|
onMount(() => {
|
|
242
196
|
void loadRates()
|
|
243
197
|
})
|
|
@@ -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 SerializedList = string | string[] | null | undefined
|
|
@@ -30,21 +31,47 @@ interface ShippingZonePayload {
|
|
|
30
31
|
status: 'active' | 'inactive' | 'draft'
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
function matchesQuery(records: ShippingZoneRecord[], query: string): ShippingZoneRecord[] {
|
|
35
|
+
return records.filter(record => [
|
|
36
|
+
record.name,
|
|
37
|
+
record.status,
|
|
38
|
+
record.shipping_method?.name,
|
|
39
|
+
...listTokens(record.countries),
|
|
40
|
+
...listTokens(record.regions),
|
|
41
|
+
...listTokens(record.postal_codes),
|
|
42
|
+
].some(value => String(value || '').toLowerCase().includes(query)))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
records,
|
|
47
|
+
loading,
|
|
48
|
+
loadError,
|
|
49
|
+
search,
|
|
50
|
+
page,
|
|
51
|
+
perPage,
|
|
52
|
+
editorOpen,
|
|
53
|
+
editorMode,
|
|
54
|
+
editing,
|
|
55
|
+
saving,
|
|
56
|
+
editorError,
|
|
57
|
+
deleting,
|
|
58
|
+
deleteBusy,
|
|
59
|
+
deleteError,
|
|
60
|
+
filteredRecords,
|
|
61
|
+
paginatedRecords,
|
|
62
|
+
goToPage,
|
|
63
|
+
handleSearch,
|
|
64
|
+
openCreate,
|
|
65
|
+
openEdit,
|
|
66
|
+
closeEditor,
|
|
67
|
+
requestDelete,
|
|
68
|
+
closeDelete,
|
|
69
|
+
upsert,
|
|
70
|
+
remove,
|
|
71
|
+
clampPage,
|
|
72
|
+
} = useRecordsDashboard<ShippingZoneRecord>({ filter: matchesQuery })
|
|
73
|
+
|
|
34
74
|
const methods = state<ShippingMethodOption[]>([])
|
|
35
|
-
const loading = state(true)
|
|
36
|
-
const loadError = state('')
|
|
37
|
-
const search = state('')
|
|
38
|
-
const page = state(1)
|
|
39
|
-
const perPage = state(10)
|
|
40
|
-
const editorOpen = state(false)
|
|
41
|
-
const editorMode = state<'create' | 'edit'>('create')
|
|
42
|
-
const editing = state<ShippingZoneRecord | null>(null)
|
|
43
|
-
const saving = state(false)
|
|
44
|
-
const editorError = state('')
|
|
45
|
-
const deleting = state<ShippingZoneRecord | null>(null)
|
|
46
|
-
const deleteBusy = state(false)
|
|
47
|
-
const deleteError = state('')
|
|
48
75
|
|
|
49
76
|
function listTokens(value: SerializedList): string[] {
|
|
50
77
|
if (Array.isArray(value))
|
|
@@ -73,25 +100,6 @@ function withMethod(record: ShippingZoneRecord): ShippingZoneRecord {
|
|
|
73
100
|
}
|
|
74
101
|
}
|
|
75
102
|
|
|
76
|
-
function filteredRecords(): ShippingZoneRecord[] {
|
|
77
|
-
const query = search().trim().toLowerCase()
|
|
78
|
-
if (!query)
|
|
79
|
-
return records()
|
|
80
|
-
return records().filter(record => [
|
|
81
|
-
record.name,
|
|
82
|
-
record.status,
|
|
83
|
-
record.shipping_method?.name,
|
|
84
|
-
...listTokens(record.countries),
|
|
85
|
-
...listTokens(record.regions),
|
|
86
|
-
...listTokens(record.postal_codes),
|
|
87
|
-
].some(value => String(value || '').toLowerCase().includes(query)))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function paginatedRecords(): ShippingZoneRecord[] {
|
|
91
|
-
const start = (page() - 1) * perPage()
|
|
92
|
-
return filteredRecords().slice(start, start + perPage())
|
|
93
|
-
}
|
|
94
|
-
|
|
95
103
|
function uniqueCount(field: 'countries' | 'regions'): number {
|
|
96
104
|
return new Set(records().flatMap(record => listTokens(record[field]))).size
|
|
97
105
|
}
|
|
@@ -122,9 +130,7 @@ async function loadZones(): Promise<void> {
|
|
|
122
130
|
throw new TypeError('Shipping zone resources were not returned as arrays.')
|
|
123
131
|
methods.set(methodData)
|
|
124
132
|
records.set(zoneData.map(withMethod))
|
|
125
|
-
|
|
126
|
-
if (page() > lastPage)
|
|
127
|
-
page.set(lastPage)
|
|
133
|
+
clampPage()
|
|
128
134
|
}
|
|
129
135
|
catch (error) {
|
|
130
136
|
loadError.set(error instanceof Error ? error.message : String(error))
|
|
@@ -135,33 +141,6 @@ async function loadZones(): Promise<void> {
|
|
|
135
141
|
}
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
function handleSearch(value: string): void {
|
|
139
|
-
search.set(value)
|
|
140
|
-
page.set(1)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function openCreate(): void {
|
|
144
|
-
editing.set(null)
|
|
145
|
-
editorMode.set('create')
|
|
146
|
-
editorError.set('')
|
|
147
|
-
editorOpen.set(true)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function openEdit(record: ShippingZoneRecord): void {
|
|
151
|
-
editing.set(record)
|
|
152
|
-
editorMode.set('edit')
|
|
153
|
-
editorError.set('')
|
|
154
|
-
editorOpen.set(true)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function closeEditor(): void {
|
|
158
|
-
if (saving())
|
|
159
|
-
return
|
|
160
|
-
editorOpen.set(false)
|
|
161
|
-
editing.set(null)
|
|
162
|
-
editorError.set('')
|
|
163
|
-
}
|
|
164
|
-
|
|
165
144
|
function setEditorError(message: string): void {
|
|
166
145
|
editorError.set(message)
|
|
167
146
|
}
|
|
@@ -181,14 +160,7 @@ async function saveZone(payload: ShippingZonePayload): Promise<void> {
|
|
|
181
160
|
},
|
|
182
161
|
)
|
|
183
162
|
const hydrated = withMethod(updated)
|
|
184
|
-
|
|
185
|
-
records.set(records().map(record => record.id === current.id ? hydrated : record))
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
const nextRecords = [...records(), hydrated]
|
|
189
|
-
records.set(nextRecords)
|
|
190
|
-
page.set(Math.max(1, Math.ceil(nextRecords.length / perPage())))
|
|
191
|
-
}
|
|
163
|
+
upsert(hydrated, current)
|
|
192
164
|
editorOpen.set(false)
|
|
193
165
|
editing.set(null)
|
|
194
166
|
pushToast('success', current ? 'Shipping zone updated' : 'Shipping zone added', {
|
|
@@ -203,18 +175,6 @@ async function saveZone(payload: ShippingZonePayload): Promise<void> {
|
|
|
203
175
|
}
|
|
204
176
|
}
|
|
205
177
|
|
|
206
|
-
function requestDelete(record: ShippingZoneRecord): 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/shipping-zones/${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', 'Shipping zone deleted', { detail: current.name })
|
|
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 loadZones()
|
|
249
203
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script client>
|
|
2
|
+
import { formErrorMessages } from '../../../../functions/dashboard-form'
|
|
2
3
|
import type {
|
|
3
4
|
PrintDeviceRecord,
|
|
4
5
|
PrintDeviceStatus,
|
|
@@ -51,10 +52,7 @@ function submit(): void {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function errorMessages(): string[] {
|
|
54
|
-
return
|
|
55
|
-
const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
|
|
56
|
-
return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
|
|
57
|
-
})
|
|
55
|
+
return formErrorMessages(errors())
|
|
58
56
|
}
|
|
59
57
|
</script>
|
|
60
58
|
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from '../../../../app/Actions/Dashboard/Commerce/print-device-records'
|
|
7
7
|
import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
|
|
8
8
|
import { pushToast } from '../../../../functions/toasts'
|
|
9
|
+
import { pageControls } from '../../../../functions/dashboard-list'
|
|
9
10
|
|
|
10
11
|
interface PrintDevicesResponse {
|
|
11
12
|
records: PrintDeviceRecord[]
|
|
@@ -234,13 +235,7 @@ async function deletePrintDevice(): Promise<void> {
|
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
|
|
237
|
-
|
|
238
|
-
page.set(Math.max(1, page() - 1))
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function nextPage(): void {
|
|
242
|
-
page.set(Math.min(pageCount(), page() + 1))
|
|
243
|
-
}
|
|
238
|
+
const { previousPage, nextPage } = pageControls(page, pageCount)
|
|
244
239
|
|
|
245
240
|
onMount(() => {
|
|
246
241
|
void loadPrintDevices()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script client>
|
|
2
2
|
import type { ProductWaitlistOptions, ProductWaitlistRecord, ProductWaitlistWritePayload } from '../../../../app/Actions/Dashboard/Commerce/product-waitlist-records'
|
|
3
|
+
import { formErrorMessages } from '../../../../functions/dashboard-form'
|
|
3
4
|
|
|
4
5
|
type DialogMode = 'add' | 'edit'
|
|
5
6
|
|
|
@@ -94,10 +95,7 @@ function submitForm(): void {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
function errorMessages(): string[] {
|
|
97
|
-
return
|
|
98
|
-
const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
|
|
99
|
-
return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
|
|
100
|
-
})
|
|
98
|
+
return formErrorMessages(errors())
|
|
101
99
|
}
|
|
102
100
|
</script>
|
|
103
101
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script client>
|
|
2
2
|
import type { RestaurantWaitlistOptions, RestaurantWaitlistRecord, RestaurantWaitlistWritePayload } from '../../../../app/Actions/Dashboard/Commerce/restaurant-waitlist-records'
|
|
3
3
|
import { restaurantWaitlistDateTimeLocal, restaurantWaitlistTimestamp } from '../../../../app/Actions/Dashboard/Commerce/restaurant-waitlist-records'
|
|
4
|
+
import { formErrorMessages } from '../../../../functions/dashboard-form'
|
|
4
5
|
|
|
5
6
|
type DialogMode = 'add' | 'edit'
|
|
6
7
|
|
|
@@ -104,10 +105,7 @@ function submitForm(): void {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
function errorMessages(): string[] {
|
|
107
|
-
return
|
|
108
|
-
const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
|
|
109
|
-
return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
|
|
110
|
-
})
|
|
108
|
+
return formErrorMessages(errors())
|
|
111
109
|
}
|
|
112
110
|
</script>
|
|
113
111
|
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { formatCurrency } from '@stacksjs/commerce/money'
|
|
9
9
|
import { dashboardApi } from '../../../../functions/dashboard-api'
|
|
10
10
|
import { pushToast } from '../../../../functions/toasts'
|
|
11
|
+
import { pageControls } from '../../../../functions/dashboard-list'
|
|
11
12
|
|
|
12
13
|
const emptySummary: AbandonedCartSummary = {
|
|
13
14
|
open: 0,
|
|
@@ -162,15 +163,7 @@ async function createCampaign(payload: Record<string, unknown>): Promise<void> {
|
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
if (page() > 1)
|
|
167
|
-
page.set(page() - 1)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function nextPage(): void {
|
|
171
|
-
if (page() < totalPages())
|
|
172
|
-
page.set(page() + 1)
|
|
173
|
-
}
|
|
166
|
+
const { previousPage, nextPage } = pageControls(page, totalPages)
|
|
174
167
|
|
|
175
168
|
onMount(() => {
|
|
176
169
|
void loadCarts()
|