Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44,294 changes: 26,050 additions & 18,244 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"typescript": "^4.3.5",
"use-axios-client": "^2.0.0",
"victory-pie": "^36.5.3",
"web-vitals": "^0.2.2"
"web-vitals": "^0.2.2",
"xlsx": "^0.18.5"
},
"resolutions": {
"chakra-react-select": "^4.2.1"
Expand Down
Binary file added src/assets/hexlabs_bg.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 12 additions & 11 deletions src/components/admin/statistics/RenderStatistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Spinner,
Center,
} from "@chakra-ui/react";

import AccordionSection from "./AccordionSection";
import GraphAccordionSection from "./GraphAccordionSection";
import TreeMapView from "./graphs/TreeMapView";
Expand Down Expand Up @@ -45,7 +46,7 @@ export const RenderStatistics = ({
} = data;

return (
<Accordion allowToggle allowMultiple defaultIndex={[0]}>
<Accordion allowMultiple defaultIndex={[0]}>
<AccordionSection name="Overall Users" small="filterable by branch">
<Tbody>
<Tr>
Expand Down Expand Up @@ -112,18 +113,18 @@ export const RenderStatistics = ({
</AccordionSection>
<AccordionSection name="Application Type" small="not filterable">
<Thead>
<Th>Branch</Th>
<Th>Draft</Th>
<Th>Applied</Th>
<Th>Total</Th>
<Th>Accepted</Th>
<Th>Waitlisted</Th>
<Th>Denied</Th>
<Th>Decision Pending</Th>
<Th key="Branch">Branch</Th>
<Th key="Draft">Draft</Th>
<Th key="Applied">Applied</Th>
<Th key="Total">Total</Th>
<Th key="Accepted">Accepted</Th>
<Th key="Waitlisted">Waitlisted</Th>
<Th key="Denied">Denied</Th>
<Th key="Decision_Pending">Decision Pending</Th>
</Thead>
<Tbody>
{Object.entries(applicationStatistics).map(([key, branchData]: [string, any]) => (
<Tr>
<Tr key={`appStats-${key}`}>
<Td>{key}</Td>
<Td>{branchData.draft}</Td>
<Td>{branchData.applied}</Td>
Expand All @@ -145,7 +146,7 @@ export const RenderStatistics = ({
</Thead>
<Tbody>
{Object.entries(confirmationStatistics).map(([key, branchData]: [string, any]) => (
<Tr>
<Tr key={`confirmationStats-${key}`}>
<Td>{key}</Td>
<Td>{branchData.confirmed}</Td>
<Td>{branchData.notAttending}</Td>
Expand Down
57 changes: 48 additions & 9 deletions src/components/admin/statistics/StatisticsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
import React from "react";
import {
Accordion,
Alert,
AlertIcon,
Box,
HStack,
Button,
Heading,
Select,
Spinner,
Stack,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
VStack,
} from "@chakra-ui/react";
import { apiUrl, ErrorScreen, LoadingScreen, Service } from "@hex-labs/core";
import { useParams } from "react-router-dom";
import useAxios from "axios-hooks";
import { DownloadIcon } from "@chakra-ui/icons";

/*
import AccordionSection from "./AccordionSection";
import GraphAccordionSection from "./GraphAccordionSection";
import TreeMapView from "./graphs/TreeMapView";
*/
import { Branch, BranchType } from "../branchSettings/BranchSettingsPage";
import { RenderStatistics } from "./RenderStatistics";
import XLSXExporter from "../../../util/xlsxExport";
import { useCurrentHexathon } from "../../../contexts/CurrentHexathonContext";

const StatisticsPage: React.FC = () => {
const [selectedBranch, setSelectedBranch] = React.useState<Branch | null>(null);
const [selectedStatus, setSelectedStatus] = React.useState<string | null>("CONFIRMED");
const { hexathonId } = useParams();
const { currentHexathon } = useCurrentHexathon();

// Enable manual mode
const [{ data, loading, error }, refetchStatistics] = useAxios({
Expand All @@ -41,6 +40,39 @@ const StatisticsPage: React.FC = () => {
},
});

const exportToXLSX = React.useCallback(() => {

if (!hexathonId) { return; }

// whitespaces bad
const noWhitespaceName = (currentHexathon.name as string).replaceAll(" ", "_");

const exporter = new XLSXExporter({name: noWhitespaceName, id: hexathonId});
exporter.addKeyValueData(data.userStatistics, "Overall User Statistics");
exporter.addTableData(data.applicationStatistics, "Branch", "Application Statistics");
exporter.addTableData(data.confirmationStatistics, "Branch", "Confirmation Statistics");

// eslint-disable-next-line guard-for-in
for (const key in data.applicationDataStatistics) {
const branchData = data.applicationDataStatistics[key];
exporter.addKeyValueData(branchData, `${key}`);
}

const url = exporter.getDownloadURL();
const a = document.createElement("a");

let downloadName = noWhitespaceName;
if (selectedBranch) downloadName = downloadName.concat(`_filterBranch-${selectedBranch.name}`);
if (selectedStatus) downloadName = downloadName.concat(`_filterStatus-${selectedStatus}`);

a.download = downloadName.concat(".xlsx");
a.href = url;
a.click();

exporter.cleanupDownloadURL();

}, [currentHexathon.name, data, hexathonId, selectedBranch, selectedStatus]);

// Fetch statistics whenever selectedBranchId changes
React.useEffect(() => {
if (hexathonId) {
Expand Down Expand Up @@ -71,7 +103,10 @@ const StatisticsPage: React.FC = () => {
},
});

if (!hexathonId) return <ErrorScreen error={new Error("Hexathon ID invalid!")} />;
if (loading) return <LoadingScreen />;
if (error) return <ErrorScreen error={error} />;
if (branchError) return <ErrorScreen error={branchError} />

return (
<Box w="100%" p={5}>
Expand All @@ -81,8 +116,12 @@ const StatisticsPage: React.FC = () => {
<Text fontSize="lg" color="grey">
All of the data crunched into this page from all of the applications we recieved.
</Text>
<Box>
<Button colorScheme="blue" onClick={exportToXLSX}><DownloadIcon />&nbsp;&nbsp;Export to XLSX</Button>
<Text fontSize="sm" textAlign="center" opacity={0.5}>(Filters will be reflected)</Text>
</Box>

{branchLoading ? (
{branchLoading? (
<Spinner />
) : (
<HStack>
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/statistics/graphs/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const TableView: React.FC<IProps> = props => (
<Table variant="simple">
<Tbody>
{Object.keys(props.data).map(key => (
<Tr>
<Tr key={key}>
<Td style={{ width: "315px", maxWidth: "315px", fontSize: "13px" }}>{key}</Td>
<Td style={{ fontSize: "13px" }}>{props.data[key]}</Td>
</Tr>
Expand Down
39 changes: 39 additions & 0 deletions src/styles/selectEvent.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.registration-topbanner {
display: flex;
flex-direction: column;
row-gap: 2rem;

background-image: url("../assets/hexlabs_bg.jpg");
background-position: center;
background-size: cover;

padding: min(20vh, 8rem) 10%;
height: 100%;

color: white;
}

.registration-topbanner h1 {
font-family: DM Sans;
text-align: left;
font-size: 72px;
font-weight: 700;
margin: 0;
text-shadow: 2px 2px 0 rgba(29, 22, 46, 0.5);
}

.registration-topbanner p {
font-weight: 500;
font-family: DM Sans;
font-size: 1rem;
}

@media screen and (max-width: 768px) {
.registration-topbanner {
padding-left: 5%;
padding-right: 5%;
}
.registration-topbanner h1 {
font-size: 3rem !important;
}
}
124 changes: 124 additions & 0 deletions src/util/xlsxExport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import * as XLSX from 'xlsx';

class XLSXExporter {

private hexathonInfo: {name: string, id: string};
private workbook: XLSX.WorkBook;

public currURL: string | null = null;

constructor(hexathonInfo: {name: string, id: string}) {
this.workbook = XLSX.utils.book_new();
this.hexathonInfo = hexathonInfo;
this.workbook.Props = {
Title: `hexathon-${hexathonInfo.name}-statistics`,
Subject: `Application Data for ${hexathonInfo.name}`,
CreatedDate: new Date()
};
}

/**
* Creates a new sheet (one of those tabs at the bottom of the excel file) and adds key-value data.
*/
addKeyValueData(data: Record<string, any>, sheetLabel: string, keyLabel?: string, valueLabel?: string, ): void {

// convert to [{key, value}] format so that they go on rows and not columns
const rows = Object.entries(data).map(([key, value]) => ({
[keyLabel || 'Key']: key,
[valueLabel || 'Value']: value,
}));

const worksheet = XLSX.utils.json_to_sheet(rows); // Updated to use rows instead of [data]
XLSX.utils.book_append_sheet(this.workbook, worksheet, sheetLabel);
}

/**
* Creates a new sheet (one of those tabs at the bottom of the excel file) and adds a 2D table of data.
*
* `rowLabelName` is what describes the top-level keys of `data` - its basically what goes in the top left
* cell of the 2d table.
*
* ### how to use
* Pass in `data` formatted like this:
* ```json
* {
* "row1": {
* "columnHeader1": 930,
* "columnHeader2": 16,
* "columnHeader3": 1266,
* ...
* },
* "row2": {
* "columnHeader1": 23,
* "columnHeader2": 0,
* "columnHeader3": 29,
* ...
* },
* "row3": {
* "columnHeader1": 13,
* "columnHeader2": 0,
* "columnHeader3": 23,
* ...
* },
* ...
* ```
*
* ^ This will result in a table that looks like this:
* ```text
* | rowLabelName | columnHeader1 | columnHeader2 | columnHeader3 |
* | ------------ | ------------- | ------------- | ------------- |
* | row1 | 930 | 16 | 1266 |
* | row2 | 23 | 0 | 29 |
* | row3 | 13 | 0 | 23 |
* ```
*/
addTableData(data: Record<string, Record<string, any>>, rowLabelName: string, sheetLabel: string): void {

const headers = Object.keys(Object.values(data)[0]);

const aoa = [];

aoa.push([rowLabelName, ...headers]);

for (const [key, values] of Object.entries(data)) {
const row = [key];
for (const header of headers) {
row.push(values[header]);
}
aoa.push(row);
}

const worksheet = XLSX.utils.aoa_to_sheet(aoa);
XLSX.utils.book_append_sheet(this.workbook, worksheet, sheetLabel);
}

getDownloadURL(): string {
if (this.currURL) {
return this.currURL;
}
this.exportToDownloadURL();

// SCREW YOU ESLINT
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.currURL!;
}

cleanupDownloadURL(): void {
if (this.currURL) {
URL.revokeObjectURL(this.currURL);
this.currURL = null;
}
}

private exportToDownloadURL(): void {
const out = XLSX.write(this.workbook, {
bookType: 'xlsx',
type: 'array',
});

const blob = new Blob([out], { type: 'application/octet-stream' });
this.currURL = URL.createObjectURL(blob);
}
}

export default XLSXExporter;
Loading