Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions src/lib/components/archiveProject.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import ArchivedPaginationWithLimit from './archivedPaginationWithLimit.svelte';
import { Button, InputText } from '$lib/elements/forms';
import { DropList, GridItem1, CardContainer, Modal } from '$lib/components';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
Expand Down Expand Up @@ -48,9 +49,19 @@
projectsToArchive: Models.Project[];
organization: Organization;
currentPlan: Plan;
archivedTotalOverall: number;
archivedOffset: number;
limit: number;
}

let { projectsToArchive, organization, currentPlan }: Props = $props();
let {
projectsToArchive,
organization,
currentPlan,
archivedTotalOverall,
archivedOffset,
limit
}: Props = $props();

// Track Read-only info droplist per archived project
let readOnlyInfoOpen = $state<Record<string, boolean>>({});
Expand Down Expand Up @@ -189,21 +200,21 @@
}

import { formatName as formatNameHelper } from '$lib/helpers/string';
function formatName(name: string, limit: number = 19) {
function formatName(name: string, limit: number = 16) {
return formatNameHelper(name, limit, $isSmallViewport);
}
</script>

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

<div class="archive-projects-margin">
<CardContainer disableEmpty={true} total={projectsToArchive.length}>
<CardContainer disableEmpty={true} total={archivedTotalOverall}>
{#each projectsToArchive as project}
{@const platforms = filterPlatforms(
project.platforms.map((platform) => getPlatformInfo(platform.type))
Expand Down Expand Up @@ -292,7 +303,7 @@
</Badge>
{/each}

{#if platforms.length > 3}
{#if platforms.length > 2}
<Badge
variant="secondary"
content={`+${platforms.length - 2}`}
Expand All @@ -308,6 +319,14 @@
</GridItem1>
{/each}
</CardContainer>

<div class="pagination-container">
Comment thread
HarshMN2345 marked this conversation as resolved.
Outdated
<ArchivedPaginationWithLimit
name="Archived Projects"
{limit}
offset={archivedOffset}
total={archivedTotalOverall} />
</div>
</div>
</Accordion>
</div>
Expand Down Expand Up @@ -381,4 +400,7 @@
align-items: center;
gap: 8px;
}
.pagination-container {
margin-top: 16px;
}
</style>
44 changes: 44 additions & 0 deletions src/lib/components/archivedLimit.svelte
Comment thread
HarshMN2345 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page as pageStore } from '$app/state';
import { InputSelect } from '$lib/elements/forms';
import { preferences } from '$lib/stores/preferences';
import { Layout } from '@appwrite.io/pink-svelte';

let { sum, limit, name }: { sum: number; limit: number; name: string } = $props();

const options = [
{ label: '6', value: 6 },
{ label: '12', value: 12 },
{ label: '24', value: 24 },
{ label: '48', value: 48 },
{ label: '96', value: 96 }
];

async function limitChange() {
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit'));
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);

// Handle archived page pagination
if (url.searchParams.has('archivedPage')) {
const page = Number(url.searchParams.get('archivedPage'));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
if (newPage === 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', newPage.toString());
}
}

await goto(url.toString());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix off-by-one and null handling when recalculating archivedPage on limit change

  • Off-by-one: should be floor(startIndex/newLimit) + 1.
  • When limit query is missing, Number(null) becomes 0 and can produce page 0.
  • Clamp to page >= 1 and defend against zero/invalid limits.
-        const previousLimit = Number(url.searchParams.get('limit'));
+        const previousLimit = Number(url.searchParams.get('limit') ?? limit) || limit;
@@
-        if (url.searchParams.has('archivedPage')) {
-            const page = Number(url.searchParams.get('archivedPage'));
-            const newPage = Math.floor(((page - 1) * previousLimit) / limit);
-            if (newPage === 1) {
+        if (url.searchParams.has('archivedPage')) {
+            const page = Math.max(1, Number(url.searchParams.get('archivedPage')) || 1);
+            const startIndex = (page - 1) * Math.max(1, previousLimit);
+            const newPage = Math.max(1, Math.floor(startIndex / Math.max(1, limit)) + 1);
+            if (newPage <= 1) {
                 url.searchParams.delete('archivedPage');
             } else {
-                url.searchParams.set('archivedPage', newPage.toString());
+                url.searchParams.set('archivedPage', String(newPage));
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function limitChange() {
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit'));
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);
// Handle archived page pagination
if (url.searchParams.has('archivedPage')) {
const page = Number(url.searchParams.get('archivedPage'));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
if (newPage === 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', newPage.toString());
}
}
await goto(url.toString());
}
async function limitChange() {
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit') ?? limit) || limit;
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);
// Handle archived page pagination
if (url.searchParams.has('archivedPage')) {
const page = Math.max(1, Number(url.searchParams.get('archivedPage')) || 1);
const startIndex = (page - 1) * Math.max(1, previousLimit);
const newPage = Math.max(1, Math.floor(startIndex / Math.max(1, limit)) + 1);
if (newPage <= 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', String(newPage));
}
}
await goto(url.toString());
}

</script>

<Layout.Stack direction="row" alignItems="center" inline>
<InputSelect id="archived-rows" {options} bind:value={limit} on:change={limitChange} />
<p class="text" style:white-space="nowrap">
{name} per page. Total: {sum >= 5000 ? `${sum}+` : sum}
</p>
</Layout.Stack>
23 changes: 23 additions & 0 deletions src/lib/components/archivedPagination.svelte
Comment thread
HarshMN2345 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { page as pageStore } from '$app/state';
import { Pagination } from '@appwrite.io/pink-svelte';

let { sum, limit, offset }: { sum: number; limit: number; offset: number } = $props();

const currentPage = $derived(Math.floor(offset / limit + 1));

function getLink(page: number): string {
const url = new URL(pageStore.url);
if (page === 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', page.toString());
}

return url.toString();
}

const paginationProps = $derived({ type: 'link', createLink: getLink } as const);
</script>

<Pagination on:page {limit} total={sum} page={currentPage} {...paginationProps} />
31 changes: 31 additions & 0 deletions src/lib/components/archivedPaginationWithLimit.svelte
Comment thread
HarshMN2345 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import ArchivedLimit from './archivedLimit.svelte';
import ArchivedPagination from './archivedPagination.svelte';
import { Layout } from '@appwrite.io/pink-svelte';

let {
limit,
offset,
total,
name,
useCreateLink = true
}: {
limit: number;
offset: number;
total: number;
name: string;
useCreateLink?: boolean;
} = $props();

const showLimit = $derived(!!useCreateLink);
const direction = $derived(showLimit ? 'row' : 'column');
const alignItems = $derived(showLimit ? 'center' : 'flex-end');
</script>

<Layout.Stack wrap="wrap" {direction} {alignItems} justifyContent="space-between">
{#if showLimit}
<ArchivedLimit {limit} sum={total} {name} />
{/if}

<ArchivedPagination on:page {limit} {offset} sum={total} />
</Layout.Stack>
25 changes: 18 additions & 7 deletions src/routes/(console)/organization-[organization]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,19 @@
return project.status === 'archived';
}

$: projectsToArchive = isCloud
? data.projects.projects.filter((project) => project.status === 'archived')
: [];
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
Comment thread
HarshMN2345 marked this conversation as resolved.
Outdated
);

$: activeProjects = data.projects.projects.filter((project) => project.status !== 'archived');
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'active'
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct archived/active filters for non-cloud (null means active)

Per prior guidance, only status === 'archived' is archived; null is active. Current fallback misclassifies nulls.

-    $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
-        (project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
-    );
+    $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
+        (project) => project.status === 'archived'
+    );
@@
-    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
-        (project) => project.status === 'active'
-    );
+    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
+        (project) => project.status !== 'archived'
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
);
$: activeProjects = data.projects.projects.filter((project) => project.status !== 'archived');
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'active'
);
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'archived'
);
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status !== 'archived'
);
🤖 Prompt for AI Agents
In src/routes/(console)/organization-[organization]/+page.svelte around lines
112–118, the fallback for non-cloud currently treats null as non-active causing
misclassification; change the archived filter to only include project.status ===
'archived' (no non-cloud fallback that treats null as archived), and change the
activeProjects filter to include project.status === 'active' or, for non-cloud,
treat null as active (i.e. project.status === 'active' || project.status == null
when isCloud is false).

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

