diff --git a/package-lock.json b/package-lock.json index fd4b6ce..bcd37a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,10 +36,14 @@ "react-router-dom": "^6.16.0", "react-scripts": "^5.0.1", "reactstrap": "^9.2.0", + "react-toastify": "^9.1.3", + "reveal.js": "^4.6.1", "typescript": "^4" }, "devDependencies": { - "sass": "^1.58.1" + "@types/reveal.js": "^4.4.3", + "sass": "^1.58.1", + "typescript-eslint-language-service": "^5.0.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2474,6 +2478,22 @@ "stylis": "4.2.0" } }, +<<<<<<< HEAD +======= + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, +>>>>>>> c4979aa (Added Submit Attendance pages) "node_modules/@emotion/cache": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", @@ -2730,6 +2750,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz", "integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==", "hasInstallScript": true, +<<<<<<< HEAD "dependencies": { "@fortawesome/fontawesome-common-types": "6.4.2" }, @@ -2754,6 +2775,9 @@ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz", "integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==", "hasInstallScript": true, +======= + "peer": true, +>>>>>>> c4979aa (Added Submit Attendance pages) "dependencies": { "@fortawesome/fontawesome-common-types": "6.4.2" }, @@ -3664,6 +3688,17 @@ } } }, +<<<<<<< HEAD +======= + "node_modules/@mui/base/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, +>>>>>>> c4979aa (Added Submit Attendance pages) "node_modules/@mui/core-downloads-tracker": { "version": "5.14.13", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.13.tgz", @@ -3738,6 +3773,17 @@ } } }, +<<<<<<< HEAD +======= + "node_modules/@mui/joy/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, +>>>>>>> c4979aa (Added Submit Attendance pages) "node_modules/@mui/material": { "version": "5.14.13", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.13.tgz", @@ -3782,6 +3828,22 @@ } } }, +<<<<<<< HEAD +======= + "node_modules/@mui/material/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, +>>>>>>> c4979aa (Added Submit Attendance pages) "node_modules/@mui/private-theming": { "version": "5.14.13", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.13.tgz", @@ -3878,6 +3940,17 @@ } } }, +<<<<<<< HEAD +======= + "node_modules/@mui/system/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, +>>>>>>> c4979aa (Added Submit Attendance pages) "node_modules/@mui/types": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.6.tgz", @@ -3918,6 +3991,14 @@ } } }, +<<<<<<< HEAD +======= + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, +>>>>>>> c4979aa (Added Submit Attendance pages) "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -4746,6 +4827,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.7.tgz", + "integrity": "sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -4759,6 +4848,12 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, + "node_modules/@types/reveal.js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@types/reveal.js/-/reveal.js-4.4.3.tgz", + "integrity": "sha512-nI4Kp7x5uNn5op2IHNM0L1dLONejRuph2Rcn6KVk3jF1NOLpxW8xZciXX0V0sBkVAlWTn6JmW88EaeFkkWDYig==", + "dev": true + }, "node_modules/@types/scheduler": { "version": "0.16.4", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", @@ -6387,9 +6482,15 @@ } }, "node_modules/clsx": { +<<<<<<< HEAD "version": "2.0.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", +======= + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", +>>>>>>> 6a96823 (Added API fetching utility functions) "engines": { "node": ">=6" } @@ -14967,6 +15068,18 @@ } } }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -15323,6 +15436,14 @@ "node": ">=0.10.0" } }, + "node_modules/reveal.js": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.6.1.tgz", + "integrity": "sha512-1CW0auaXNPmwmvQ7TwpszwVxMi2Xr5cTS3J3EBC/HHgbPF32Dn7aiu/LKWDOGjMbaDwKQiGmfqcoGQ74HUHCMw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -16961,6 +17082,17 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-eslint-language-service": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/typescript-eslint-language-service/-/typescript-eslint-language-service-5.0.5.tgz", + "integrity": "sha512-b7gWXpwSTqMVKpPX3WttNZEyVAMKs/2jsHKF79H+qaD6mjzCyU5jboJe/lOZgLJD+QRsXCr0GjIVxvl5kI1NMw==", + "dev": true, + "peerDependencies": { + "@typescript-eslint/parser": ">= 5.0.0", + "eslint": ">= 8.0.0", + "typescript": ">= 4.0.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 4dba4ab..fce7ff4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "react-router": "^6.8.1", "react-router-dom": "^6.16.0", "react-scripts": "^5.0.1", + "react-toastify": "^9.1.3", "reactstrap": "^9.2.0", + "reveal.js": "^4.6.1", "typescript": "^4" }, "scripts": { @@ -58,6 +60,15 @@ ] }, "devDependencies": { - "sass": "^1.58.1" + "@types/reveal.js": "^4.4.3", + "sass": "^1.58.1", + "typescript-eslint-language-service": "^5.0.5" + }, + "compilerOptions": { + "plugins": [ + { + "name": "typescript-eslint-language-service" + } + ] } } diff --git a/src/API/API.ts b/src/API/API.ts new file mode 100644 index 0000000..b353dd8 --- /dev/null +++ b/src/API/API.ts @@ -0,0 +1,134 @@ +import { toast } from "react-toastify"; +import { ErrorInfo } from "./Types"; + +export const baseURL: string = `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}`; + + +// Can't name a method `get`, so ... +export const getJSON = (url: string, params?: any): Promise => { + if (!url.startsWith("/")) { + url = "/" + url; + } + let qm = url.includes("?"); + for (const key in params || {}) { + if (!qm) { + url += "?"; + qm = true; + } else { + url += "&"; + } + url += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + } + return fetch(baseURL + url) + .then(body => { + if (body.status !== 200) { + throw body; + } + return body.json() + }) + .then(e => e as T); +} + +export const post = (url: string, body?: any, params?: any): Promise => { + if (!url.startsWith("/")) { + url = "/" + url; + } + let qm = url.includes("?"); + for (const key in params || {}) { + if (!qm) { + url += "?"; + qm = true; + } else { + url += "&"; + } + url += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + } + let describe: RequestInit = { + method: "POST", + } + if (body) { + describe = { + ...describe, + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json" + } + } + } + return fetch(baseURL + url, describe) + .then(response => { + if (response.status !== 200) { + throw response; + } + return response; + }); +} + +export const patch = (url: string, body?: any, params?: any): Promise => { + if (!url.startsWith("/")) { + url = "/" + url; + } + let qm = url.includes("?"); + for (const key in params || {}) { + if (!qm) { + url += "?"; + qm = true; + } else { + url += "&"; + } + url += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + } + let describe: RequestInit = { + method: "PATCH", + } + if (body) { + describe = { + ...describe, + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json" + } + } + } + return fetch(baseURL + url, describe) + .then(response => { + if (response.status !== 200) { + throw response; + } + return response; + }); +} + +//can't name function `delete` 😞 +export const apiDelete = (url: string, params?: any): Promise => { + if (!url.startsWith("/")) { + url = "/" + url; + } + let qm = url.includes("?"); + for (const key in params || {}) { + if (!qm) { + url += "?"; + qm = true; + } else { + url += "&"; + } + url += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + } + return fetch(baseURL + url, { + method: "DELETE" + }) + .then(response => { + if (response.status !== 200) { + throw response; + } + return response; + }); +} + +export const toastError = (message: string) => (resp: any) => { + resp.json() + .then((error: ErrorInfo) => + toast.error(`${message}: ${error.message}`, { + theme: "colored" + })) +} diff --git a/src/API/Types.ts b/src/API/Types.ts new file mode 100644 index 0000000..613751a --- /dev/null +++ b/src/API/Types.ts @@ -0,0 +1,51 @@ +export interface ErrorInfo { + message: string, + stackTrace: string +} + +export const DirectorshipTypes = ["Chairman", "Ad-Hoc", "Evaluations", "Financial", "Research and Development", "House Improvements", "OpComm", "History", "Social", "Public Relations"] as const; +export type DirectorshipType = typeof DirectorshipTypes[number] + +export interface UserInfo { + username: string, + fullName: string, +} + +export interface DirectorshipAttendanceRecord { + approved: boolean, + committe: DirectorshipType, + frosh: number[], + members: string[], + timestamp: Date +} + +export interface SeminarAttendanceRecord { + approved: boolean, + name: string, + frosh: number[], + members: string[], + timestamp: Date, +} + +export interface IntroEvalsSummary { + name: string, + uid: string | null, + seminars: number, + directorships: number, + missed_hms: number, + signatures: number, + max_signatures: number, +} + +export interface IntroEvalsForm { + uid: string, + social_events: string | null, + comments: string | null, +} + +export interface Batch { + name: string, + members: string[], + creator: string, + conditions: string[] +} diff --git a/src/App.tsx b/src/App.tsx index 71f0f55..cf2b647 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,35 @@ import React from 'react' import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' import Home from './pages/Home' +import AttendanceHistory from './pages/Attendance/AttendanceHistory' +import SubmitDirectorship from './pages/SubmitDirectorship' import PageContainer from './containers/PageContainer' import 'csh-material-bootstrap/dist/csh-material-bootstrap.css' import NotFound from './pages/NotFound' -import { request } from 'http' +import SubmitSeminar from './pages/SubmitSeminar' +import IntroEvalsSlideshow from './pages/Slideshow/IntroEvalsSlideshow' + type Props = { - rerouteHomeOn404?: boolean + rerouteHomeOn404?: boolean } const App: React.FC = ({ rerouteHomeOn404 = null }) => { - return ( - - - - } /> - } /> - : } /> - - - - ) + + return ( + + + + } /> + } /> + } /> + } /> + } /> + } /> + : } /> + + + + ) } export default App diff --git a/src/components/UserSearch.tsx b/src/components/UserSearch.tsx new file mode 100644 index 0000000..dfafb77 --- /dev/null +++ b/src/components/UserSearch.tsx @@ -0,0 +1,83 @@ +import { faX } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { Badge, Button, Container, Input } from "reactstrap"; +import { getJSON, toastError } from "../API/API"; +import { UserInfo } from "../API/Types"; + +const UserSearch: React.FC<{ + users: [UserInfo[], Dispatch>] +}> = ({ users: [users, setUsers] }) => { + + const [inputText, setInputText] = useState(""); + + const [results, setResults] = useState([]); + + useEffect(() => { + if (!inputText || inputText.trim().length < 2) { + setResults([]); + return; + } + getJSON("/api/users/search", { + query: inputText + }) + .then(e => e.slice(0, 10)) + .then(setResults) + .catch(toastError("Unable to search Users")); + }, [inputText]); + + const addUser = (user: UserInfo) => { + if (users.filter(u => u.username === user.username).length === 0) { + setUsers(old => [...old, user]); + } + setInputText(""); + } + + const removeUser = (user: UserInfo) => { + setUsers(old => old.filter(u => u.username !== user.username)); + } + + return ( + + { + users.map((user, index) => +

