Skip to content

Commit 543325a

Browse files
authored
Merge pull request #2322 from appwrite/fix-pagination
2 parents c8c45a8 + ce842db commit 543325a

1 file changed

Lines changed: 108 additions & 95 deletions

File tree

src/routes/(console)/organization-[organization]/billing/paymentHistory.svelte

Lines changed: 108 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { page } from '$app/state';
3+
import { onMount } from 'svelte';
34
import { CardGrid, PaginationInline } from '$lib/components';
45
import { Button } from '$lib/elements/forms';
56
import { toLocaleDate } from '$lib/helpers/date';
@@ -28,23 +29,37 @@
2829
IconRefresh
2930
} from '@appwrite.io/pink-icons-svelte';
3031
32+
let limit = $state(5);
3133
let offset = $state(0);
3234
let isLoadingInvoices = $state(false);
3335
let invoiceList: InvoiceList = $state({
3436
invoices: [],
3537
total: 0
3638
});
3739
38-
const limit = 5;
3940
const endpoint = getApiEndpoint();
4041
const hasPaymentError = $derived(invoiceList?.invoices.some((invoice) => invoice?.lastError));
4142
42-
async function request() {
43+
/**
44+
* Special case handling for the first page!
45+
*
46+
* As per Damodar - `there is some logic to **hide current cycle invoice** in the endpoint`.
47+
*
48+
* Due to this, the first page always loads `limit - 1` invoices which is inconsistent!
49+
* Therefore, we load `limit + 1` to counter that so the returned invoices are consistent.
50+
*/
51+
onMount(() => request(true));
52+
53+
async function request(patchQuery: boolean = false) {
4354
isLoadingInvoices = true;
4455
invoiceList = await sdk.forConsole.billing.listInvoices(page.params.organization, [
45-
Query.limit(limit),
46-
Query.offset(offset),
47-
Query.orderDesc('$createdAt')
56+
Query.orderDesc('$createdAt'),
57+
58+
// first page extra must have an extra limit!
59+
Query.limit(patchQuery ? limit + 1 : limit),
60+
61+
// so an invoice isn't repeated on 2nd page!
62+
Query.offset(patchQuery ? offset : offset + 1)
4863
]);
4964
5065
isLoadingInvoices = false;
@@ -62,17 +77,11 @@
6277
}
6378
});
6479
65-
$effect(() => {
66-
if (offset !== null) {
67-
request();
68-
}
69-
});
70-
7180
const columns = $derived([
7281
{ id: 'dueDate', width: { min: 120 } },
7382
{ id: 'status', width: { min: hasPaymentError ? 200 : 100 } },
7483
{ id: 'amount', width: { min: 120 } },
75-
{ id: 'action', width: 40 }
84+
{ id: 'actions', width: 40 }
7685
]);
7786
</script>
7887

@@ -86,11 +95,11 @@
8695
<Table.Header.Cell column="dueDate" {root}>Due date</Table.Header.Cell>
8796
<Table.Header.Cell column="status" {root}>Status</Table.Header.Cell>
8897
<Table.Header.Cell column="amount" {root}>Amount due</Table.Header.Cell>
89-
<Table.Header.Cell column="action" {root} />
98+
<Table.Header.Cell column="actions" {root} />
9099
</svelte:fragment>
91100

