Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add non-interactive Participants table for check-ins #335

Merged
merged 3 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
18 changes: 13 additions & 5 deletions apps/api/src/admin/participant_manager.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
from logging import getLogger
from typing import Optional
from typing import Any, Optional

from auth.user_identity import User, utc_now
from services import mongodb_handler
from services.mongodb_handler import Collection
from utils.user_record import Role, Status
from utils.user_record import Role, Status, UserRecord

log = getLogger(__name__)


async def get_attending_applicants() -> list[dict[str, object]]:
class Participant(UserRecord):
"""Participants attending the event."""

first_name: str
last_name: str
status: Status


async def get_attending_applicants() -> list[Participant]:
"""Fetch all applicants who have a status of ATTENDING"""
records = await mongodb_handler.retrieve(
records: list[dict[str, Any]] = await mongodb_handler.retrieve(
Collection.USERS,
{"role": Role.APPLICANT, "status": Status.ATTENDING},
[
Expand All @@ -23,7 +31,7 @@ async def get_attending_applicants() -> list[dict[str, object]]:
],
)

return records
return [Participant(**user, **user["application_data"]) for user in records]


async def check_in_applicant(uid: str, associate: User) -> None:
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/routers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pydantic import BaseModel, EmailStr, Field, TypeAdapter, ValidationError

from admin import participant_manager, summary_handler
from admin.participant_manager import Participant
from auth.authorization import require_role
from auth.user_identity import User, utc_now
from models.ApplicationData import Decision, Review
Expand Down Expand Up @@ -244,9 +245,9 @@ async def waitlist_release(uid: str) -> None:
log.info(f"Accepted {uid} off the waitlist and sent email.")


@router.get("/attending", dependencies=[Depends(require_checkin_associate)])
async def attending() -> list[dict[str, object]]:
"""Get list of attending participants."""
@router.get("/participants", dependencies=[Depends(require_checkin_associate)])
async def participants() -> list[Participant]:
"""Get list of participants."""
# TODO: non-hackers
return await participant_manager.get_attending_applicants()

Expand Down
88 changes: 87 additions & 1 deletion apps/site/src/app/admin/participants/Participants.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,91 @@
"use client";

import Box from "@cloudscape-design/components/box";
import Header from "@cloudscape-design/components/header";
import SpaceBetween from "@cloudscape-design/components/space-between";
import Table from "@cloudscape-design/components/table";
import TextFilter from "@cloudscape-design/components/text-filter";

import ApplicantStatus from "@/app/admin/applicants/components/ApplicantStatus";
import useParticipants from "@/lib/admin/useParticipants";

import ParticipantAction from "./components/ParticipantAction";
import RoleBadge from "./components/RoleBadge";

function Participants() {
return <></>;
const { participants, loading } = useParticipants();

// TODO: sorting
// TODO: search functionality
// TODO: role and status filters

const emptyMessage = (
<Box margin={{ vertical: "xs" }} textAlign="center" color="inherit">
<SpaceBetween size="m">
<b>No participants</b>
</SpaceBetween>
</Box>
);

return (
<Table
// TODO: aria labels
columnDefinitions={[
{
id: "uid",
header: "UID",
cell: (item) => item._id,
sortingField: "uid",
isRowHeader: true,
},
{
id: "firstName",
header: "First name",
cell: (item) => item.first_name,
sortingField: "firstName",
},
{
id: "lastName",
header: "Last name",
cell: (item) => item.last_name,
sortingField: "lastName",
},
{
id: "role",
header: "Role",
cell: RoleBadge,
sortingField: "role",
},
{
id: "status",
header: "Status",
cell: ApplicantStatus,
sortingField: "status",
},
{
id: "action",
header: "Action",
cell: ParticipantAction,
},
]}
header={
<Header counter={`(${participants.length})`}>Participants</Header>
}
items={participants}
loading={loading}
loadingText="Loading participants"
resizableColumns
variant="full-page"
stickyColumns={{ first: 1, last: 0 }}
trackBy="uid"
empty={emptyMessage}
filter={
<TextFilter filteringPlaceholder="Find participants" filteringText="" />
}
// TODO: pagination
// TODO: collection preferences
/>
);
}

export default Participants;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Button from "@cloudscape-design/components/button";

import { Participant } from "@/lib/admin/useParticipants";

function ParticipantAction({ _id }: Participant) {
// TODO: waitlist promotion
return (
<Button variant="inline-link" ariaLabel={`Check in ${_id}`}>
Check In
</Button>
);
}

export default ParticipantAction;
10 changes: 10 additions & 0 deletions apps/site/src/app/admin/participants/components/RoleBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Badge from "@cloudscape-design/components/badge";

import { Participant } from "@/lib/admin/useParticipants";

function RoleBadge({ role }: Participant) {
// TODO: custom colors
return <Badge>{role}</Badge>;
}

export default RoleBadge;
41 changes: 41 additions & 0 deletions apps/site/src/lib/admin/useParticipants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axios from "axios";
import useSWR from "swr";

import { Status, Uid } from "@/lib/admin/useApplicant";

const enum Role {
Director = "director",
Organizer = "organizer",
CheckInLead = "checkin_lead",
Applicant = "applicant",
Mentor = "mentor",
Volunteer = "volunteer",
Sponsor = "sponsor",
Judge = "judge",
WorkshopLead = "workshop_lead",
}

export interface Participant {
_id: Uid;
first_name: string;
last_name: string;
role: Role;
status: Status;
}

const fetcher = async (url: string) => {
const res = await axios.get<Participant[]>(url);
return res.data;
};

function useParticipants() {
const { data, error, isLoading } = useSWR<Participant[]>(
"/api/admin/participants",
fetcher,
);

// TODO: implement check-in mutation
return { participants: data ?? [], loading: isLoading, error };
}

export default useParticipants;
Loading