Skip to content

Commit

Permalink
Merge pull request #126 from whyphi/refactor/simplify-applicant-itera…
Browse files Browse the repository at this point in the history
…tion

Refactor/simplify applicant iteration
  • Loading branch information
wderocco8 authored May 20, 2024
2 parents e2263dd + 2c237d2 commit 570b738
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 84 deletions.
22 changes: 0 additions & 22 deletions app/admin/listing/[listingId]/[applicantId]/layout.tsx

This file was deleted.

150 changes: 125 additions & 25 deletions app/admin/listing/[listingId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import React, { useState, useEffect, useRef } from "react";
import Loader from "@/components/Loader";
import ApplicantCard from "@/components/admin/listing/ApplicantCard";
import { Applicant } from "@/types/applicant";
import { useRouter } from "next/navigation";
import { Tabs, TabsRef, Table } from 'flowbite-react';
import { HiOutlineCollection, HiOutlineTable } from 'react-icons/hi';
import { selectedApplicantIdKey } from "@/utils/globals";
import { Tabs, TabsRef, Table, Button, Pagination } from 'flowbite-react';
import { HiArrowLeft, HiOutlineCollection, HiOutlineTable } from 'react-icons/hi';
import { useAuth } from "@/app/contexts/AuthContext";


import ApplicantPage from "../../../../components/admin/listing/ApplicantPage";

export default function Listing({ params }: { params: { listingId: string } }) {
const router = useRouter();
const { token } = useAuth();
const [applicantData, setApplicantData] = useState<[] | [Applicant]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [selectedApplicant, setSelectedApplicant] = useState<Applicant | null>(null);
const [selectedApplicantIndex, setSelectedApplicantIndex] = useState<number>(-1);

const tabsRef = useRef<TabsRef>(null);
const applicantRefs = useRef<(HTMLDivElement | null)[]>([]);

// Need to investigate why tabs are changing state using refs
// const [activeTab, setActiveTab] = useState(0);
Expand All @@ -33,20 +34,45 @@ export default function Listing({ params }: { params: { listingId: string } }) {
})
.then((response) => response.json())
.then((data: [Applicant]) => {
// update applicantData and loading status
setApplicantData(data)
setIsLoading(false);

// after fetching listings -> use localStorage to check if selectedApplicant is defined
const selectedApplicantId =localStorage.getItem(selectedApplicantIdKey);

// check that applicant exists in localStorage
if (selectedApplicantId) {
// Find the index of the applicant in the applicantData array
const applicantIndex = data.findIndex(applicant => selectedApplicantId == applicant.applicantId);

// Check if an applicant was found
if (applicantIndex !== -1) {
const applicant = data[applicantIndex];

// Check that applicant is still valid
if (applicant) {
setSelectedApplicant(applicant);
setSelectedApplicantIndex(applicantIndex);
}
}
}
})
.catch((error) => console.error("Error fetching listings:", error));

}, [params.listingId, token]);

const renderApplicantCardView = () => {
return (
<div className="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
{applicantData.map((applicant, index) => (
<div key={index} className="col-span-1">
<div key={index} className="col-span-1" ref={(ref) => (applicantRefs.current[index] = ref)}>
<ApplicantCard
listingId={params.listingId}
applicant={applicant}
index={index}
setSelectedApplicant={setSelectedApplicant}
setSelectedApplicantIndex={setSelectedApplicantIndex}
/>
</div>
))}
Expand All @@ -72,13 +98,18 @@ export default function Listing({ params }: { params: { listingId: string } }) {
key={index}
className="bg-white dark:border-gray-700 dark:bg-gray-800 cursor-pointer"
onClick={(event) => {
if (event.metaKey) {
// Cmd + click (on macOS) or Ctrl + click (on Windows/Linux)
window.open(`/admin/listing/${applicant.listingId}/${applicant.applicantId}`, '_blank');
} else {
// Regular click
router.push(`/admin/listing/${applicant.listingId}/${applicant.applicantId}`);
}
// scroll to top of page
window.scrollTo({
top: 0,
// behavior: 'smooth', // change 'smooth' to 'auto' if you don't want a smooth scroll
});

// update state variables
setSelectedApplicant(applicant);
setSelectedApplicantIndex(index);

// push to localStorage
localStorage.setItem(selectedApplicantIdKey, applicant.applicantId);
}}
>
<Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
Expand All @@ -94,29 +125,98 @@ export default function Listing({ params }: { params: { listingId: string } }) {
</Table.Cell>
<Table.Cell>{applicant.major}</Table.Cell>
</Table.Row>

))}
</Table.Body>
</Table>
</div>
)
}

