From 491c98df3d50cec37d35f7e89ce2bf4be4aa5667 Mon Sep 17 00:00:00 2001 From: Felix Schlegel Date: Sat, 11 Jan 2025 02:35:38 +0100 Subject: [PATCH] feat: add project papers page --- src/lib/backend-api.ts | 1 + src/lib/controller/project-controller.ts | 15 ++- .../project/[projectId]/papers/+page.svelte | 100 +++++++++++++++++- .../project/[projectId]/papers/+page.ts | 86 +++++++++++++++ .../project/[projectId]/papers/types.ts | 6 ++ 5 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/routes/project/[projectId]/papers/+page.ts create mode 100644 src/routes/project/[projectId]/papers/types.ts diff --git a/src/lib/backend-api.ts b/src/lib/backend-api.ts index e2909e67..440365ce 100644 --- a/src/lib/backend-api.ts +++ b/src/lib/backend-api.ts @@ -76,6 +76,7 @@ export interface IProjectController { getStageCount(): Promise; getCurrentStage(): Promise; stage(stageIndex: number): IStageController; + getAllPapers(): Promise; getMembers(): Promise; inviteUser(email: string): Promise; diff --git a/src/lib/controller/project-controller.ts b/src/lib/controller/project-controller.ts index 1283bba3..d5ebdb8c 100644 --- a/src/lib/controller/project-controller.ts +++ b/src/lib/controller/project-controller.ts @@ -1,4 +1,11 @@ -import type { Criterion, CriterionSpec, Project, ProjectSpec, User } from "$lib/model/backend"; +import type { + Criterion, + CriterionSpec, + Project, + ProjectSpec, + StageEntry, + User, +} from "$lib/model/backend"; import type { ICriterionController, IProjectController, IStageController } from "../backend-api"; import { CriterionController } from "./criterion-controller"; import { HttpClient } from "./http-client"; @@ -61,7 +68,7 @@ export class ProjectController implements IProjectController { } async getCriteria(): Promise { - throw new Error("Method not implemented."); + return this.client.get("criteria").then((response) => response.json()); } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -72,4 +79,8 @@ export class ProjectController implements IProjectController { criterion(criterionId: number): ICriterionController { return new CriterionController(this.path, criterionId); } + + async getAllPapers(): Promise { + return this.client.get("papers").then((response) => response.json()); + } } diff --git a/src/routes/project/[projectId]/papers/+page.svelte b/src/routes/project/[projectId]/papers/+page.svelte index 10467a3d..566efdc9 100644 --- a/src/routes/project/[projectId]/papers/+page.svelte +++ b/src/routes/project/[projectId]/papers/+page.svelte @@ -1,8 +1,44 @@ @@ -15,3 +51,65 @@ {/await} +
+
+
+
+ + {}} /> +
+ {#if showFilters} +
+ + + + + + +
+ {/if} +
+
+ + {#each papersByStage as stage, index} + + Stage {stage.stageIndex} + +
+ {#each stage.papers as { paper }} + + {/each} +
+
+
+ {/each} +
+
+
+ +
diff --git a/src/routes/project/[projectId]/papers/+page.ts b/src/routes/project/[projectId]/papers/+page.ts new file mode 100644 index 00000000..ed4de924 --- /dev/null +++ b/src/routes/project/[projectId]/papers/+page.ts @@ -0,0 +1,86 @@ +import { ProjectController } from "$lib/controller/project-controller"; +import type { StageEntry } from "$lib/model/backend"; +import type { PageLoad } from "./$types"; +import type { PapersByStage } from "./types"; + +export const load: PageLoad = ({ params }) => { + const projectId = Number(params.projectId); + if (Number.isNaN(projectId)) { + throw new Error(`Invalid projectId ${params.projectId}`); + } + + const projectController = new ProjectController(projectId); + + const loadingCriteria = projectController.getCriteria(); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingCriteria.catch(() => {}); + + const loadingStageCount = projectController.getStageCount(); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingStageCount.catch(() => {}); + + const loadingStageEntries = projectController.getAllPapers(); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingStageEntries.catch(() => {}); + + const loadingPapersByStage = Promise.all([loadingStageEntries, loadingStageCount]).then( + ([stageEntries, stageCount]) => getPapersByStage(stageEntries, stageCount), + ); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingPapersByStage.catch(() => {}); + + const loadingYears = loadingStageEntries.then((stageEntries) => { + const years = new Set(); + stageEntries.forEach((stageEntry) => { + years.add(stageEntry.paper.year ?? -1); + }); + return Array.from(years); + }); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingYears.catch(() => {}); + + const loadingPublishers = loadingStageEntries.then((stageEntries) => { + const publishers = new Set(); + stageEntries.forEach((stageEntry) => { + publishers.add(stageEntry.paper.publisherName); + }); + return Array.from(publishers); + }); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingPublishers.catch(() => {}); + + const loadingMembers = projectController.getMembers(); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingMembers.catch(() => {}); + + const loadingReviewers = Promise.all([loadingStageEntries, loadingMembers]).then( + ([stageEntries, members]) => { + const reviewerIds = new Set(); + stageEntries.forEach((stageEntry) => { + stageEntry.paper.reviewData?.reviews.forEach((review) => { + reviewerIds.add(review.user.id); + }); + }); + return members.filter((member) => reviewerIds.has(member.id)); + }, + ); + // attach noop-catch to handle promise rejection correctly (see https://svelte.dev/docs/kit/load#Streaming-with-promises) + loadingReviewers.catch(() => {}); + + return { + loadingCriteria, + loadingStageCount, + loadingPapersByStage, + loadingYears, + loadingPublishers, + loadingReviewers, + }; +}; + +function getPapersByStage(papers: StageEntry[], stageCount: number): PapersByStage[] { + const papersByStage: PapersByStage[] = []; + for (let i = 0; i <= stageCount; i++) { + papersByStage.push({ stageIndex: i, papers: papers.filter((paper) => paper.stage === i) }); + } + return papersByStage; +} diff --git a/src/routes/project/[projectId]/papers/types.ts b/src/routes/project/[projectId]/papers/types.ts new file mode 100644 index 00000000..49992510 --- /dev/null +++ b/src/routes/project/[projectId]/papers/types.ts @@ -0,0 +1,6 @@ +import type { StageEntry } from "$lib/model/backend"; + +export interface PapersByStage { + stageIndex: number; + papers: StageEntry[]; +}