+ + {user.fullName} ({user.username}) + + +

+ ) + } + setInputText(e.target.value)} + /> + +
+ ) +} + +export default UserSearch; diff --git a/src/containers/PageContainer.tsx b/src/containers/PageContainer.tsx index e00dedf..2ba84d1 100644 --- a/src/containers/PageContainer.tsx +++ b/src/containers/PageContainer.tsx @@ -7,14 +7,15 @@ type Props = { } export const PageContainer: React.FC = ({ children }) => { + return (
- - + + {!window.location.href.includes("slideshow") && } {children}
) } -export default PageContainer \ No newline at end of file +export default PageContainer diff --git a/src/pages/Slideshow/BatchSlide.tsx b/src/pages/Slideshow/BatchSlide.tsx new file mode 100644 index 0000000..fafa111 --- /dev/null +++ b/src/pages/Slideshow/BatchSlide.tsx @@ -0,0 +1,81 @@ +import { useState } from "react"; +import { Button, Col, Container, Row } from "reactstrap" +import { Batch } from "../../API/Types"; + +const BatchSlide = (props: { + batch: Batch, + onPassFail: (passed: boolean) => void +}) => { + + const [passed, setPassed] = useState(null); + + return ( +
+
+ + + +

{props.batch.name}

+ +
+ + { + props.batch.members.map((f, i) => + +

{f}

+ + ) + } +
+ { + passed == null ? + + + + + + : +

{passed}

+ } +
+
+
+ + + +

Creator: {props.batch.creator}

+ +
+ + + {props.batch.conditions.map(c =>

{c}

)} + +
+
+
+
+ ) +} + +export default BatchSlide diff --git a/src/pages/Slideshow/IntroEvalsSlideshow.tsx b/src/pages/Slideshow/IntroEvalsSlideshow.tsx new file mode 100644 index 0000000..4b1673e --- /dev/null +++ b/src/pages/Slideshow/IntroEvalsSlideshow.tsx @@ -0,0 +1,101 @@ +import { useEffect, useReducer, useState } from "react" +import Reveal from "reveal.js"; +import Slide from "./Slide" +import 'reveal.js/dist/reset.css' +import 'reveal.js/dist/reveal.css' +import 'reveal.js/dist/theme/white.css' +import './index.css' +import { Batch, IntroEvalsSummary } from "../../API/Types"; +import { getJSON, toastError } from "../../API/API"; +import { Col, Container, Row } from "reactstrap"; +import BatchSlide from "./BatchSlide"; +import NumberBox from "./NumberBox"; + +const IntroEvalsSlideshow = () => { + + const initSlides = () => { + let slides = new Reveal({ + backgroundTransition: 'slide', + transition: 'slide' + }); + slides.initialize(); + } + + const [removedMembers, setRemovedMembers] = useState([]); + + const [frosh, setFrosh] = useState([]); + + useEffect(() => { + getJSON("/api/evals/intro") + .then(setFrosh) + .then(initSlides) + .catch(toastError("Unable to fetch Intro Evals data")); + }, []) + + const [batches, setBatches] = useState([]); + + useEffect(() => { + getJSON("/api/batch") + .then(e => setBatches(e.map(b => ({ + ...b, + members: b.members.map(m => m.split(",")[0]), + })))) + .catch(toastError("Unable to fetch Batches")) + }, []); + + const passFailBatch = (batch: Batch) => (pass: boolean) => { + setRemovedMembers([...removedMembers, ...batch.members]) + } + + return ( +
+
+ {/* placeholder, because slideshow doesn't work unless at least one slide is present from the beginning*/} +
+
+ +

Intro Evals Slideshow

+ + + + + + + + +
+
+
+ +

Hi :)

+
+
+
+ { + batches.map((b, i) => + + ) + } + + { + frosh.filter(f => !removedMembers.includes(f.name)).sort((a, b) => a.name.localeCompare(b.name)).map((f, i) => + ) + } +
+
+ + ) +} + +export default IntroEvalsSlideshow diff --git a/src/pages/Slideshow/NumberBox.tsx b/src/pages/Slideshow/NumberBox.tsx new file mode 100644 index 0000000..1348b33 --- /dev/null +++ b/src/pages/Slideshow/NumberBox.tsx @@ -0,0 +1,19 @@ +import { Card, CardText, Container, Row } from "reactstrap" + +const NumberBox = (props: { text: string, subtext: string, success: boolean }) => { + + return ( + + + +

{props.text}

+
+ +

{props.subtext}

+
+
+
+ ) +} + +export default NumberBox diff --git a/src/pages/Slideshow/Slide.tsx b/src/pages/Slideshow/Slide.tsx new file mode 100644 index 0000000..ecbfa0f --- /dev/null +++ b/src/pages/Slideshow/Slide.tsx @@ -0,0 +1,83 @@ +import { useEffect, useState } from "react" +import { Button, Card, CardText, Col, Container, Row } from "reactstrap" +import { getJSON, toastError } from "../../API/API"; +import { IntroEvalsForm } from "../../API/Types"; +import NumberBox from "./NumberBox" + +const Slide = (props: { + name: string, + uid: string | null, + packet: number, // number from 0 - 100 + hm_absences: number, + directorships: number, + seminars: number, +}) => { + + const [comments, setComments] = useState(""); + const [socials, setSocials] = useState(""); + + useEffect(() => { + if (props.uid !== null) { + getJSON(`/api/forms/intro/${props.uid}`) + .then(e => { + setComments(comments + (e[0].comments || "")); + setSocials(socials + (e[0].social_events || "")); + }) + .catch(toastError(`Unable to fetch Data for ${props.uid}`)) + } + }, []); + + return ( +
+
+ + + +

{props.name}

+ +
+ + + = 60} /> + + + + + + = 6} /> + + + = 2} /> + + + + + + +
+
+
+ + + +