const viewAllApplicants = () => {
// grab current selectedApplicantIndex
const index = selectedApplicantIndex;

// update state variables
setSelectedApplicant(null);
setSelectedApplicantIndex(-1);

// clear selectedApplicantId from localStorage
localStorage.removeItem(selectedApplicantIdKey);

// Scroll to that index after state update (ensures scroll occurs in NEXT event-loop-cycle)
setTimeout(() => {
scrollApplicantIntoView(index);
}, 0);
}

const onPageChange = (page: number) => {
const index = page - 1;
if (index < 0) {
console.error("uh oh, page index out of range", index, applicantData.length)
}

const applicant = applicantData[index]

// update state variables
setSelectedApplicant(applicant);
setSelectedApplicantIndex(index);

// push to localStorage
localStorage.setItem(selectedApplicantIdKey, applicant.applicantId);
};

const scrollApplicantIntoView = (index: number) => {
const applicantRef = applicantRefs.current[index];
if (applicantRef) {
applicantRef.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
};

if (isLoading) return (<Loader />)

return (
<div>
<h1 className="text-2xl font-bold mb-4">Applicants</h1>
<Tabs aria-label="Default tabs" style="default" ref={tabsRef}>
<Tabs.Item active title="Card" icon={HiOutlineCollection}>
{renderApplicantCardView()}
</Tabs.Item>
<Tabs.Item title="Table" icon={HiOutlineTable}>
{renderApplicantTableView()}
</Tabs.Item>
</Tabs>
{/* either render Tabs (all applicants) OR single applicant view */}
{selectedApplicant == null
?
<Tabs aria-label="Default tabs" style="default" ref={tabsRef}>
<Tabs.Item active title="Card" icon={HiOutlineCollection}>
{renderApplicantCardView()}
</Tabs.Item>
<Tabs.Item title="Table" icon={HiOutlineTable}>
{renderApplicantTableView()}
</Tabs.Item>
</Tabs>
:
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<div className="flex justify-between">
<Button color="gray" onClick={viewAllApplicants}>
<HiArrowLeft className="mr-2"/>
View all
</Button>
<Pagination
layout="navigation"
currentPage={selectedApplicantIndex + 1}
totalPages={applicantData.length}
onPageChange={onPageChange}
previousLabel={"Previous"}
nextLabel={"Next"}
showIcons
/>
</div>
<div className="flex justify-end text-gray-500 text-sm">
<div className="">({selectedApplicantIndex + 1} of {applicantData.length} applicants)</div>
</div>
</div>
<ApplicantPage applicant={selectedApplicant}/>
</div>
}

</div>


)
}
2 changes: 0 additions & 2 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import ListingCard from "@/components/admin/ListingCard";
import Loader from "@/components/Loader";
import { useAuth } from "../contexts/AuthContext";



interface Listing {
listingId: string;
title: string;
Expand Down
3 changes: 2 additions & 1 deletion components/admin/ListingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useRouter } from "next/navigation";
import { useState } from "react";
import { ToggleSwitch, Dropdown } from 'flowbite-react';
import Link from "next/link";
import { selectedApplicantIdKey } from "@/utils/globals";

