mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #2322 from appwrite/fix-pagination
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import { CardGrid, PaginationInline } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
@@ -28,6 +29,7 @@
|
||||
IconRefresh
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
let limit = $state(5);
|
||||
let offset = $state(0);
|
||||
let isLoadingInvoices = $state(false);
|
||||
let invoiceList: InvoiceList = $state({
|
||||
@@ -35,16 +37,29 @@
|
||||
total: 0
|
||||
});
|
||||
|
||||
const limit = 5;
|
||||
const endpoint = getApiEndpoint();
|
||||
const hasPaymentError = $derived(invoiceList?.invoices.some((invoice) => invoice?.lastError));
|
||||
|
||||
async function request() {
|
||||
/**
|
||||
* Special case handling for the first page!
|
||||
*
|
||||
* As per Damodar - `there is some logic to **hide current cycle invoice** in the endpoint`.
|
||||
*
|
||||
* Due to this, the first page always loads `limit - 1` invoices which is inconsistent!
|
||||
* Therefore, we load `limit + 1` to counter that so the returned invoices are consistent.
|
||||
*/
|
||||
onMount(() => request(true));
|
||||
|
||||
async function request(patchQuery: boolean = false) {
|
||||
isLoadingInvoices = true;
|
||||
invoiceList = await sdk.forConsole.billing.listInvoices(page.params.organization, [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset),
|
||||
Query.orderDesc('$createdAt')
|
||||
Query.orderDesc('$createdAt'),
|
||||
|
||||
// first page extra must have an extra limit!
|
||||
Query.limit(patchQuery ? limit + 1 : limit),
|
||||
|
||||
// so an invoice isn't repeated on 2nd page!
|
||||
Query.offset(patchQuery ? offset : offset + 1)
|
||||
]);
|
||||
|
||||
isLoadingInvoices = false;
|
||||
@@ -62,17 +77,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (offset !== null) {
|
||||
request();
|
||||
}
|
||||
});
|
||||
|
||||
const columns = $derived([
|
||||
{ id: 'dueDate', width: { min: 120 } },
|
||||
{ id: 'status', width: { min: hasPaymentError ? 200 : 100 } },
|
||||
{ id: 'amount', width: { min: 120 } },
|
||||
{ id: 'action', width: 40 }
|
||||
{ id: 'actions', width: 40 }
|
||||
]);
|
||||
</script>
|
||||
|
||||
@@ -86,11 +95,11 @@
|
||||
<Table.Header.Cell column="dueDate" {root}>Due date</Table.Header.Cell>
|
||||
<Table.Header.Cell column="status" {root}>Status</Table.Header.Cell>
|
||||
<Table.Header.Cell column="amount" {root}>Amount due</Table.Header.Cell>
|
||||
<Table.Header.Cell column="action" {root} />
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
|
||||
{#if isLoadingInvoices}
|
||||
{#each Array.from({ length: 2 }).keys() as index (index)}
|
||||
{#each Array.from({ length: 5 }).keys() as index (index)}
|
||||
<Table.Row.Base {root}>
|
||||
{#each columns as column}
|
||||
<Table.Cell column={column.id} {root}>
|
||||
@@ -99,91 +108,95 @@
|
||||
{/each}
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#each invoiceList?.invoices as invoice}
|
||||
{@const status = invoice.status}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="dueDate" {root}>
|
||||
{toLocaleDate(invoice.dueAt)}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="status" {root}>
|
||||
{@const isDanger =
|
||||
status === 'overdue' ||
|
||||
status === 'failed' ||
|
||||
status === 'requires_authentication'}
|
||||
{@const isSuccess = status === 'paid' || status === 'succeeded'}
|
||||
{@const isWarning = status === 'pending'}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={status === 'requires_authentication'
|
||||
? 'failed'
|
||||
: status}
|
||||
type={isDanger
|
||||
? 'error'
|
||||
: isWarning
|
||||
? 'warning'
|
||||
: isSuccess
|
||||
? 'success'
|
||||
: undefined} />
|
||||
{#if invoice?.lastError}
|
||||
<Popover let:toggle>
|
||||
<Link.Button on:click={toggle}>Details</Link.Button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
The scheduled payment has failed.
|
||||
<Link.Button on:click={() => retryPayment(invoice)}
|
||||
>Try again
|
||||
</Link.Button>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="amount" {root}>
|
||||
{formatCurrency(invoice.grossAmount)}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="status" {root}>
|
||||
<Popover let:toggle placement="bottom-start" padding="none">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
<!-- todo: add missing event -->
|
||||
<ActionMenu.Item.Anchor
|
||||
leadingIcon={IconExternalLink}
|
||||
external
|
||||
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
|
||||
View invoice
|
||||
</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Anchor
|
||||
leadingIcon={IconDownload}
|
||||
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
|
||||
Download PDF
|
||||
</ActionMenu.Item.Anchor>
|
||||
{#if status === 'overdue' || status === 'failed' || status === 'abandoned'}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconRefresh}
|
||||
on:click={() => {
|
||||
retryPayment(invoice);
|
||||
trackEvent(`click_retry_payment`, {
|
||||
from: 'button',
|
||||
source: 'billing_invoice_menu'
|
||||
});
|
||||
}}>
|
||||
Retry payment
|
||||
</ActionMenu.Item.Button>
|
||||
{:else}
|
||||
{#each invoiceList?.invoices as invoice (invoice.$id)}
|
||||
{@const status = invoice.status}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="dueDate" {root}
|
||||
>{toLocaleDate(invoice.dueAt)}</Table.Cell>
|
||||
<Table.Cell column="status" {root}>
|
||||
{@const isDanger =
|
||||
status === 'overdue' ||
|
||||
status === 'failed' ||
|
||||
status === 'requires_authentication'}
|
||||
{@const isSuccess = status === 'paid' || status === 'succeeded'}
|
||||
{@const isWarning = status === 'pending'}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={status === 'requires_authentication'
|
||||
? 'failed'
|
||||
: status}
|
||||
type={isDanger
|
||||
? 'error'
|
||||
: isWarning
|
||||
? 'warning'
|
||||
: isSuccess
|
||||
? 'success'
|
||||
: undefined} />
|
||||
{#if invoice?.lastError}
|
||||
<Popover let:toggle>
|
||||
<Link.Button on:click={toggle}>Details</Link.Button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
The scheduled payment has failed.
|
||||
<Link.Button on:click={() => retryPayment(invoice)}
|
||||
>Try again
|
||||
</Link.Button>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="amount" {root}>
|
||||
{formatCurrency(invoice.grossAmount)}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="actions" {root}>
|
||||
<Popover let:toggle placement="bottom-start" padding="none">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
<!-- todo: add missing event -->
|
||||
<ActionMenu.Item.Anchor
|
||||
leadingIcon={IconExternalLink}
|
||||
external
|
||||
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
|
||||
View invoice
|
||||
</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Anchor
|
||||
leadingIcon={IconDownload}
|
||||
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
|
||||
Download PDF
|
||||
</ActionMenu.Item.Anchor>
|
||||
{#if status === 'overdue' || status === 'failed' || status === 'abandoned'}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconRefresh}
|
||||
on:click={() => {
|
||||
retryPayment(invoice);
|
||||
trackEvent(`click_retry_payment`, {
|
||||
from: 'button',
|
||||
source: 'billing_invoice_menu'
|
||||
});
|
||||
}}>
|
||||
Retry payment
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
{/if}
|
||||
</Table.Root>
|
||||
{#if invoiceList.total > limit}
|
||||
{#if invoiceList.total >= limit}
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<p class="text">Total results: {invoiceList.total}</p>
|
||||
<PaginationInline {limit} bind:offset total={invoiceList.total} hidePages />
|
||||
<PaginationInline
|
||||
{limit}
|
||||
hidePages
|
||||
bind:offset
|
||||
total={invoiceList.total}
|
||||
on:change={() => request()} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user