Comments

+ + +

Social Events

+ +
+ + +

{comments}

+ + +

{socials}

+ +
+
+
+
+ ) +} + +export default Slide diff --git a/src/pages/Slideshow/index.css b/src/pages/Slideshow/index.css new file mode 100644 index 0000000..76a5510 --- /dev/null +++ b/src/pages/Slideshow/index.css @@ -0,0 +1,47 @@ +/* for slide transitions */ +[hidden] { + display: inherit !important; +} + +.number-box{ + border-width: 10px !important; + border-radius: 20px !important; +} + +.pf-button { + border-width: 6px !important; + border-radius: 35px !important; +} + +.number-box-text { + font-size: 6em; + text-align: center; + white-space: nowrap; +} + +.number-box-subtext { + justify-content: flex-end; + vertical-align: bottom; + height: auto; + font-size: 16pt; +} + +div.main { + margin-left: 0px; + margin-right: 0px; +} + +.batch-name { + font-size: 12pt; + white-space: nowrap; +} + +.disband-gray { + border-color: lightgray !important; + color: gray !important; +} + +.soc-com { + font-size: 10pt; + white-space: pre-line; +} diff --git a/src/pages/SubmitDirectorship.tsx b/src/pages/SubmitDirectorship.tsx new file mode 100644 index 0000000..facf220 --- /dev/null +++ b/src/pages/SubmitDirectorship.tsx @@ -0,0 +1,90 @@ +import { post, toastError } from '../API/API'; +import { UserInfo } from "../API/Types"; +import { Button, Card, CardBody, CardHeader, Container, Form, FormGroup, Input } from "reactstrap"; +import { useState } from 'react'; +import { DirectorshipType, DirectorshipTypes } from '../API/Types'; +import UserSearch from "../components/UserSearch"; +import { toast } from 'react-toastify'; + +const SubmitDirectorship = () => { + + const [meetingType, setMeetingType] = useState(undefined); + + const [meetingDate, setMeetingDate] = useState(new Date().toISOString().split("T")[0]); + + const [attendees, setAttendees] = useState([]); + + const canSubmit = () => + meetingType !== undefined + && attendees.length > 0 + && (d => d instanceof Date && !isNaN(d.getTime()))(new Date(meetingDate)) + + const submit = () => { + post("/attendance/directorship", { + date: meetingDate, + members: attendees.map(a => a.username), + type: meetingType as string, + frosh: [] //TODO: implement frosh + }) + .then(() => toast.success("Submitted successfully!", { theme: "colored" })) + .then(() => setTimeout(() => window.location.assign("/"), 3000)) + .catch(toastError("Unable to submit Attendance")) + } + + return ( + + +

Submit Directorship Attendance

+
+
+ + + Meeting Type + + setMeetingType(e.target.value as DirectorshipType)}> + + { + DirectorshipTypes.map((dt, index) => + + ) + } + + + + + + + Date + + setMeetingDate(e.target.value)} + /> + + + + + + Attendees + + + + + + + + +
+
+ ) +} + +export default SubmitDirectorship diff --git a/src/pages/SubmitSeminar.tsx b/src/pages/SubmitSeminar.tsx new file mode 100644 index 0000000..db3ac9c --- /dev/null +++ b/src/pages/SubmitSeminar.tsx @@ -0,0 +1,88 @@ +import { post, toastError } from '../API/API'; +import { UserInfo } from "../API/Types"; +import { Button, Card, CardBody, CardHeader, Container, Form, FormGroup, Input } from "reactstrap"; +import { useState } from 'react'; +import UserSearch from "../components/UserSearch"; +import { toast } from 'react-toastify'; + +const SubmitSeminar = () => { + + const [meetingName, setMeetingName] = useState(""); + + const [meetingDate, setMeetingDate] = useState(new Date().toISOString().split("T")[0]); + + const [attendees, setAttendees] = useState([ + { + username: "ethanf108", + fullName: "Ethan Ferguson", + } + ]); + + const canSubmit = () => + meetingName.length > 1 + && attendees.length > 0 + && (d => d instanceof Date && !isNaN(d.getTime()))(new Date(meetingDate)) + + const submit = () => { + post("/api/attendance/seminar", { + date: meetingDate, + members: attendees.map(a => a.username), + name: meetingName, + frosh: [], //TODO: implement frosh + approved: false + }) + .then(() => toast.success("Submitted successfully!", { theme: "colored" })) + .then(() => setTimeout(() => window.location.assign("/"), 3000)) + .catch(toastError("Unable to submit Attendance")) + } + + return ( + + +

Submit Technical Seminar Attendance

+
+
+ + + Seminar Name + + setMeetingName(e.target.value)} /> + + + + + + Date + + setMeetingDate(e.target.value)} + /> + + + + + + Attendees + + + + + + + + +
+
+ ) +} + +export default SubmitSeminar diff --git a/tsfmt.json b/tsfmt.json new file mode 100644 index 0000000..1f66fe0 --- /dev/null +++ b/tsfmt.json @@ -0,0 +1,8 @@ +{ + "indentSize": 4, + "tabSize": 4, + "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, + "placeOpenBraceOnNewLineForFunctions": false, + "placeOpenBraceOnNewLineForControlBlocks": false +} +