Skip to content

Commit

Permalink
feat: add project papers page
Browse files Browse the repository at this point in the history
  • Loading branch information
Slartibartfass2 committed Jan 15, 2025
1 parent a9e96e8 commit 491c98d
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/lib/backend-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface IProjectController {
getStageCount(): Promise<number>;
getCurrentStage(): Promise<number>;
stage(stageIndex: number): IStageController;
getAllPapers(): Promise<StageEntry[]>;

getMembers(): Promise<User[]>;
inviteUser(email: string): Promise<void>;
Expand Down
15 changes: 13 additions & 2 deletions src/lib/controller/project-controller.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -61,7 +68,7 @@ export class ProjectController implements IProjectController {
}

async getCriteria(): Promise<Criterion[]> {
throw new Error("Method not implemented.");
return this.client.get("criteria").then((response) => response.json());
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -72,4 +79,8 @@ export class ProjectController implements IProjectController {
criterion(criterionId: number): ICriterionController {
return new CriterionController(this.path, criterionId);
}

async getAllPapers(): Promise<StageEntry[]> {
return this.client.get("papers").then((response) => response.json());
}
}
100 changes: 99 additions & 1 deletion src/routes/project/[projectId]/papers/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
<script lang="ts">
import ProjectNavigationBar from "$lib/components/composites/navigation-bar/ProjectNavigationBar.svelte";
import PaperListEntry from "$lib/components/composites/paper-components/PaperListEntry.svelte";
import SearchBar from "$lib/components/composites/search-bar/SearchBar.svelte";
import * as Accordion from "$lib/components/primitives/accordion/index.js";
import Button from "$lib/components/primitives/button/button.svelte";
import ChevronDown from "lucide-svelte/icons/chevron-down";
import ChevronUp from "lucide-svelte/icons/chevron-up";
import * as Card from "$lib/components/primitives/card/index.js";
import StagesSelect from "./StagesSelect.svelte";
import ReviewersSelect from "./ReviewersSelect.svelte";
import PublishersSelect from "./PublishersSelect.svelte";
import YearsSelect from "./YearsSelect.svelte";
import DecisionsSelect from "./DecisionsSelect.svelte";
import CriteriaSelect from "./CriteriaSelect.svelte";
import type { PapersByStage } from "./types";
let { data } = $props();
const { user, projectId, loadingProject } = data;
const {
user,
projectId,
loadingProject,
loadingCriteria,
loadingStageCount,
loadingPapersByStage,
loadingYears,
loadingPublishers,
loadingReviewers,
} = data;
let papersByStage = $state<PapersByStage[]>([]);
loadingPapersByStage
.then((loadedPapersByStage) => {
papersByStage = loadedPapersByStage;
})
.catch((error) => {
console.error(error);
});
let showFilters = $state(true);
</script>

<svelte:head>
Expand All @@ -15,3 +51,65 @@
{/await}
</svelte:head>
<ProjectNavigationBar {user} {projectId} {loadingProject} defaultTabValue="papers" />
<main class="flex flex-row h-full w-full px-4 py-2 gap-10">
<div class="flex flex-col w-full h-full gap-5">
<div class="flex flex-col w-full h-fit gap-2.5">
<div class="flex flex-row items-center gap-2.5">
<Button onclick={() => (showFilters = !showFilters)}>
Filters
{#if showFilters}
<ChevronUp class="size-4" />
{:else}
<ChevronDown class="size-4" />
{/if}
</Button>
<SearchBar placeholderText="Search paper" onSearch={() => {}} />
</div>
{#if showFilters}
<div class="flex flex-row items-center gap-2.5 flex-wrap">
<StagesSelect {loadingStageCount} />
<ReviewersSelect {loadingReviewers} />
<PublishersSelect {loadingPublishers} />
<YearsSelect {loadingYears} />
<DecisionsSelect />
<CriteriaSelect {loadingCriteria} />
</div>
{/if}
</div>
<div class="w-full h-full">
<Accordion.Root type="multiple">
{#each papersByStage as stage, index}
<Accordion.Item value={`stage-${index}`}>
<Accordion.Trigger>Stage {stage.stageIndex}</Accordion.Trigger>
<Accordion.Content>
<div class="flex flex-col pl-5 gap-4">
{#each stage.papers as { paper }}
<PaperListEntry {paper} {projectId} showReviewStatus />
{/each}
</div>
</Accordion.Content>
</Accordion.Item>
{/each}
</Accordion.Root>
</div>
</div>
<Card.Root class="flex flex-col w-[60%] h-full gap-5 shadow-lg hidden">
<Card.Content>
<h2>General Information</h2>
<div class="flex flex-col gap-2.5">
<div class="flex flex-row justify-between items-center">
<h3>Number of Papers</h3>
<p>4</p>
</div>
<div class="flex flex-row justify-between items-center">
<h3>Number of Reviews</h3>
<p>8</p>
</div>
<div class="flex flex-row justify-between items-center">
<h3>Number of Decisions</h3>
<p>4</p>
</div>
</div>
</Card.Content>
</Card.Root>
</main>
86 changes: 86 additions & 0 deletions src/routes/project/[projectId]/papers/+page.ts
Original file line number Diff line number Diff line change
@@ -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<number>();
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<string>();
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<number>();
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;
}
6 changes: 6 additions & 0 deletions src/routes/project/[projectId]/papers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { StageEntry } from "$lib/model/backend";

export interface PapersByStage {
stageIndex: number;
papers: StageEntry[];
}

0 comments on commit 491c98d

Please sign in to comment.