Skip to content

Commit cc9a62c

Browse files
committed
feat(org-projects): add separate pagination for archived projects and optimize queries
1 parent 11e460c commit cc9a62c

6 files changed

Lines changed: 265 additions & 143 deletions

File tree

src/lib/components/archiveProject.svelte

Lines changed: 119 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { Button } from '$lib/elements/forms';
3-
import { DropList, GridItem1, CardContainer, Paginator } from '$lib/components';
3+
import { DropList, GridItem1, CardContainer } from '$lib/components';
4+
import ArchivedPaginationWithLimit from './archivedPaginationWithLimit.svelte';
45
import {
56
Badge,
67
Icon,
@@ -45,9 +46,19 @@
4546
projectsToArchive: Models.Project[];
4647
organization: Organization;
4748
currentPlan: Plan;
49+
archivedTotalOverall: number;
50+
archivedOffset: number;
51+
limit: number;
4852
}
4953
50-
let { projectsToArchive, organization, currentPlan }: Props = $props();
54+
let {
55+
projectsToArchive,
56+
organization,
57+
currentPlan,
58+
archivedTotalOverall,
59+
archivedOffset,
60+
limit
61+
}: Props = $props();
5162
5263
// Track Read-only info droplist per archived project
5364
let readOnlyInfoOpen = $state<Record<string, boolean>>({});
@@ -153,128 +164,118 @@
153164

