@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
|
@@ -13,8 +13,6 @@ const title = useReactiveProp('title', 'Notification Deliveries')
|
|
|
13
13
|
const description = useReactiveProp('description', 'Monitor notification transport attempts.')
|
|
14
14
|
|
|
15
15
|
const deliveries = state<NotificationDelivery[]>([])
|
|
16
|
-
const currentPage = state(1)
|
|
17
|
-
const perPage = state(10)
|
|
18
16
|
const isLoading = state(true)
|
|
19
17
|
const searchQuery = state('')
|
|
20
18
|
const selectedStatus = state('all')
|
|
@@ -46,30 +44,27 @@ const sortedDeliveries = derived(() => [...filteredDeliveries()].sort((a, b) =>
|
|
|
46
44
|
return sortDirection() === 'asc' ? comparison : -comparison
|
|
47
45
|
}))
|
|
48
46
|
|
|
49
|
-
const paginatedDeliveries = derived(() => {
|
|
50
|
-
const start = (currentPage() - 1) * perPage()
|
|
51
|
-
return sortedDeliveries().slice(start, start + perPage())
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const totalItems = derived(() => filteredDeliveries().length)
|
|
55
|
-
const totalPages = derived(() => Math.max(1, Math.ceil(totalItems() / perPage())))
|
|
56
|
-
const rangeStart = derived(() => totalItems() === 0 ? 0 : (currentPage() - 1) * perPage() + 1)
|
|
57
|
-
const rangeEnd = derived(() => Math.min(currentPage() * perPage(), totalItems()))
|
|
58
|
-
const canGoBack = derived(() => currentPage() > 1)
|
|
59
|
-
const canGoForward = derived(() => currentPage() < totalPages())
|
|
60
47
|
const sentCount = derived(() => deliveries().filter(delivery => delivery.status === 'sent' || delivery.status === 'delivered').length)
|
|
61
48
|
const failedCount = derived(() => deliveries().filter(delivery => delivery.status === 'failed').length)
|
|
62
49
|
const pendingCount = derived(() => deliveries().filter(delivery => delivery.status === 'pending').length)
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
/**
|
|
52
|
+
* What the table is showing: a change returns it to the first page, as this
|
|
53
|
+
* page's own pager did before the table took over paging.
|
|
54
|
+
*/
|
|
55
|
+
const viewKey = derived(() => JSON.stringify([searchQuery(), selectedStatus(), sortDirection()]))
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A component event reaches this handler as the emitted value, not as a
|
|
59
|
+
* CustomEvent, so reading `.detail` gave undefined: View opened an empty
|
|
60
|
+
* dialog and Retry did nothing (stacksjs/stacks#2977). Accept either.
|
|
61
|
+
*/
|
|
62
|
+
function eventValue<T>(value: T | CustomEvent<T>): T {
|
|
63
|
+
return value instanceof CustomEvent ? value.detail : value
|
|
64
|
+
}
|
|
70
65
|
|
|
71
|
-
function openDelivery(event: CustomEvent<NotificationDelivery>): void {
|
|
72
|
-
selectedDelivery.set(event
|
|
66
|
+
function openDelivery(event: NotificationDelivery | CustomEvent<NotificationDelivery>): void {
|
|
67
|
+
selectedDelivery.set(eventValue(event))
|
|
73
68
|
showDetails.set(true)
|
|
74
69
|
}
|
|
75
70
|
|
|
@@ -84,8 +79,8 @@ async function loadDeliveries(): Promise<void> {
|
|
|
84
79
|
isLoading.set(false)
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
async function retryDelivery(event: CustomEvent<NotificationDelivery>): Promise<void> {
|
|
88
|
-
const delivery = event
|
|
82
|
+
async function retryDelivery(event: NotificationDelivery | CustomEvent<NotificationDelivery>): Promise<void> {
|
|
83
|
+
const delivery = eventValue(event)
|
|
89
84
|
retryingId.set(delivery.id)
|
|
90
85
|
const retried = await retryNotificationDelivery(delivery)
|
|
91
86
|
if (retried)
|
|
@@ -99,16 +94,6 @@ function resetFilters(): void {
|
|
|
99
94
|
sortDirection.set('desc')
|
|
100
95
|
}
|
|
101
96
|
|
|
102
|
-
function previousPage(): void {
|
|
103
|
-
if (canGoBack())
|
|
104
|
-
currentPage.update(page => page - 1)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function nextPage(): void {
|
|
108
|
-
if (canGoForward())
|
|
109
|
-
currentPage.update(page => page + 1)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
97
|
onMount(loadDeliveries)
|
|
113
98
|
</script>
|
|
114
99
|
|
|
@@ -183,7 +168,10 @@ onMount(loadDeliveries)
|
|
|
183
168
|
|
|
184
169
|
<div class="mt-8">
|
|
185
170
|
<NotificationDeliveryTable
|
|
186
|
-
:deliveries="
|
|
171
|
+
:deliveries="sortedDeliveries"
|
|
172
|
+
:paginated="true"
|
|
173
|
+
:sort-reset-key="sortDirection"
|
|
174
|
+
:page-reset-key="viewKey"
|
|
187
175
|
:loading="isLoading"
|
|
188
176
|
:actions="true"
|
|
189
177
|
:retryingId="retryingId"
|
|
@@ -192,16 +180,6 @@ onMount(loadDeliveries)
|
|
|
192
180
|
@retry="retryDelivery($event)"
|
|
193
181
|
/>
|
|
194
182
|
</div>
|
|
195
|
-
|
|
196
|
-
<div class="flex items-center justify-between mt-4">
|
|
197
|
-
<p class="text-gray-700 text-sm dark:text-neutral-300">
|
|
198
|
-
Showing <span class="font-medium">{{ rangeStart }}</span> to <span class="font-medium">{{ rangeEnd }}</span> of <span class="font-medium">{{ totalItems }}</span> results
|
|
199
|
-
</p>
|
|
200
|
-
<div class="flex gap-2 items-center">
|
|
201
|
-
<Button variant="secondary" :disabled="!canGoBack" @click="previousPage()">Previous</Button>
|
|
202
|
-
<Button variant="secondary" :disabled="!canGoForward" @click="nextPage()">Next</Button>
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
183
|
</div>
|
|
206
184
|
|
|
207
185
|
<NotificationDeliveryDialog :open="showDetails" :delivery="selectedDelivery" @close="closeDelivery()" />
|
|
@@ -102,8 +102,17 @@ function nextPage(): void {
|
|
|
102
102
|
void loadHistory()
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
/**
|
|
106
|
+
* A component event reaches this handler as the emitted value, not as a
|
|
107
|
+
* CustomEvent, so reading `.detail` gave undefined: View opened an empty
|
|
108
|
+
* dialog and Retry did nothing (stacksjs/stacks#2977). Accept either.
|
|
109
|
+
*/
|
|
110
|
+
function eventValue<T>(value: T | CustomEvent<T>): T {
|
|
111
|
+
return value instanceof CustomEvent ? value.detail : value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function openDelivery(event: NotificationDelivery | CustomEvent<NotificationDelivery>): void {
|
|
115
|
+
selectedDelivery.set(eventValue(event))
|
|
107
116
|
showDetails.set(true)
|
|
108
117
|
}
|
|
109
118
|
|
|
@@ -112,8 +121,8 @@ function closeDelivery(): void {
|
|
|
112
121
|
selectedDelivery.set(null)
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
async function retryDelivery(event: CustomEvent<NotificationDelivery>): Promise<void> {
|
|
116
|
-
const delivery = event
|
|
124
|
+
async function retryDelivery(event: NotificationDelivery | CustomEvent<NotificationDelivery>): Promise<void> {
|
|
125
|
+
const delivery = eventValue(event)
|
|
117
126
|
retryingId.set(delivery.id)
|
|
118
127
|
const retried = await retryNotificationDelivery(delivery)
|
|
119
128
|
if (retried)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import('../UI/Table')
|
|
2
|
+
|
|
1
3
|
<script client>
|
|
2
4
|
import type { NotificationDelivery } from '../../../../functions/notifications/deliveries'
|
|
3
5
|
|
|
@@ -7,6 +9,16 @@ const actions = useReactiveProp('actions', false)
|
|
|
7
9
|
const allowRetry = useReactiveProp('allowRetry', true)
|
|
8
10
|
const retryingId = useReactiveProp('retryingId', null as number | null)
|
|
9
11
|
const emptyMessage = useReactiveProp('emptyMessage', 'No notification delivery attempts have been recorded.')
|
|
12
|
+
/**
|
|
13
|
+
* Off by default: the delivery history pages on the server and hands this
|
|
14
|
+
* one page at a time, while the per-channel deliveries page passes every
|
|
15
|
+
* attempt it loaded and lets Table page them (stacksjs/stacks#2977).
|
|
16
|
+
*/
|
|
17
|
+
const paginated = useReactiveProp('paginated', false)
|
|
18
|
+
/** The parent's sort select: a change hands the order back from a header sort. */
|
|
19
|
+
const sortResetKey = useReactiveProp<unknown>('sortResetKey', '')
|
|
20
|
+
/** The parent's search and filters: a change returns to the first page. */
|
|
21
|
+
const pageResetKey = useReactiveProp<unknown>('pageResetKey', '')
|
|
10
22
|
const emit = defineEmits()
|
|
11
23
|
|
|
12
24
|
function channelIcon(channel: string): string {
|
|
@@ -25,101 +37,77 @@ function channelIcon(channel: string): string {
|
|
|
25
37
|
return 'i-hugeicons-notification-03'
|
|
26
38
|
}
|
|
27
39
|
|
|
28
|
-
function statusClass(status: string): string {
|
|
29
|
-
if (status === 'failed')
|
|
30
|
-
return 'bg-red-50 text-red-700 ring-red-600/20 dark:bg-red-950/40 dark:text-red-300'
|
|
31
|
-
if (status === 'pending')
|
|
32
|
-
return 'bg-amber-50 text-amber-700 ring-amber-600/20 dark:bg-amber-950/40 dark:text-amber-300'
|
|
33
|
-
if (status === 'sent')
|
|
34
|
-
return 'bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-950/40 dark:text-blue-300'
|
|
35
|
-
return 'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-950/40 dark:text-green-300'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function formatDate(value: string): string {
|
|
39
|
-
if (!value)
|
|
40
|
-
return 'Not recorded'
|
|
41
|
-
const date = new Date(value)
|
|
42
|
-
return Number.isNaN(date.getTime()) ? value : new Intl.DateTimeFormat('en-US', {
|
|
43
|
-
dateStyle: 'medium',
|
|
44
|
-
timeStyle: 'short',
|
|
45
|
-
}).format(date)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
40
|
function provider(delivery: NotificationDelivery): string {
|
|
49
41
|
const value = delivery.metadata.provider || delivery.metadata.driver
|
|
50
42
|
return typeof value === 'string' && value ? value : 'Default'
|
|
51
43
|
}
|
|
52
44
|
|
|
53
|
-
function summary(delivery: NotificationDelivery): string {
|
|
54
|
-
return delivery.subject || delivery.body || 'No content recorded'
|
|
55
|
-
}
|
|
56
|
-
|
|
57
45
|
function canRetry(delivery: NotificationDelivery): boolean {
|
|
58
46
|
return allowRetry()
|
|
59
47
|
&& delivery.status === 'failed'
|
|
60
48
|
&& (delivery.channel === 'email' || delivery.channel === 'sms')
|
|
61
49
|
}
|
|
50
|
+
|
|
51
|
+
/** The channel cell is slotted for its icon. */
|
|
52
|
+
const COLUMNS = [
|
|
53
|
+
{ key: 'channel', label: 'Channel', sortable: true, slotted: true },
|
|
54
|
+
{ key: 'recipient', label: 'Recipient', sortable: true, emptyText: 'Not recorded', class: 'whitespace-nowrap' },
|
|
55
|
+
{ key: 'summary', label: 'Content', class: 'max-w-sm truncate' },
|
|
56
|
+
{
|
|
57
|
+
key: 'status',
|
|
58
|
+
label: 'Status',
|
|
59
|
+
type: 'badge' as const,
|
|
60
|
+
sortable: true,
|
|
61
|
+
badgeClasses: {
|
|
62
|
+
failed: 'capitalize bg-red-50 text-red-700 dark:bg-red-950/40 dark:text-red-300',
|
|
63
|
+
pending: 'capitalize bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300',
|
|
64
|
+
sent: 'capitalize bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300',
|
|
65
|
+
delivered: 'capitalize bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{ key: 'providerLabel', label: 'Provider', sortable: true, class: 'whitespace-nowrap' },
|
|
69
|
+
{ key: 'sent_at', label: 'Attempted', type: 'datetime' as const, sortable: true, align: 'right' as const, emptyText: 'Not recorded', class: 'whitespace-nowrap' },
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
const ROW_ACTIONS = [
|
|
73
|
+
{ action: 'retry', label: 'Retry delivery to', icon: 'i-hugeicons-reload', when: { key: 'retryable', equals: true } },
|
|
74
|
+
{ action: 'view', label: 'View details of delivery to', icon: 'i-hugeicons-view' },
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
const rows = derived(() => deliveries().map(delivery => ({
|
|
78
|
+
...delivery,
|
|
79
|
+
icon: `h-5 w-5 ${channelIcon(delivery.channel)}`,
|
|
80
|
+
summary: delivery.subject || delivery.body || 'No content recorded',
|
|
81
|
+
providerLabel: provider(delivery),
|
|
82
|
+
retryable: canRetry(delivery),
|
|
83
|
+
actionName: delivery.recipient || `attempt ${delivery.id}`,
|
|
84
|
+
})))
|
|
85
|
+
|
|
86
|
+
function handleRowAction(payload: { action: string, row: NotificationDelivery }): void {
|
|
87
|
+
emit(payload.action, payload.row)
|
|
88
|
+
}
|
|
62
89
|
</script>
|
|
63
90
|
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</tr>
|
|
88
|
-
<template :for="delivery in deliveries">
|
|
89
|
-
<tr class="hover:bg-gray-50 dark:hover:bg-neutral-700/50">
|
|
90
|
-
<td class="pl-4 pr-3 py-4 sm:pl-6 text-sm whitespace-nowrap">
|
|
91
|
-
<span class="inline-flex gap-2 items-center font-medium text-gray-900 dark:text-neutral-100">
|
|
92
|
-
<span :class="'h-5 w-5 ' + channelIcon(delivery.channel)"></span>
|
|
93
|
-
<span class="capitalize">{{ delivery.channel }}</span>
|
|
94
|
-
</span>
|
|
95
|
-
</td>
|
|
96
|
-
<td class="px-3 py-4 text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ delivery.recipient || 'Not recorded' }}</td>
|
|
97
|
-
<td class="px-3 py-4 text-gray-500 text-sm dark:text-neutral-400">
|
|
98
|
-
<span class="block max-w-sm truncate">{{ summary(delivery) }}</span>
|
|
99
|
-
</td>
|
|
100
|
-
<td class="px-3 py-4 text-sm whitespace-nowrap">
|
|
101
|
-
<span :class="'inline-flex items-center px-2 py-1 font-medium text-xs ring-1 ring-inset rounded-md capitalize ' + statusClass(delivery.status)">
|
|
102
|
-
{{ delivery.status }}
|
|
103
|
-
</span>
|
|
104
|
-
</td>
|
|
105
|
-
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ provider(delivery) }}</td>
|
|
106
|
-
<td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-400">{{ formatDate(delivery.sent_at) }}</td>
|
|
107
|
-
<td :if="actions" class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
|
|
108
|
-
<Button
|
|
109
|
-
:if="canRetry(delivery)"
|
|
110
|
-
variant="secondary"
|
|
111
|
-
size="sm"
|
|
112
|
-
:loading="retryingId === delivery.id"
|
|
113
|
-
@click="emit('retry', delivery)"
|
|
114
|
-
>
|
|
115
|
-
Retry
|
|
116
|
-
</Button>
|
|
117
|
-
<Button variant="ghost" size="sm" @click="emit('view', delivery)">
|
|
118
|
-
View Details
|
|
119
|
-
</Button>
|
|
120
|
-
</td>
|
|
121
|
-
</tr>
|
|
122
|
-
</template>
|
|
123
|
-
</tbody>
|
|
124
|
-
</table>
|
|
125
|
-
</div>
|
|
91
|
+
<Table
|
|
92
|
+
:sort-reset-key="sortResetKey()"
|
|
93
|
+
:page-reset-key="pageResetKey()"
|
|
94
|
+
:columns="COLUMNS"
|
|
95
|
+
:data="rows()"
|
|
96
|
+
:loading="loading()"
|
|
97
|
+
:row-actions="actions() ? ROW_ACTIONS : []"
|
|
98
|
+
action-label-key="actionName"
|
|
99
|
+
busy-key="id"
|
|
100
|
+
:busy-value="retryingId()"
|
|
101
|
+
:paginated="paginated()"
|
|
102
|
+
:page-size="10"
|
|
103
|
+
empty-title="No delivery attempts"
|
|
104
|
+
:empty-description="emptyMessage()"
|
|
105
|
+
@row-action="handleRowAction"
|
|
106
|
+
>
|
|
107
|
+
<template #cell="{ row }">
|
|
108
|
+
<span class="inline-flex gap-2 items-center font-medium text-gray-900 dark:text-neutral-100">
|
|
109
|
+
<span :class="row.icon"></span>
|
|
110
|
+
<span class="capitalize" x-text="row.channel"></span>
|
|
111
|
+
</span>
|
|
112
|
+
</template>
|
|
113
|
+
</Table>
|
|
@@ -210,14 +210,43 @@ const actionLabelKey = useReactiveProp('actionLabelKey', '')
|
|
|
210
210
|
*/
|
|
211
211
|
const busyKey = useReactiveProp('busyKey', '')
|
|
212
212
|
const busyValue = useReactiveProp<unknown>('busyValue', '')
|
|
213
|
+
/**
|
|
214
|
+
* The parent's own ordering, for a page whose sort select orders the rows
|
|
215
|
+
* Table receives.
|
|
216
|
+
*
|
|
217
|
+
* A header click overrides that order, and nothing used to hand it back:
|
|
218
|
+
* once a column was sorted, changing the select changed nothing visible, so
|
|
219
|
+
* the page had two sort controls that disagreed (stacksjs/stacks#2977). Pass
|
|
220
|
+
* the select's value here and a change to it clears the header sort, so the
|
|
221
|
+
* order on screen is always the one the viewer chose last.
|
|
222
|
+
*/
|
|
223
|
+
const sortResetKey = useReactiveProp<unknown>('sortResetKey', '')
|
|
224
|
+
/**
|
|
225
|
+
* The parent's search and filters, for a page that filters the rows Table
|
|
226
|
+
* receives. A change returns to the first page, as a parent that paged
|
|
227
|
+
* itself always did: a narrower result read from page 3 is a page that
|
|
228
|
+
* looks empty or unrelated to what was just typed (stacksjs/stacks#2977).
|
|
229
|
+
*/
|
|
230
|
+
const pageResetKey = useReactiveProp<unknown>('pageResetKey', '')
|
|
213
231
|
|
|
214
232
|
const selectedKeys = state<TableKey[]>([])
|
|
215
233
|
const sortKey = state('')
|
|
216
234
|
const sortDirection = state<SortDirection>('asc')
|
|
235
|
+
|
|
236
|
+
effect(() => {
|
|
237
|
+
sortResetKey()
|
|
238
|
+
sortKey.set('')
|
|
239
|
+
})
|
|
217
240
|
const query = state('')
|
|
218
241
|
const columnFilters = state<Record<string, string>>({})
|
|
219
242
|
const hiddenColumns = state<string[]>([])
|
|
220
243
|
const page = state(1)
|
|
244
|
+
|
|
245
|
+
effect(() => {
|
|
246
|
+
sortResetKey()
|
|
247
|
+
pageResetKey()
|
|
248
|
+
page.set(1)
|
|
249
|
+
})
|
|
221
250
|
const columnMenuOpen = state(false)
|
|
222
251
|
const emit = defineEmits()
|
|
223
252
|
|
|
@@ -486,10 +515,20 @@ function toggleRow(row: DashboardTableRow, event: Event): void {
|
|
|
486
515
|
|
|
487
516
|
/* ------------------------------------------------------------------ sort */
|
|
488
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Ascending, descending, then off. Off is the order the rows arrived in,
|
|
520
|
+
* which on a page with its own sort select is that select's order; without
|
|
521
|
+
* a third state a header click could never be undone.
|
|
522
|
+
*/
|
|
489
523
|
function sort(column: DashboardTableColumn): void {
|
|
490
524
|
if (!column.sortable)
|
|
491
525
|
return
|
|
492
|
-
|
|
526
|
+
if (sortKey() === column.key && sortDirection() === 'desc') {
|
|
527
|
+
sortKey.set('')
|
|
528
|
+
emit('sort', { key: '', direction: 'asc' })
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
const direction: SortDirection = sortKey() === column.key ? 'desc' : 'asc'
|
|
493
532
|
sortKey.set(column.key)
|
|
494
533
|
sortDirection.set(direction)
|
|
495
534
|
emit('sort', { key: column.key, direction })
|
|
@@ -498,7 +537,7 @@ function sort(column: DashboardTableColumn): void {
|
|
|
498
537
|
function sortLabel(column: DashboardTableColumn): string {
|
|
499
538
|
if (sortKey() !== column.key)
|
|
500
539
|
return `Sort by ${column.label}`
|
|
501
|
-
return `Sort by ${column.label}
|
|
540
|
+
return sortDirection() === 'asc' ? `Sort by ${column.label} descending` : `Stop sorting by ${column.label}`
|
|
502
541
|
}
|
|
503
542
|
|
|
504
543
|
/** Mirrors sort state into aria-sort so screen readers announce it. */
|