interface ListingCardProps {
listingId: string;
Expand Down Expand Up @@ -159,7 +160,7 @@ export default function ListingCard({ listingId, title, active, deadline, dateCr
</div>
</div>
</div>
<Link href={`admin/listing/${listingId}`} className="py-6 px-6 flex-grow">
<Link onClick={() => localStorage.removeItem(selectedApplicantIdKey)} href={`admin/listing/${listingId}`} className="py-6 px-6 flex-grow">
<div>
<h5 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">{title}</h5>
<div className="mt-1">
Expand Down
23 changes: 21 additions & 2 deletions components/admin/listing/ApplicantCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import { useRouter } from 'next/navigation';
interface ApplicantCardProps {
listingId: string;
applicant: Applicant;
index: number;
setSelectedApplicant: (applicant: Applicant) => void;
setSelectedApplicantIndex: (index: number) => void;
}

export default function ApplicantCard({ applicant }: ApplicantCardProps) {
export default function ApplicantCard({ listingId, applicant, index, setSelectedApplicant, setSelectedApplicantIndex }: ApplicantCardProps) {
const router = useRouter();
const { colleges } = applicant;

Expand All @@ -22,7 +25,23 @@ export default function ApplicantCard({ applicant }: ApplicantCardProps) {
const gradYear = applicant.gradYear.split(' ').pop();

return (
<Card className="cursor-pointer" onClick={() => router.push(`/admin/listing/${applicant.listingId}/${applicant.applicantId}`)}>
<Card
className="cursor-pointer hover:bg-purple-50"
onClick={() => {
// scroll to top of page
window.scrollTo({
top: 0,
// behavior: 'smooth', // change 'smooth' to 'auto' if you don't want a smooth scroll
});

// update state variables
setSelectedApplicant(applicant);
setSelectedApplicantIndex(index);

// push to localStorage
localStorage.setItem("selectedApplicantId", applicant.applicantId);
}}
>
<div className="flex flex-col items-center mb-1">
<Image
width={96}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,24 @@
"use client";
import { useState, useEffect } from "react";
import { Applicant, EventsAttended } from "@/types/applicant";
import { Badge, Tabs, Table } from 'flowbite-react';
import { HiMenuAlt1, HiDocumentText, HiUserGroup } from 'react-icons/hi';
import ResponseCard from "@/components/admin/listing/ResponseCard";
import ApplicantInfoCard from "@/components/admin/listing/ApplicantInfoCard";
import ApplicantPDFViewer from "@/components/admin/listing/ApplicantPDFViewer";
import Loader from "@/components/Loader";
import { useAuth } from "@/app/contexts/AuthContext";

export default function ApplicantPage({ params }: { params: { applicantId: string } }) {
const { token } = useAuth();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [applicantData, setApplicantData] = useState<null | Applicant>(null);

useEffect(() => {
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/applicant/${params.applicantId}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data: Applicant) => {
setApplicantData(data);
setIsLoading(false)
})
.catch((error) => console.error("Error fetching listings:", error));

}, [params.applicantId, token]);
interface ApplicantPageProps {
applicant: Applicant;
}

export default function ApplicantPage({ applicant }: ApplicantPageProps) {

const renderResponses = () => {
return (
applicantData?.responses.length === 0 ? (
applicant?.responses.length === 0 ? (
<p>None</p>
) : (
<div className="">
{applicantData?.responses.map((response, index) => (
{applicant?.responses.map((response, index) => (
<ResponseCard
key={index}
question={response.question}
Expand Down Expand Up @@ -82,14 +62,14 @@ export default function ApplicantPage({ params }: { params: { applicantId: strin
);
}

if (isLoading) return <Loader />
// if (isLoading) return <Loader />


return (
<div className="flex flex-wrap">
{/* Left component (ApplicantInfoCard) */}
<div className="w-full lg:pr-6 lg:w-1/3 overflow-auto lg:sticky top-0 lg:h-screen">
{applicantData && <ApplicantInfoCard applicant={applicantData} />}
{applicant && <ApplicantInfoCard applicant={applicant} />}
</div>

{/* Right component (Tabs and content) */}
Expand All @@ -110,17 +90,17 @@ export default function ApplicantPage({ params }: { params: { applicantId: strin
icon={HiDocumentText}
title="Resume"
>
{applicantData && applicantData.resume ? (
<ApplicantPDFViewer resumeLink={applicantData.resume} />
{applicant && applicant.resume ? (
<ApplicantPDFViewer resumeLink={applicant.resume} />
) : (
<p>No resume available.</p>
)}
</Tabs.Item>
{applicantData?.events ? (<Tabs.Item
{applicant?.events ? (<Tabs.Item
icon={HiUserGroup}
title="Events Attended"
>
{renderEventsAttended(applicantData.events)}
{renderEventsAttended(applicant.events)}
</Tabs.Item>) : ("")}

</Tabs>
Expand Down
4 changes: 4 additions & 0 deletions utils/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* NOTE: this file contains global variables that are used in various places within the application */

// localStorage variables
export const selectedApplicantIdKey = "selectedApplicantId";

0 comments on commit 570b738

Please sign in to comment.