92101
{#if isLoadingInvoices}
93-
{#each Array.from({ length: 2 }).keys() as index (index)}
102+
{#each Array.from({ length: 5 }).keys() as index (index)}
94103
<Table.Row.Base {root}>
95104
{#each columns as column}
96105
<Table.Cell column={column.id} {root}>
@@ -99,91 +108,95 @@
99108
{/each}
100109
</Table.Row.Base>
101110
{/each}
102-
{/if}
103-
104-
{#each invoiceList?.invoices as invoice}
105-
{@const status = invoice.status}
106-
<Table.Row.Base {root}>
107-
<Table.Cell column="dueDate" {root}>
108-
{toLocaleDate(invoice.dueAt)}
109-
</Table.Cell>
110-
<Table.Cell column="status" {root}>
111-
{@const isDanger =
112-
status === 'overdue' ||
113-
status === 'failed' ||
114-
status === 'requires_authentication'}
115-
{@const isSuccess = status === 'paid' || status === 'succeeded'}
116-
{@const isWarning = status === 'pending'}
117-
<Layout.Stack direction="row" gap="s">
118-
<Badge
119-
variant="secondary"
120-
content={status === 'requires_authentication'
121-
? 'failed'
122-
: status}
123-
type={isDanger
124-
? 'error'
125-
: isWarning
126-
? 'warning'
127-
: isSuccess
128-
? 'success'
129-
: undefined} />
130-
{#if invoice?.lastError}
131-
<Popover let:toggle>
132-
<Link.Button on:click={toggle}>Details</Link.Button>
133-
<svelte:fragment slot="tooltip">
134-
The scheduled payment has failed.
135-
<Link.Button on:click={() => retryPayment(invoice)}
136-
>Try again
137-
</Link.Button>
138-
</svelte:fragment>
139-
</Popover>
140-
{/if}
141-
</Layout.Stack>
142-
</Table.Cell>
143-
<Table.Cell column="amount" {root}>
144-
{formatCurrency(invoice.grossAmount)}
145-
</Table.Cell>
146-
<Table.Cell column="status" {root}>
147-
<Popover let:toggle placement="bottom-start" padding="none">
148-
<Button text icon ariaLabel="more options" on:click={toggle}>
149-
<Icon icon={IconDotsHorizontal} size="s" />
150-
</Button>
151-
<ActionMenu.Root slot="tooltip">
152-
<!-- todo: add missing event -->
153-
<ActionMenu.Item.Anchor
154-
leadingIcon={IconExternalLink}
155-
external
156-
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
157-
View invoice
158-
</ActionMenu.Item.Anchor>
159-
<ActionMenu.Item.Anchor
160-
leadingIcon={IconDownload}
161-
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
162-
Download PDF
163-
</ActionMenu.Item.Anchor>
164-
{#if status === 'overdue' || status === 'failed' || status === 'abandoned'}
165-
<ActionMenu.Item.Button
166-
leadingIcon={IconRefresh}
167-
on:click={() => {
168-
retryPayment(invoice);
169-
trackEvent(`click_retry_payment`, {
170-
from: 'button',
171-
source: 'billing_invoice_menu'
172-
});
173-
}}>
174-
Retry payment
175-
</ActionMenu.Item.Button>
111+
{:else}
112+
{#each invoiceList?.invoices as invoice (invoice.$id)}
113+
{@const status = invoice.status}
114+
<Table.Row.Base {root}>
115+
<Table.Cell column="dueDate" {root}
116+
>{toLocaleDate(invoice.dueAt)}</Table.Cell>
117+
<Table.Cell column="status" {root}>
118+
{@const isDanger =
119+
status === 'overdue' ||
120+
status === 'failed' ||
121+
status === 'requires_authentication'}
122+
{@const isSuccess = status === 'paid' || status === 'succeeded'}
123+
{@const isWarning = status === 'pending'}
124+
<Layout.Stack direction="row" gap="s">
125+
<Badge
126+
variant="secondary"
127+
content={status === 'requires_authentication'
128+
? 'failed'
129+
: status}
130+
type={isDanger
131+
? 'error'
132+
: isWarning
133+
? 'warning'
134+
: isSuccess
135+
? 'success'
136+
: undefined} />
137+
{#if invoice?.lastError}
138+
<Popover let:toggle>
139+
<Link.Button on:click={toggle}>Details</Link.Button>
140+
<svelte:fragment slot="tooltip">
141+
The scheduled payment has failed.
142+
<Link.Button on:click={() => retryPayment(invoice)}
143+
>Try again
144+
</Link.Button>
145+
</svelte:fragment>
146+
</Popover>
176147
{/if}
177-
</ActionMenu.Root>
178-
</Popover>
179-
</Table.Cell>
180-
</Table.Row.Base>
181-
{/each}
148+
</Layout.Stack>
149+
</Table.Cell>
150+
<Table.Cell column="amount" {root}>
151+
{formatCurrency(invoice.grossAmount)}
152+
</Table.Cell>
153+
<Table.Cell column="actions" {root}>
154+
<Popover let:toggle placement="bottom-start" padding="none">
155+
<Button text icon ariaLabel="more options" on:click={toggle}>
156+
<Icon icon={IconDotsHorizontal} size="s" />
157+
</Button>
158+
<ActionMenu.Root slot="tooltip">
159+
<!-- todo: add missing event -->
160+
<ActionMenu.Item.Anchor
161+
leadingIcon={IconExternalLink}
162+
external
163+
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
164+
View invoice
165+
</ActionMenu.Item.Anchor>
166+
<ActionMenu.Item.Anchor
167+
leadingIcon={IconDownload}
168+
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
169+
Download PDF
170+
</ActionMenu.Item.Anchor>
171+
{#if status === 'overdue' || status === 'failed' || status === 'abandoned'}
172+
<ActionMenu.Item.Button
173+
leadingIcon={IconRefresh}
174+
on:click={() => {
175+
retryPayment(invoice);
176+
trackEvent(`click_retry_payment`, {
177+
from: 'button',
178+
source: 'billing_invoice_menu'
179+
});
180+
}}>
181+
Retry payment
182+
</ActionMenu.Item.Button>
183+
{/if}
184+
</ActionMenu.Root>
185+
</Popover>
186+
</Table.Cell>
187+
</Table.Row.Base>
188+
{/each}
189+
{/if}
182190
</Table.Root>
183-
{#if invoiceList.total > limit}
191+
{#if invoiceList.total >= limit}
184192
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
185193
<p class="text">Total results: {invoiceList.total}</p>
186-
<PaginationInline {limit} bind:offset total={invoiceList.total} hidePages />
194+
<PaginationInline
195+
{limit}
196+
hidePages
197+
bind:offset
198+
total={invoiceList.total}
199+
on:change={() => request()} />
187200
</Layout.Stack>
188201
{/if}
189202
{:else}

0 commit comments

Comments
 (0)