154165
{#if projectsToArchive.length > 0}
155166
<div class="archive-projects-margin-top">
156-
<Accordion title="Archived projects" badge={`${projectsToArchive.length}`}>
167+
<Accordion title="Archived projects" badge={`${archivedTotalOverall}`}>
157168
<Typography.Text tag="p" size="s">
158169
These projects have been archived and are read-only. You can view and migrate their
159170
data.
160171
</Typography.Text>
161172

162173
<div class="archive-projects-margin">
163-
<Paginator
164-
items={projectsToArchive}
165-
limit={6}
166-
hidePages={false}
167-
hideFooter={projectsToArchive.length <= 6}>
168-
{#snippet children(items)}
169-
<CardContainer disableEmpty={true} total={projectsToArchive.length}>
170-
{#each items as project}
171-
{@const platforms = filterPlatforms(
172-
project.platforms.map((platform) =>
173-
getPlatformInfo(platform.type)
174-
)
175-
)}
176-
{@const formatted = formatName(project.name)}
177-
<GridItem1>
178-
<svelte:fragment slot="eyebrow">
179-
{project?.platforms?.length
180-
? project?.platforms?.length
181-
: 'No'} apps
182-
</svelte:fragment>
183-
<svelte:fragment slot="title">{formatted}</svelte:fragment>
184-
<svelte:fragment slot="status">
185-
<div class="status-container">
186-
<DropList
187-
bind:show={readOnlyInfoOpen[project.$id]}
188-
placement="bottom-start"
189-
noArrow>
190-
<Tag
191-
size="s"
192-
style="white-space: nowrap;"
193-
on:click={(e) => {
194-
e.preventDefault();
195-
e.stopPropagation();
196-
readOnlyInfoOpen = {
197-
...readOnlyInfoOpen,
198-
[project.$id]:
199-
!readOnlyInfoOpen[project.$id]
200-
};
201-
}}>
202-
<Icon icon={IconInfo} size="s" />
203-
<span>Read only</span>
204-
</Tag>
205-
<svelte:fragment slot="list">
206-
<li
207-
class="drop-list-item u-width-250"
208-
style="padding: var(--space-5, 12px) var(--space-6, 16px)">
209-
<span class="u-block u-mb-8">
210-
Archived projects are read-only. You can
211-
view and migrate their data, but they no
212-
longer accept edits or requests.
213-
</span>
214-
</li>
215-
</svelte:fragment>
216-
</DropList>
217-
<Popover
218-
let:toggle
219-
padding="none"
220-
placement="bottom-end">
221-
<Button
222-
text
223-
icon
224-
size="s"
225-
ariaLabel="more options"
226-
on:click={(e) => {
227-
e.preventDefault();
228-
e.stopPropagation();
229-
toggle(e);
230-
}}>
231-
<Icon icon={IconDotsHorizontal} size="s" />
232-
</Button>
233-
<ActionMenu.Root slot="tooltip">
234-
<ActionMenu.Item.Button
235-
leadingIcon={IconInboxIn}
236-
disabled={isUnarchiveDisabled()}
237-
on:click={() =>
238-
handleUnarchiveProject(project)}
239-
>Unarchive project</ActionMenu.Item.Button>
240-
<ActionMenu.Item.Button
241-
leadingIcon={IconSwitchHorizontal}
242-
on:click={() =>
243-
handleMigrateProject(project)}
244-
>Migrate project</ActionMenu.Item.Button>
245-
</ActionMenu.Root>
246-
</Popover>
247-
</div>
248-
</svelte:fragment>
174+
<CardContainer disableEmpty={true} total={archivedTotalOverall}>
175+
{#each projectsToArchive as project}
176+
{@const platforms = filterPlatforms(
177+
project.platforms.map((platform) => getPlatformInfo(platform.type))
178+
)}
179+
{@const formatted = formatName(project.name)}
180+
<GridItem1>
181+
<svelte:fragment slot="eyebrow">
182+
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
183+
</svelte:fragment>
184+
<svelte:fragment slot="title">{formatted}</svelte:fragment>
185+
<svelte:fragment slot="status">
186+
<div class="status-container">
187+
<DropList
188+
bind:show={readOnlyInfoOpen[project.$id]}
189+
placement="bottom-start"
190+
noArrow>
191+
<Tag
192+
size="s"
193+
style="white-space: nowrap;"
194+
on:click={(e) => {
195+
e.preventDefault();
196+
e.stopPropagation();
197+
readOnlyInfoOpen = {
198+
...readOnlyInfoOpen,
199+
[project.$id]: !readOnlyInfoOpen[project.$id]
200+
};
201+
}}>
202+
<Icon icon={IconInfo} size="s" />
203+
<span>Read only</span>
204+
</Tag>
205+
<svelte:fragment slot="list">
206+
<li
207+
class="drop-list-item u-width-250"
208+
style="padding: var(--space-5, 12px) var(--space-6, 16px)">
209+
<span class="u-block u-mb-8">
210+
Archived projects are read-only. You can view
211+
and migrate their data, but they no longer
212+
accept edits or requests.
213+
</span>
214+
</li>
215+
</svelte:fragment>
216+
</DropList>
217+
<Popover let:toggle padding="none" placement="bottom-end">
218+
<Button
219+
text
220+
icon
221+
size="s"
222+
ariaLabel="more options"
223+
on:click={(e) => {
224+
e.preventDefault();
225+
e.stopPropagation();
226+
toggle(e);
227+
}}>
228+
<Icon icon={IconDotsHorizontal} size="s" />
229+
</Button>
230+
<ActionMenu.Root slot="tooltip">
231+
<ActionMenu.Item.Button
232+
leadingIcon={IconInboxIn}
233+
disabled={isUnarchiveDisabled()}
234+
on:click={() => handleUnarchiveProject(project)}
235+
>Unarchive project</ActionMenu.Item.Button>
236+
<ActionMenu.Item.Button
237+
leadingIcon={IconSwitchHorizontal}
238+
on:click={() => handleMigrateProject(project)}
239+
>Migrate project</ActionMenu.Item.Button>
240+
</ActionMenu.Root>
241+
</Popover>
242+
</div>
243+
</svelte:fragment>
249244

250-
{#each platforms.slice(0, 2) as platform}
251-
{@const icon = getIconForPlatform(platform.icon)}
252-
<Badge
253-
variant="secondary"
254-
content={platform.name}
255-
style="width: max-content;">
256-
<Icon {icon} size="s" slot="start" />
257-
</Badge>
258-
{/each}
245+
{#each platforms.slice(0, 2) as platform}
246+
{@const icon = getIconForPlatform(platform.icon)}
247+
<Badge
248+
variant="secondary"
249+
content={platform.name}
250+
style="width: max-content;">
251+
<Icon {icon} size="s" slot="start" />
252+
</Badge>
253+
{/each}
259254

260-
{#if platforms.length > 2}
261-
<Badge
262-
variant="secondary"
263-
content={`+${platforms.length - 2}`}
264-
style="width: max-content;" />
265-
{/if}
255+
{#if platforms.length > 2}
256+
<Badge
257+
variant="secondary"
258+
content={`+${platforms.length - 2}`}
259+
style="width: max-content;" />
260+
{/if}
266261

267-
<svelte:fragment slot="icons">
268-
{#if isCloud && $regionsStore?.regions}
269-
{@const region = findRegion(project)}
270-
<Typography.Text>{region?.name}</Typography.Text>
271-
{/if}
272-
</svelte:fragment>
273-
</GridItem1>
274-
{/each}
275-
</CardContainer>
276-
{/snippet}
277-
</Paginator>
262+
<svelte:fragment slot="icons">
263+
{#if isCloud && $regionsStore?.regions}
264+
{@const region = findRegion(project)}
265+
<Typography.Text>{region?.name}</Typography.Text>
266+
{/if}
267+
</svelte:fragment>
268+
</GridItem1>
269+
{/each}
270+
</CardContainer>
271+
272+
<div class="pagination-container">
273+
<ArchivedPaginationWithLimit
274+
name="Archived Projects"
275+
{limit}
276+
offset={archivedOffset}
277+
total={archivedTotalOverall} />
278+
</div>
278279
</div>
279280
</Accordion>
280281
</div>
@@ -307,4 +308,7 @@
307308
align-items: center;
308309
gap: 8px;
309310
}
311+
.pagination-container {
312+
margin-top: 16px;
313+
}
310314
</style>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script lang="ts">
2+
import { goto } from '$app/navigation';
3+
import { page as pageStore } from '$app/state';
4+
import { InputSelect } from '$lib/elements/forms';
5+
import { preferences } from '$lib/stores/preferences';
6+
import { Layout } from '@appwrite.io/pink-svelte';
7+
8+
let { sum, limit, name }: { sum: number; limit: number; name: string } = $props();
9+
10+
const options = [
11+
{ label: '6', value: 6 },
12+
{ label: '12', value: 12 },
13+
{ label: '24', value: 24 },
14+
{ label: '48', value: 48 },
15+
{ label: '96', value: 96 }
16+
];
17+
18+
async function limitChange() {
19+
const url = new URL(pageStore.url);
20+
const previousLimit = Number(url.searchParams.get('limit'));
21+
url.searchParams.set('limit', limit.toString());
22+
preferences.setLimit(limit);
23+
24+
// Handle archived page pagination
25+
if (url.searchParams.has('archivedPage')) {
26+
const page = Number(url.searchParams.get('archivedPage'));
27+
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
28+
if (newPage === 1) {
29+
url.searchParams.delete('archivedPage');
30+
} else {
31+
url.searchParams.set('archivedPage', newPage.toString());
32+
}
33+
}
34+
35+
await goto(url.toString());
36+
}
37+
</script>
38+
39+
<Layout.Stack direction="row" alignItems="center" inline>
40+
<InputSelect id="archived-rows" {options} bind:value={limit} on:change={limitChange} />
41+
<p class="text" style:white-space="nowrap">
42+
{name} per page. Total: {sum >= 5000 ? `${sum}+` : sum}
43+
</p>
44+
</Layout.Stack>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script lang="ts">
2+
import { page as pageStore } from '$app/state';
3+
import { Pagination } from '@appwrite.io/pink-svelte';
4+
5+
let { sum, limit, offset }: { sum: number; limit: number; offset: number } = $props();
6+
7+
const currentPage = $derived(Math.floor(offset / limit + 1));
8+
9+
function getLink(page: number): string {
10+
const url = new URL(pageStore.url);
11+
if (page === 1) {
12+
url.searchParams.delete('archivedPage');
13+
} else {
14+
url.searchParams.set('archivedPage', page.toString());
15+
}
16+
17+
return url.toString();
18+
}
19+
20+
const paginationProps = $derived({ type: 'link', createLink: getLink } as const);
21+
</script>
22+
23+
<Pagination on:page {limit} total={sum} page={currentPage} {...paginationProps} />
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script lang="ts">
2+
import ArchivedLimit from './archivedLimit.svelte';
3+
import ArchivedPagination from './archivedPagination.svelte';
4+
import { Layout } from '@appwrite.io/pink-svelte';
5+
6+
let {
7+
limit,
8+
offset,
9+
total,
10+
name,
11+
useCreateLink = true
12+
}: {
13+
limit: number;
14+
offset: number;
15+
total: number;
16+
name: string;
17+
useCreateLink?: boolean;
18+
} = $props();
19+
20+
const showLimit = $derived(!!useCreateLink);
21+
const direction = $derived(showLimit ? 'row' : 'column');
22+
const alignItems = $derived(showLimit ? 'center' : 'flex-end');
23+
</script>
24+
25+
<Layout.Stack wrap="wrap" {direction} {alignItems} justifyContent="space-between">
26+
{#if showLimit}
27+
<ArchivedLimit {limit} sum={total} {name} />
28+
{/if}
29+
30+
<ArchivedPagination on:page {limit} {offset} sum={total} />
31+
</Layout.Stack>

src/routes/(console)/organization-[organization]/+page.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,10 @@
257257
<ArchiveProject
258258
{projectsToArchive}
259259
organization={data.organization}
260-
currentPlan={$currentPlan} />
260+
currentPlan={$currentPlan}
261+
archivedTotalOverall={data.archivedTotalOverall}
262+
archivedOffset={data.archivedOffset}
263+
limit={data.limit} />
261264
</Container>
262265
<CreateOrganization bind:show={addOrganization} />
263266
<CreateProject bind:show={showCreate} teamId={page.params.organization} />

0 commit comments

Comments
 (0)