@stacksjs/defaults 0.75.279 → 0.75.280
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/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/resources/components/Dashboard/Analytics/CampaignAnalytics.stx +28 -30
- package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +72 -29
- package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +8 -29
- package/resources/components/Dashboard/Commerce/CommerceCouponsTable.stx +69 -49
- package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +8 -27
- package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +70 -48
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +8 -29
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsTable.stx +72 -82
- package/resources/components/Dashboard/Commerce/CommerceManufacturersDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/CommerceOrdersTable.stx +76 -49
- package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +81 -34
- package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +11 -37
- package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +106 -77
- package/resources/components/Dashboard/Commerce/CommerceTaxesDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceUnitsDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceVariantsDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +1 -11
- package/resources/components/Dashboard/Commerce/Delivery/CouriersTable.stx +55 -82
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +57 -86
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveriesDashboard.stx +1 -24
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +62 -80
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +58 -99
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsDashboard.stx +1 -23
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +51 -86
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +58 -71
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +85 -110
- package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/PrintDevicesTable.stx +62 -57
- package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +7 -24
- package/resources/components/Dashboard/Marketing/AbandonedCartsTable.stx +63 -59
- package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +7 -25
- package/resources/components/Dashboard/Marketing/CampaignsTable.stx +80 -57
- package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +7 -27
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +66 -71
- package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +7 -25
- package/resources/components/Dashboard/Marketing/SocialPostsTable.stx +75 -62
- package/resources/components/Dashboard/Monitoring/ErrorDashboard.stx +9 -50
- package/resources/components/Dashboard/Monitoring/ErrorTable.stx +68 -97
- package/resources/components/Dashboard/Notifications/NotificationDeliveries.stx +22 -44
- package/resources/components/Dashboard/Notifications/NotificationDeliveryHistory.stx +13 -4
- package/resources/components/Dashboard/Notifications/NotificationDeliveryTable.stx +74 -86
- package/resources/components/Dashboard/UI/Table.stx +41 -2
|
@@ -1,61 +1,83 @@
|
|
|
1
|
+
@import('../UI/Table')
|
|
2
|
+
|
|
1
3
|
<script client>
|
|
2
4
|
import type { CommerceCustomerRecord } from '../../../../app/Actions/Dashboard/Commerce/commerce-customer-records'
|
|
3
|
-
import { formatCurrency } from '@stacksjs/commerce/money'
|
|
4
5
|
|
|
5
6
|
const records = useReactiveProp('records', [] as CommerceCustomerRecord[])
|
|
6
7
|
const loading = useReactiveProp('loading', false)
|
|
8
|
+
/** The parent's sort select: a change hands the order back from a header sort. */
|
|
9
|
+
const sortResetKey = useReactiveProp<unknown>('sortResetKey', '')
|
|
10
|
+
/** The parent's search and filters: a change returns to the first page. */
|
|
11
|
+
const pageResetKey = useReactiveProp<unknown>('pageResetKey', '')
|
|
7
12
|
const currency = useReactiveProp('currency', 'USD')
|
|
8
13
|
const emit = defineEmits()
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
15
|
+
/** Only the customer cell is slotted, for its initial. */
|
|
16
|
+
const COLUMNS = [
|
|
17
|
+
{ key: 'nameLabel', label: 'Customer', sortable: true, slotted: true },
|
|
18
|
+
{ key: 'emailLabel', label: 'Contact', sortable: true, subtitleKey: 'phoneLabel', class: 'text-gray-700 dark:text-neutral-200' },
|
|
19
|
+
// Money is integer minor units; the row carries the store currency so the
|
|
20
|
+
// column formats it through formatCurrency, not as a bare number.
|
|
21
|
+
{ key: 'totalSpent', label: 'Recorded spend', type: 'currency' as const, currencyKey: 'currency', sortable: true, class: 'font-medium' },
|
|
22
|
+
{ key: 'lastOrder', label: 'Last order', type: 'date' as const, sortable: true, emptyText: 'No order recorded', class: 'whitespace-nowrap' },
|
|
23
|
+
{
|
|
24
|
+
key: 'status',
|
|
25
|
+
label: 'Status',
|
|
26
|
+
type: 'badge' as const,
|
|
27
|
+
sortable: true,
|
|
28
|
+
badgeClasses: {
|
|
29
|
+
Active: 'bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300',
|
|
30
|
+
Inactive: 'bg-gray-100 text-gray-600 dark:bg-neutral-700 dark:text-neutral-300',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
]
|
|
13
34
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
const ROW_ACTIONS = [
|
|
36
|
+
{ action: 'view', label: 'View', icon: 'i-hugeicons-view' },
|
|
37
|
+
{ action: 'edit', label: 'Edit', icon: 'i-hugeicons-edit-02' },
|
|
38
|
+
{ action: 'delete', label: 'Delete', icon: 'i-hugeicons-delete-02', class: 'text-red-500' },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const rows = derived(() => records().map(record => ({
|
|
42
|
+
...record,
|
|
43
|
+
currency: currency(),
|
|
44
|
+
nameLabel: record.name || 'Unnamed customer',
|
|
45
|
+
initial: (record.name || '?').slice(0, 1).toUpperCase(),
|
|
46
|
+
idLabel: `ID ${record.id || 'not recorded'}`,
|
|
47
|
+
emailLabel: record.email || 'No email recorded',
|
|
48
|
+
phoneLabel: record.phone || 'No phone recorded',
|
|
49
|
+
})))
|
|
27
50
|
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
return 'bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300'
|
|
31
|
-
if (record.status === 'Inactive')
|
|
32
|
-
return 'bg-gray-100 text-gray-600 dark:bg-neutral-700 dark:text-neutral-300'
|
|
33
|
-
return 'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300'
|
|
51
|
+
function handleRowAction(payload: { action: string, row: CommerceCustomerRecord }): void {
|
|
52
|
+
emit(payload.action, payload.row)
|
|
34
53
|
}
|
|
35
54
|
</script>
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</div>
|
|
56
|
+
{{--
|
|
57
|
+
No `searchable` or `filterable`: the parent dashboard owns the search box
|
|
58
|
+
and the filter selects. Table pages the whole filtered list the parent passes.
|
|
59
|
+
--}}
|
|
60
|
+
<Table
|
|
61
|
+
:sort-reset-key="sortResetKey()"
|
|
62
|
+
:page-reset-key="pageResetKey()"
|
|
63
|
+
:columns="COLUMNS"
|
|
64
|
+
:data="rows()"
|
|
65
|
+
:loading="loading()"
|
|
66
|
+
:row-actions="ROW_ACTIONS"
|
|
67
|
+
action-label-key="nameLabel"
|
|
68
|
+
paginated
|
|
69
|
+
:page-size="15"
|
|
70
|
+
empty-title="No customers found"
|
|
71
|
+
empty-description="Adjust the filters or add a native Customer record."
|
|
72
|
+
@row-action="handleRowAction"
|
|
73
|
+
>
|
|
74
|
+
<template #cell="{ row }">
|
|
75
|
+
<div class="flex gap-3 items-center">
|
|
76
|
+
<span class="flex items-center justify-center shrink-0 h-9 w-9 font-semibold text-blue-700 text-sm dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md" x-text="row.initial"></span>
|
|
77
|
+
<div class="min-w-0">
|
|
78
|
+
<p class="font-medium text-gray-900 text-sm dark:text-white" x-text="row.nameLabel"></p>
|
|
79
|
+
<p class="mt-1 font-mono text-gray-400 text-xs dark:text-neutral-500" x-text="row.idLabel"></p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
83
|
+
</Table>
|
|
@@ -7,7 +7,6 @@ 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'
|
|
11
10
|
|
|
12
11
|
interface GiftCardIndexResponse {
|
|
13
12
|
records: GiftCardRecord[]
|
|
@@ -49,8 +48,6 @@ const statusFilter = state('all')
|
|
|
49
48
|
const enabledFilter = state('all')
|
|
50
49
|
const deliveryFilter = state('all')
|
|
51
50
|
const sort = state('newest')
|
|
52
|
-
const page = state(1)
|
|
53
|
-
const pageSize = 20
|
|
54
51
|
const loading = state(true)
|
|
55
52
|
const loadError = state('')
|
|
56
53
|
const dialogOpen = state(false)
|
|
@@ -98,15 +95,6 @@ function filteredRecords(): GiftCardRecord[] {
|
|
|
98
95
|
})
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
function pageCount(): number {
|
|
102
|
-
return Math.max(1, Math.ceil(filteredRecords().length / pageSize))
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function visibleRecords(): GiftCardRecord[] {
|
|
106
|
-
const selectedPage = Math.min(page(), pageCount())
|
|
107
|
-
return filteredRecords().slice((selectedPage - 1) * pageSize, selectedPage * pageSize)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
98
|
function metricCards(): Array<{ label: string, value: string, detail: string }> {
|
|
111
99
|
const current = summary()
|
|
112
100
|
return [
|
|
@@ -140,14 +128,13 @@ function exportHref(): string {
|
|
|
140
128
|
return `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${csv}`)}`
|
|
141
129
|
}
|
|
142
130
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
sort()
|
|
149
|
-
|
|
150
|
-
})
|
|
131
|
+
/**
|
|
132
|
+
* What the table is showing: a change returns it to the first page, as this
|
|
133
|
+
* page's own pager did before the table took over paging.
|
|
134
|
+
*/
|
|
135
|
+
function viewKey(): string {
|
|
136
|
+
return JSON.stringify([search(), statusFilter(), enabledFilter(), deliveryFilter(), sort()])
|
|
137
|
+
}
|
|
151
138
|
|
|
152
139
|
async function loadGiftCards(): Promise<void> {
|
|
153
140
|
loading.set(true)
|
|
@@ -156,7 +143,6 @@ async function loadGiftCards(): Promise<void> {
|
|
|
156
143
|
const data = await dashboardApi<GiftCardIndexResponse>('/api/dashboard/commerce/gift-cards')
|
|
157
144
|
records.set(Array.isArray(data.records) ? data.records : [])
|
|
158
145
|
summary.set(data.summary ?? { ...emptySummary })
|
|
159
|
-
page.set(Math.min(page(), Math.max(1, Math.ceil((data.records?.length || 0) / pageSize))))
|
|
160
146
|
}
|
|
161
147
|
catch (error) {
|
|
162
148
|
loadError.set('Gift cards could not be loaded.')
|
|
@@ -258,8 +244,6 @@ async function deleteGiftCard(): Promise<void> {
|
|
|
258
244
|
}
|
|
259
245
|
}
|
|
260
246
|
|
|
261
|
-
const { previousPage, nextPage } = pageControls(page, pageCount)
|
|
262
|
-
|
|
263
247
|
onMount(() => {
|
|
264
248
|
void loadGiftCards()
|
|
265
249
|
})
|
|
@@ -295,14 +279,9 @@ onMount(() => {
|
|
|
295
279
|
|
|
296
280
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
297
281
|
<p class="text-gray-500 text-sm dark:text-neutral-400">{{ filteredRecords().length }} matching gift cards</p>
|
|
298
|
-
<div class="flex gap-2 items-center">
|
|
299
|
-
<Button variant="secondary" size="sm" :disabled="page() <= 1" @click="previousPage()">Previous</Button>
|
|
300
|
-
<span class="text-gray-500 text-sm dark:text-neutral-400">Page {{ Math.min(page(), pageCount()) }} of {{ pageCount() }}</span>
|
|
301
|
-
<Button variant="secondary" size="sm" :disabled="page() >= pageCount()" @click="nextPage()">Next</Button>
|
|
302
|
-
</div>
|
|
303
282
|
</div>
|
|
304
283
|
|
|
305
|
-
<CommerceGiftCardsTable :records="
|
|
284
|
+
<CommerceGiftCardsTable :sort-reset-key="sort()" :page-reset-key="viewKey()" :records="filteredRecords()" :loading="loading()" @view="openDetails" @edit="openEdit" @delete="openDelete" />
|
|
306
285
|
</section>
|
|
307
286
|
</div>
|
|
308
287
|
|
|
@@ -1,94 +1,84 @@
|
|
|
1
|
+
@import('../UI/Table')
|
|
2
|
+
|
|
1
3
|
<script client>
|
|
2
4
|
import type { GiftCardRecord, GiftCardStatus } from '../../../../app/Actions/Dashboard/Commerce/gift-card-records'
|
|
3
5
|
import { formatCurrency } from '@stacksjs/commerce/money'
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function money(value: number, currency: string): string {
|
|
13
|
-
return formatCurrency(value, currency)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function date(value: string): string {
|
|
17
|
-
if (!value)
|
|
18
|
-
return 'No expiry'
|
|
19
|
-
const parsed = new Date(value)
|
|
20
|
-
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString()
|
|
21
|
-
}
|
|
7
|
+
const records = useReactiveProp('records', [] as GiftCardRecord[])
|
|
8
|
+
const loading = useReactiveProp('loading', false)
|
|
9
|
+
/** The parent's sort select: a change hands the order back from a header sort. */
|
|
10
|
+
const sortResetKey = useReactiveProp<unknown>('sortResetKey', '')
|
|
11
|
+
/** The parent's search and filters: a change returns to the first page. */
|
|
12
|
+
const pageResetKey = useReactiveProp<unknown>('pageResetKey', '')
|
|
13
|
+
const emit = defineEmits()
|
|
22
14
|
|
|
23
15
|
function statusLabel(status: GiftCardStatus): string {
|
|
24
16
|
return status.charAt(0) + status.slice(1).toLowerCase()
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The balance is a currency column keyed on each card's own currency; the
|
|
21
|
+
* initial balance beneath it is formatted here with the same helper, since
|
|
22
|
+
* a subtitle is text.
|
|
23
|
+
*/
|
|
24
|
+
const COLUMNS = [
|
|
25
|
+
{ key: 'code', label: 'Gift card', sortable: true, subtitleKey: 'currency', class: 'font-medium text-gray-900 dark:text-white' },
|
|
26
|
+
{ key: 'recipientLabel', label: 'Recipient', sortable: true, subtitleKey: 'recipientEmailLabel', class: 'text-gray-900 dark:text-white' },
|
|
27
|
+
{ key: 'currentBalance', label: 'Balance', type: 'currency' as const, currencyKey: 'currency', sortable: true, subtitleKey: 'initialLabel', class: 'font-medium text-gray-900 dark:text-white' },
|
|
28
|
+
{
|
|
29
|
+
key: 'status',
|
|
30
|
+
label: 'Status',
|
|
31
|
+
type: 'badge' as const,
|
|
32
|
+
sortable: true,
|
|
33
|
+
badgeLabelKey: 'statusLabel',
|
|
34
|
+
subtitleKey: 'enabledLabel',
|
|
35
|
+
badgeClasses: {
|
|
36
|
+
ACTIVE: 'bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300',
|
|
37
|
+
USED: 'bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300',
|
|
38
|
+
EXPIRED: 'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{ key: 'deliveryLabel', label: 'Delivery', sortable: true, subtitleKey: 'reloadLabel', class: 'text-gray-700 dark:text-neutral-200' },
|
|
42
|
+
{ key: 'expiryDate', label: 'Expiry', type: 'date' as const, sortable: true, emptyText: 'No expiry', class: 'whitespace-nowrap' },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
const ROW_ACTIONS = [
|
|
46
|
+
{ action: 'view', label: 'View', icon: 'i-hugeicons-view' },
|
|
47
|
+
{ action: 'edit', label: 'Edit', icon: 'i-hugeicons-edit-02' },
|
|
48
|
+
{ action: 'delete', label: 'Delete', icon: 'i-hugeicons-delete-02', class: 'text-red-500' },
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
const rows = derived(() => records().map(record => ({
|
|
52
|
+
...record,
|
|
53
|
+
recipientLabel: record.recipientName || 'Unassigned',
|
|
54
|
+
recipientEmailLabel: record.recipientEmail || 'No recipient email',
|
|
55
|
+
initialLabel: `of ${formatCurrency(record.initialBalance, record.currency)}`,
|
|
56
|
+
statusLabel: statusLabel(record.status),
|
|
57
|
+
enabledLabel: record.isActive ? 'Enabled' : 'Disabled',
|
|
58
|
+
deliveryLabel: record.isDigital ? 'Digital' : 'Physical',
|
|
59
|
+
reloadLabel: record.isReloadable ? 'Reloadable' : 'Single use',
|
|
60
|
+
})))
|
|
61
|
+
|
|
62
|
+
function handleRowAction(payload: { action: string, row: GiftCardRecord }): void {
|
|
63
|
+
emit(payload.action, payload.row)
|
|
35
64
|
}
|
|
36
65
|
</script>
|
|
37
66
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
</tr>
|
|
57
|
-
</thead>
|
|
58
|
-
<tbody class="divide-gray-100 divide-y dark:divide-neutral-700">
|
|
59
|
-
<template :for="record in records" :key="record.id">
|
|
60
|
-
<tr class="hover:bg-gray-50/70 dark:hover:bg-neutral-900/40">
|
|
61
|
-
<td class="px-4 py-3">
|
|
62
|
-
<Button variant="ghost" size="xs" @click="emit('view', record)">{{ record.code }}</Button>
|
|
63
|
-
<p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ record.currency }}</p>
|
|
64
|
-
</td>
|
|
65
|
-
<td class="px-4 py-3">
|
|
66
|
-
<p class="text-gray-900 text-sm dark:text-white">{{ record.recipientName || 'Unassigned' }}</p>
|
|
67
|
-
<p class="mt-1 max-w-52 text-gray-400 text-xs truncate dark:text-neutral-500">{{ record.recipientEmail || 'No recipient email' }}</p>
|
|
68
|
-
</td>
|
|
69
|
-
<td class="px-4 py-3">
|
|
70
|
-
<p class="font-medium text-gray-900 text-sm dark:text-white">{{ money(record.currentBalance, record.currency) }}</p>
|
|
71
|
-
<p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">of {{ money(record.initialBalance, record.currency) }}</p>
|
|
72
|
-
</td>
|
|
73
|
-
<td class="px-4 py-3">
|
|
74
|
-
<span :class="'inline-flex px-2 py-1 font-medium text-xs rounded-full ' + statusClass(record.status)">{{ statusLabel(record.status) }}</span>
|
|
75
|
-
<p :class="'mt-1 text-xs ' + (record.isActive ? 'text-green-600 dark:text-green-400' : 'text-gray-400 dark:text-neutral-500')">{{ record.isActive ? 'Enabled' : 'Disabled' }}</p>
|
|
76
|
-
</td>
|
|
77
|
-
<td class="px-4 py-3">
|
|
78
|
-
<p class="text-gray-700 text-sm dark:text-neutral-200">{{ record.isDigital ? 'Digital' : 'Physical' }}</p>
|
|
79
|
-
<p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ record.isReloadable ? 'Reloadable' : 'Single use' }}</p>
|
|
80
|
-
</td>
|
|
81
|
-
<td class="px-4 py-3 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ date(record.expiryDate) }}</td>
|
|
82
|
-
<td class="px-4 py-3">
|
|
83
|
-
<div class="flex gap-1 justify-end">
|
|
84
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'View ' + record.code" @click="emit('view', record)"><span class="block h-4 w-4 text-blue-500 i-hugeicons-view"></span></Button>
|
|
85
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'Edit ' + record.code" @click="emit('edit', record)"><span class="block h-4 w-4 text-blue-500 i-hugeicons-edit-02"></span></Button>
|
|
86
|
-
<Button variant="ghost" size="sm" iconOnly :ariaLabel="'Delete ' + record.code" @click="emit('delete', record)"><span class="block h-4 w-4 text-red-500 i-hugeicons-delete-02"></span></Button>
|
|
87
|
-
</div>
|
|
88
|
-
</td>
|
|
89
|
-
</tr>
|
|
90
|
-
</template>
|
|
91
|
-
</tbody>
|
|
92
|
-
</table>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
67
|
+
{{--
|
|
68
|
+
No `searchable` or `filterable`: the parent dashboard owns the search box
|
|
69
|
+
and the filter selects. Table pages the whole filtered list the parent passes.
|
|
70
|
+
--}}
|
|
71
|
+
<Table
|
|
72
|
+
:sort-reset-key="sortResetKey()"
|
|
73
|
+
:page-reset-key="pageResetKey()"
|
|
74
|
+
:columns="COLUMNS"
|
|
75
|
+
:data="rows()"
|
|
76
|
+
:loading="loading()"
|
|
77
|
+
:row-actions="ROW_ACTIONS"
|
|
78
|
+
action-label-key="code"
|
|
79
|
+
paginated
|
|
80
|
+
:page-size="20"
|
|
81
|
+
empty-title="No gift cards found"
|
|
82
|
+
empty-description="Adjust the filters or add a native GiftCard record."
|
|
83
|
+
@row-action="handleRowAction"
|
|
84
|
+
/>
|
|
@@ -233,7 +233,7 @@ onMount(() => {
|
|
|
233
233
|
</label>
|
|
234
234
|
</div>
|
|
235
235
|
|
|
236
|
-
<CommerceManufacturersTable :records="visibleRecords()" :loading="loading()" @edit="openEdit" @delete="openDelete" />
|
|
236
|
+
<CommerceManufacturersTable :sort-reset-key="sort()" :records="visibleRecords()" :loading="loading()" @edit="openEdit" @delete="openDelete" />
|
|
237
237
|
</section>
|
|
238
238
|
</div>
|
|
239
239
|
|
|
@@ -5,6 +5,8 @@ import type { ManufacturerRecord } from '../../../../app/Actions/Dashboard/Comme
|
|
|
5
5
|
|
|
6
6
|
const records = useReactiveProp('records', [] as ManufacturerRecord[])
|
|
7
7
|
const loading = useReactiveProp('loading', false)
|
|
8
|
+
/** The parent's sort select; a change hands the order back from a header sort. */
|
|
9
|
+
const sortResetKey = useReactiveProp<unknown>('sortResetKey', '')
|
|
8
10
|
const emit = defineEmits()
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -55,6 +57,7 @@ function handleRowAction(payload: { action: string, row: ManufacturerRecord }):
|
|
|
55
57
|
duplicate them. Paging is what was missing, and Table brings it.
|
|
56
58
|
--}}
|
|
57
59
|
<Table
|
|
60
|
+
:sort-reset-key="sortResetKey()"
|
|
58
61
|
:columns="COLUMNS"
|
|
59
62
|
:data="rows()"
|
|
60
63
|
:loading="loading()"
|
|
@@ -7,7 +7,6 @@ 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'
|
|
11
10
|
|
|
12
11
|
interface OrderIndexResponse {
|
|
13
12
|
records: CommerceOrderRecord[]
|
|
@@ -53,8 +52,6 @@ const search = state('')
|
|
|
53
52
|
const statusFilter = state('all')
|
|
54
53
|
const typeFilter = state('all')
|
|
55
54
|
const sort = state('newest')
|
|
56
|
-
const page = state(1)
|
|
57
|
-
const pageSize = 12
|
|
58
55
|
const loading = state(true)
|
|
59
56
|
const loadError = state('')
|
|
60
57
|
const dialogOpen = state(false)
|
|
@@ -114,15 +111,6 @@ function filteredRecords(): CommerceOrderRecord[] {
|
|
|
114
111
|
})
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
function pageCount(): number {
|
|
118
|
-
return Math.max(1, Math.ceil(filteredRecords().length / pageSize))
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function visibleRecords(): CommerceOrderRecord[] {
|
|
122
|
-
const selectedPage = Math.min(page(), pageCount())
|
|
123
|
-
return filteredRecords().slice((selectedPage - 1) * pageSize, selectedPage * pageSize)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
114
|
function matchingLabel(): string {
|
|
127
115
|
const count = filteredRecords().length
|
|
128
116
|
return `${count} matching ${count === 1 ? 'order' : 'orders'}`
|
|
@@ -164,13 +152,13 @@ function exportHref(): string {
|
|
|
164
152
|
return `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${csv}`)}`
|
|
165
153
|
}
|
|
166
154
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
155
|
+
/**
|
|
156
|
+
* What the table is showing: a change returns it to the first page, as this
|
|
157
|
+
* page's own pager did before the table took over paging.
|
|
158
|
+
*/
|
|
159
|
+
function viewKey(): string {
|
|
160
|
+
return JSON.stringify([search(), statusFilter(), typeFilter(), sort()])
|
|
161
|
+
}
|
|
174
162
|
|
|
175
163
|
async function loadOrders(): Promise<void> {
|
|
176
164
|
loading.set(true)
|
|
@@ -183,7 +171,6 @@ async function loadOrders(): Promise<void> {
|
|
|
183
171
|
statuses.set(Array.isArray(data.statuses) ? data.statuses : [])
|
|
184
172
|
orderTypes.set(Array.isArray(data.orderTypes) ? data.orderTypes : [])
|
|
185
173
|
defaultCurrency.set(data.defaultCurrency || 'USD')
|
|
186
|
-
page.set(Math.min(page(), Math.max(1, Math.ceil((data.records?.length || 0) / pageSize))))
|
|
187
174
|
}
|
|
188
175
|
catch (error) {
|
|
189
176
|
loadError.set('Order records could not be loaded.')
|
|
@@ -285,8 +272,6 @@ async function deleteOrder(): Promise<void> {
|
|
|
285
272
|
}
|
|
286
273
|
}
|
|
287
274
|
|
|
288
|
-
const { previousPage, nextPage } = pageControls(page, pageCount)
|
|
289
|
-
|
|
290
275
|
onMount(() => {
|
|
291
276
|
void loadOrders()
|
|
292
277
|
})
|
|
@@ -324,14 +309,9 @@ onMount(() => {
|
|
|
324
309
|
|
|
325
310
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
326
311
|
<p class="text-gray-500 text-sm dark:text-neutral-400">{{ matchingLabel() }}</p>
|
|
327
|
-
<div class="flex gap-2 items-center">
|
|
328
|
-
<Button variant="secondary" size="sm" :disabled="page() <= 1" @click="previousPage()">Previous</Button>
|
|
329
|
-
<span class="text-gray-500 text-sm dark:text-neutral-400">Page {{ Math.min(page(), pageCount()) }} of {{ pageCount() }}</span>
|
|
330
|
-
<Button variant="secondary" size="sm" :disabled="page() >= pageCount()" @click="nextPage()">Next</Button>
|
|
331
|
-
</div>
|
|
332
312
|
</div>
|
|
333
313
|
|
|
334
|
-
<CommerceOrdersTable :records="
|
|
314
|
+
<CommerceOrdersTable :sort-reset-key="sort()" :page-reset-key="viewKey()" :records="filteredRecords()" :loading="loading()" @view="openDetails" @edit="openEdit" @delete="openDelete" />
|
|
335
315
|
</section>
|
|
336
316
|
</div>
|
|
337
317
|
|