Skip to content
This repository has been archived by the owner on May 24, 2022. It is now read-only.

Commit

Permalink
Merge pull request #530 from SELab-2/stateshistory_list
Browse files Browse the repository at this point in the history
States history filters (fast typing)
  • Loading branch information
SeppeM8 authored May 21, 2022
2 parents b925733 + cc8247e commit 9689c45
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 133 deletions.
16 changes: 0 additions & 16 deletions frontend/src/components/AdminsComponents/AdminList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { User } from "../../utils/api/users/users";
import { AdminsTable } from "./styles";
import React from "react";
import { AdminListItem } from "./index";
import LoadSpinner from "../Common/LoadSpinner";
import { ListDiv } from "../Common/Users/styles";
import { SpinnerContainer } from "../Common/LoadSpinner/styles";
import { RemoveTh } from "../Common/Tables/styles";

/**
Expand All @@ -21,20 +19,6 @@ export default function AdminList(props: {
gotData: boolean;
removeAdmin: (user: User) => void;
}) {
if (props.loading) {
return (
<SpinnerContainer>
<LoadSpinner show={true} />
</SpinnerContainer>
);
} else if (props.admins.length === 0) {
if (props.gotData) {
return <div>No admins</div>;
} else {
return null;
}
}

return (
<ListDiv>
<AdminsTable>
Expand Down
29 changes: 19 additions & 10 deletions frontend/src/utils/api/mail_overview.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Email, Student } from "../../data/interfaces";
import { EmailType } from "../../data/enums";
import { axiosInstance } from "./api";
import axios from "axios";

/**
* A student together with its email history
Expand All @@ -26,17 +27,25 @@ export async function getMailOverview(
name: string,
filters: EmailType[],
controller: AbortController
): Promise<StudentEmails> {
const FormatFilters: string[] = filters.map(filter => {
return `&email_status=${Object.values(EmailType).indexOf(filter)}`;
});
const concatted: string = FormatFilters.join("");
): Promise<StudentEmails | null> {
try {
const FormatFilters: string[] = filters.map(filter => {
return `&email_status=${Object.values(EmailType).indexOf(filter)}`;
});
const concatted: string = FormatFilters.join("");

const response = await axiosInstance.get(
`/editions/${edition}/students/emails?page=${page}&name=${name}${concatted}`,
{ signal: controller.signal }
);
return response.data as StudentEmails;
const response = await axiosInstance.get(
`/editions/${edition}/students/emails?page=${page}&name=${name}${concatted}`,
{ signal: controller.signal }
);
return response.data as StudentEmails;
} catch (error) {
if (axios.isAxiosError(error) && error.code === "ERR_CANCELED") {
return null;
} else {
throw error;
}
}
}

/**
Expand Down
225 changes: 118 additions & 107 deletions frontend/src/views/MailOverviewPage/MailOverviewPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { getMailOverview, setStateRequest, StudentEmail } from "../../utils/api/mail_overview";
import Dropdown from "react-bootstrap/Dropdown";
import InfiniteScroll from "react-infinite-scroller";
Expand All @@ -12,6 +12,7 @@ import {
MessageDiv,
MailOverviewDiv,
SearchAndChangeDiv,
ClearDiv,
} from "./styles";
import { EmailType } from "../../data/enums";
import { useParams } from "react-router-dom";
Expand All @@ -32,9 +33,11 @@ interface EmailRow {
* Page that shows the email status of all students, with the possibility to change the status
*/
export default function MailOverviewPage() {
const { editionId } = useParams();

const [emailRows, setEmailRows] = useState<EmailRow[]>([]);
const [gotEmails, setGotEmails] = useState(false);
const [loading, setLoading] = useState(false);
const [requestedEdition, setRequestedEdition] = useState(editionId);
const [moreEmailsAvailable, setMoreEmailsAvailable] = useState(true); // Endpoint has more emailRows available
const [page, setPage] = useState(0);
const [allSelected, setAllSelected] = useState(false);
Expand All @@ -44,14 +47,16 @@ export default function MailOverviewPage() {
// Keep track of the set filters
const [searchTerm, setSearchTerm] = useState("");
const [filters, setFilters] = useState<EmailType[]>([]);

const { editionId } = useParams();
const [filtersChanged, setFiltersChanged] = useState(0);

/**
* update the table with new values
*/
async function updateMailOverview() {
if (loading) {
async function updateMailOverview(requested: number) {
const filterChanged = requested === -1;
const requestedPage = requested === -1 ? 0 : page;

if (loading && !filterChanged) {
return;
}

Expand All @@ -64,56 +69,56 @@ export default function MailOverviewPage() {
setController(newController);

const response = await toast.promise(
getMailOverview(editionId, page, searchTerm, filters, newController),
getMailOverview(editionId, requestedPage, searchTerm, filters, newController),
{ error: "Failed to retrieve states" }
);
if (response.studentEmails.length === 0) {
setMoreEmailsAvailable(false);
}
if (page === 0) {
setEmailRows(
response.studentEmails.map(email => {
return {
email: email,
checked: false,
};
})
);
} else {
setEmailRows(
emailRows.concat(

if (response !== null) {
if (response.studentEmails.length === 0 && !filterChanged) {
setMoreEmailsAvailable(false);
}
if (requestedPage === 0) {
setEmailRows(
response.studentEmails.map(email => {
return {
email: email,
checked: false,
};
})
)
);
);
} else {
setEmailRows(
emailRows.concat(
response.studentEmails.map(email => {
return {
email: email,
checked: false,
};
})
)
);
}
setPage(requestedPage + 1);
} else {
setMoreEmailsAvailable(false);
}
setPage(page + 1);

setGotEmails(true);
setLoading(false);
}

function refresh() {
setPage(0);
setGotEmails(false);
setMoreEmailsAvailable(true);
setEmailRows([]);
setAllSelected(false);
}

function searchName(newSearchTerm: string) {
setSearchTerm(newSearchTerm);
refresh();
}

function changeFilter(newFilter: EmailType[]) {
setFilters(newFilter);
refresh();
}
useEffect(() => {
if (editionId !== requestedEdition) {
setEmailRows([]);
setPage(0);
setMoreEmailsAvailable(true);
updateMailOverview(-1);
setRequestedEdition(editionId);
} else {
setPage(0);
setMoreEmailsAvailable(true);
updateMailOverview(-1);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchTerm, filtersChanged, editionId]);

/**
* Keeps the selectedRows list up-to-date when a student is selected/unselected in the table
Expand Down Expand Up @@ -156,74 +161,70 @@ export default function MailOverviewPage() {
})
);
setAllSelected(false);
refresh();
}

let table;
if (gotEmails && emailRows.length === 0) {
table = (
<CenterDiv>
<MessageDiv>No students found.</MessageDiv>
</CenterDiv>
);
if (emailRows.length === 0) {
if (loading) {
table = <LoadSpinner show={true} />;
} else {
table = (
<CenterDiv>
<MessageDiv>No students found.</MessageDiv>
</CenterDiv>
);
}
} else {
table = (
<TableDiv>
<InfiniteScroll
loadMore={updateMailOverview}
hasMore={moreEmailsAvailable}
loader={<LoadSpinner show={true} key="spinner" />}
initialLoad={true}
useWindow={false}
getScrollParent={() => document.getElementById("root")}
>
<StyledTable>
<thead>
<tr>
<th>
<InfiniteScroll
loadMore={updateMailOverview}
hasMore={moreEmailsAvailable}
loader={<LoadSpinner show={true} key="spinner" />}
initialLoad={true}
useWindow={false}
getScrollParent={() => document.getElementById("root")}
>
<StyledTable>
<thead>
<tr>
<th>
<Form.Check
type="checkbox"
onChange={e => selectAll(e.target.checked)}
checked={allSelected}
/>
</th>
<th>First Name</th>
<th>Last Name</th>
<th>Current State</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{emailRows.map(row => (
<tr key={row.email.student.studentId}>
<td>
<Form.Check
type="checkbox"
onChange={e => selectAll(e.target.checked)}
checked={allSelected}
onChange={e =>
selectNewStudent(row.email.student, e.target.checked)
}
checked={row.checked}
/>
</th>
<th>First Name</th>
<th>Last Name</th>
<th>Current State</th>
<th>Date</th>
</td>
<td>{row.email.student.firstName}</td>
<td>{row.email.student.lastName}</td>
<td>{Object.values(EmailType)[row.email.emails[0].decision]}</td>
<td>
{new Date(String(row.email.emails[0].date)).toLocaleString(
"nl-be"
)}
</td>
</tr>
</thead>
<tbody>
{emailRows.map(row => (
<tr key={row.email.student.studentId}>
<td>
<Form.Check
type="checkbox"
onChange={e =>
selectNewStudent(
row.email.student,
e.target.checked
)
}
checked={row.checked}
/>
</td>
<td>{row.email.student.firstName}</td>
<td>{row.email.student.lastName}</td>
<td>
{Object.values(EmailType)[row.email.emails[0].decision]}
</td>
<td>
{new Date(String(row.email.emails[0].date)).toLocaleString(
"nl-be"
)}
</td>
</tr>
))}
</tbody>
</StyledTable>
</InfiniteScroll>
</TableDiv>
))}
</tbody>
</StyledTable>
</InfiniteScroll>
);
}

Expand All @@ -232,7 +233,8 @@ export default function MailOverviewPage() {
<SearchDiv>
<SearchBar
onChange={e => {
searchName(e.target.value);
setPage(0);
setSearchTerm(e.target.value);
}}
value={searchTerm}
placeholder="Search a student"
Expand All @@ -245,8 +247,16 @@ export default function MailOverviewPage() {
placeholder=" Filter on State"
showArrow={true}
isObject={false}
onRemove={changeFilter}
onSelect={changeFilter}
onRemove={e => {
setPage(0);
setFilters(e);
setFiltersChanged(filtersChanged + 1);
}}
onSelect={e => {
setPage(0);
setFilters(e);
setFiltersChanged(filtersChanged + 1);
}}
options={Object.values(EmailType)}
/>
</FilterDiv>
Expand All @@ -267,7 +277,8 @@ export default function MailOverviewPage() {
</CommonDropdownButton>
</DropDownButtonDiv>
</SearchAndChangeDiv>
{table}
<ClearDiv />
<TableDiv>{table}</TableDiv>
</MailOverviewDiv>
);
}
4 changes: 4 additions & 0 deletions frontend/src/views/MailOverviewPage/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ export const MailOverviewDiv = styled.div`
width: fit-content;
margin: auto;
`;

export const ClearDiv = styled.div`
clear: both;
`;

0 comments on commit 9689c45

Please sign in to comment.