$: activeTotalOverall =
data?.activeTotalOverall ??
data?.organization?.projects?.length ??
data?.projects?.total ??
0;
function clearSearch() {
searchQuery?.clearInput();
}
Expand Down Expand Up @@ -165,7 +173,7 @@
{#if activeProjects.length > 0}
<CardContainer
disableEmpty={!$canWriteProjects}
total={data.projects.total}
total={activeTotalOverall}
offset={data.offset}
on:click={handleCreateProject}>
{#each activeProjects as project}
Expand Down Expand Up @@ -250,13 +258,16 @@
name="Projects"
limit={data.limit}
offset={data.offset}
total={data.projects.total} />
total={activeTotalOverall} />

<!-- Archived Projects Section -->
<ArchiveProject
{projectsToArchive}
organization={data.organization}
currentPlan={$currentPlan} />
currentPlan={$currentPlan}
archivedTotalOverall={data.archivedTotalOverall}
archivedOffset={data.archivedOffset}
limit={data.limit} />
</Container>
<CreateOrganization bind:show={addOrganization} />
<CreateProject bind:show={showCreate} teamId={page.params.organization} />
Expand Down
50 changes: 47 additions & 3 deletions src/routes/(console)/organization-[organization]/+page.ts
Comment thread
HarshMN2345 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,69 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const offset = pageToOffset(page, limit);
const search = getSearch(url);

const projects = await sdk.forConsole.projects.list({
const archivedPage = parseInt(url.searchParams.get('archivedPage') || '1');
const archivedOffset = pageToOffset(archivedPage, limit);

Comment thread
HarshMN2345 marked this conversation as resolved.
Outdated
// fetch active projects with offset set
const activeProjects = await sdk.forConsole.projects.list({
queries: [
Query.offset(offset),
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
});

// Fetch archived projects with separate pagination
const archivedProjects = await sdk.forConsole.projects.list({
queries: [
Query.offset(archivedOffset),
Query.equal('teamId', params.organization),
Query.equal('status', 'archived'),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
});

// get total counts
const activeTotal = await sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')])
],
search: search || undefined
});

const archivedTotal = await sdk.forConsole.projects.list({
queries: [Query.equal('teamId', params.organization), Query.equal('status', 'archived')],
search: search || undefined
});

// set `default` if no region!
for (const project of projects.projects) {
for (const project of activeProjects.projects) {
project.region ??= 'default';
}
for (const project of archivedProjects.projects) {
project.region ??= 'default';
}

return {
offset,
limit,
projects,
projects: {
...activeProjects,
projects: activeProjects.projects,
total: activeTotal.total
},
activeProjectsPage: activeProjects.projects,
archivedProjectsPage: archivedProjects.projects,
activeTotalOverall: activeTotal.total,
archivedTotalOverall: archivedTotal.total,
archivedOffset,
archivedPage,
search
};
};